/**
 * SanityDocFetcher — full adapter for the DocFetcher port.
 *
 * Fetches documentation from the Sanity Content Lake with support for:
 * - Basic slug-based fetching (GROQ → Portable Text → Markdown)
 * - Perspective-aware fetching (content release diffing)
 * - Document overlays (replace/append canonical docs by document ID)
 * - Direct URL fetching (markdown endpoints + content negotiation)
 * - Custom HTTP headers
 * - Document manifest (traceability: _id, _rev, slug, title)
 * - Release impact metadata (added/modified/removed/unchanged)
 *
 * The pipeline step (FetchDocsStep) calls ctx.docFetcher.fetch() and
 * writes the returned metadata to disk for downstream consumption.
 */
import { type DocFetcher, type DocSourceConfig, type DocumentManifestEntry, type FetchResult, type GeneralizedTaskDefinition, type IdDocRef, type PathDocRef, type PerspectiveDocRef, type PerspectiveExpansion, type ProductFeature, type ReleaseImpactDoc, type SlugDocRef } from "../../_vendor/ailf-core/index.d.ts";
/** Sanity Content Lake source — narrows `DocSourceConfig.kind` to its literal. */
export interface SanityContentLakeSource extends DocSourceConfig {
    kind: "sanity-content-lake";
}
/** Sanity-tagged variants of core's four ref interfaces. */
export type TaggedSlugDocRef = SlugDocRef & {
    kind?: "sanity-slug";
};
export type TaggedPathDocRef = PathDocRef & {
    kind?: "sanity-path";
};
export type TaggedIdDocRef = IdDocRef & {
    kind?: "sanity-id";
};
export type TaggedPerspectiveDocRef = PerspectiveDocRef & {
    kind?: "sanity-release-perspective";
};
/**
 * The ref union the Sanity adapter accepts. Untagged refs (legacy form) still
 * satisfy each variant because `kind` is optional at core.
 */
export type SanityDocRef = TaggedSlugDocRef | TaggedPathDocRef | TaggedIdDocRef | TaggedPerspectiveDocRef;
/**
 * Build the `FetchMetadata.perspectiveExpansion` envelope from the
 * collected perspective refs and their fetch-time document expansion.
 *
 * Perspective doc refs expand one-to-many only during fetch, so this is
 * the single point where the expansion (release → docs) and the
 * attribution (task → releases) are both known. Returning them together
 * lets the orchestration step attribute perspective-expanded documents back
 * to their owning tasks during release auto-scope computation.
 *
 * The expansion is keyed type-agnostically on the document key (D0084):
 * `docsByPerspective` is the authoritative surface auto-scope joins on, and
 * `slugsByPerspective` is the slug view (a slugless doc surfaces its base
 * `documentId`) retained for display / back-compat.
 *
 * Exported for unit testing.
 */
export declare function buildPerspectiveExpansion(perspectiveRefs: {
    perspective: string;
    taskId: string;
}[], perspectiveToDocs: Map<string, ReleaseImpactDoc[]>): PerspectiveExpansion;
/**
 * Collapse manifest rows that share a document key, keeping the richer entry.
 *
 * A task that pins the SAME document by both an `id` ref and a `perspective`
 * ref resolves it twice — once from the published baseline ({@link
 * SanityDocFetcher} `resolveIdRefs`) and once as a release member
 * (`resolvePerspectiveDocContent`) — so two {@link DocumentManifestEntry} rows
 * arrive for one document (D0085). This is manifest hygiene, not a correctness
 * fix: every downstream consumer already collapses on the base `documentId`
 * (auto-scope's `Set`/`Map`, attribution), so the only artifact is a redundant
 * row if the raw `FetchMetadata.manifest` is read unaggregated.
 *
 * Keyed on the base `documentId` (the D0084 document key the diff and
 * attribution already join on; every producer here stamps a base `_id`, so this
 * is "one row per `_id`"). The winner comes from {@link richerManifestEntry} — a
 * pure function of entry content — so the surfaced row is independent of
 * `manifest.push` order (W-dedup-document-manifest).
 *
 * Exported for unit testing.
 */
export declare function dedupeManifestByDocId(manifest: DocumentManifestEntry[]): DocumentManifestEntry[];
/**
 * Pick the entry to surface when two manifest rows collide on a document key.
 *
 * Order-independent — a total order over entry content, so reordering the
 * `manifest.push` loops can never change which row wins. Precedence: prefer the
 * locator that names a `documentType` (D0084), then the release-version locator
 * (a `perspective` is pinned) over the published one, then the lexically-greater
 * `_rev` as a stable final tiebreak. A full tie means the same version read the
 * same way, so either entry surfaces identical data.
 *
 * Exported for unit testing.
 */
export declare function richerManifestEntry(a: DocumentManifestEntry, b: DocumentManifestEntry): DocumentManifestEntry;
/** Optional construction-time collaborators for SanityDocFetcher. */
export interface SanityDocFetcherOptions {
    /**
     * Product feature registry powering the perspective-pin relatedness
     * guard (section→area mapping). When omitted, the guard still warns on
     * empty expansions but skips the area-relatedness assessment.
     */
    features?: ProductFeature[];
}
export declare class SanityDocFetcher implements DocFetcher {
    private readonly rootDir;
    private readonly features;
    /**
     * Per-URL cache of attachment-body fetches. A single `typesReference` doc is
     * now fetched once for the grader symbol index and again for each task that
     * renders it (relevance-scoped per D0069 §5), so without this a 658 KB
     * typedoc would be downloaded N+1 times per run. Sanity file-asset URLs are
     * content-addressed (the asset id is in the path), so a cached body never
     * goes stale. The promise is cached so concurrent callers dedupe too. A
     * failed fetch resolves to `null` (never rejects) and is also memoized, so a
     * transient failure fails fast for the whole run rather than re-downloading.
     */
    private readonly attachmentBodyCache;
    constructor(rootDir: string, options?: SanityDocFetcherOptions);
    fetch(tasks: GeneralizedTaskDefinition[], source?: DocSourceConfig): Promise<FetchResult>;
    private fetchInternal;
    private fetchManifest;
    /**
     * Warn when a bare `slug` ref resolves to more than one published article.
     *
     * The by-slug content fetch (`ARTICLE_BY_SLUG_QUERY`, `...[0]`) can only
     * return the first match, so a colliding legacy slug ref silently injects an
     * arbitrary winner. Naming the candidate `_id`s surfaces the ambiguity so the
     * ref can be pinned to an id — the collision-safe path id refs already take.
     * Id-resolved slugs are excluded: those refs already name a specific
     * document, so their slug appearing on multiple articles is expected and not
     * a warning.
     */
    private warnOnAmbiguousSlugRefs;
    /**
     * Batch-resolve document ID refs without filtering on `_type`.
     *
     * Articles are routed back into the slug-flow (so manifest, perspective
     * diffing, and overlay handling continue to work unchanged). Non-articles
     * are rendered eagerly via the renderer registry — `typesReference` gets
     * a high-fidelity formatter; anything else falls through to the default
     * walker so authors can pin marketing pages, glossary entries, etc.
     * without an AILF code change.
     *
     * Three log shapes:
     *   - `info` — doc rendered with the default formatter (suggests a
     *     dedicated renderer would improve fidelity)
     *   - `warn` — registered renderer produced empty content (W0195 AC#3)
     *   - `warn` — id had no matching document (existed/wrong tenant/typo)
     */
    private resolveIdRefs;
    /**
     * Fetch the raw body of a Sanity file asset URL, used by the
     * `typesReference` renderer to inline typedoc JSON. Returns `null`
     * on any HTTP/network failure rather than throwing — the renderer
     * surfaces a placeholder so the rest of the context still renders.
     *
     * Wrapped in a 30s `AbortSignal.timeout` so a slow CDN can't hang the
     * eval pipeline indefinitely. Timeouts surface as a single `null`
     * return like any other fetch failure. Results are memoized per URL (see
     * `attachmentBodyCache`).
     */
    private fetchAttachmentBody;
    private fetchAttachmentBodyUncached;
    /**
     * Resolve path-based canonical doc references to their article slugs.
     *
     * Handles two path formats:
     * - Simple path: "webhooks" → resolves as slug lookup
     * - Section-qualified path: "content-lake/webhooks" → matches section + slug
     *
     * @see docs/design-docs/canonical-doc-resolution.md
     */
    private resolvePathRefsToSlugs;
    /**
     * Expand perspective canonical doc refs to the **document keys** (D0084)
     * within each content release, type-agnostically.
     *
     * Each perspective ref resolves to ALL documents versioned in that release
     * — of any type — over the type-agnostic `DOCS_IN_RELEASE_QUERY`. This is
     * the one-to-many expansion described in the canonical-doc-resolution design
     * doc, generalized off the article-only `ARTICLES_IN_RELEASE_QUERY` so a
     * release of `openApiReference` (or any typeless) version docs expands to its
     * members rather than to zero. Each entry carries the base `documentId` (the
     * cross-version join key auto-scope intersects on), the `documentType` when
     * projected, and the `slug` for display (absent for typeless docs).
     *
     * The perspective value from the ref is used as both the release ID
     * for the versioned document query AND the client perspective for
     * fetching content. If a source-level perspective is also set, the
     * ref-level perspective takes precedence for its own resolution.
     *
     * Expansion is intentionally unconditional; a stale or unrelated pin is
     * surfaced through the non-fatal {@link warnOnSuspectPerspectivePin}
     * guard rather than gating the fetch.
     *
     * @see docs/design-docs/canonical-doc-resolution.md
     * @see docs/design-docs/doctype-agnostic-eval.md
     */
    private resolvePerspectiveRefsToDocs;
    /**
     * Non-fatal stale-pin guard for one perspective ref expansion.
     *
     * Warns when the pinned release expanded to no articles (release gone,
     * published, or empty) or when the expanded articles' sections are
     * unrelated to the pinning task's area per the feature-registry
     * section→area mapping (see {@link buildPerspectivePinWarnings}).
     * Warning only — expansion remains unconditional and the fetch never
     * fails because of a suspect pin.
     */
    private warnOnSuspectPerspectivePin;
    /**
     * Render the NON-article members of each expanded perspective ref through the
     * type-agnostic `DOCS_BY_IDS_QUERY` → renderer-registry path — the same
     * content path id-refs take — so a release of `openApiReference` (or any
     * non-article) docs reaches the per-task grading context and the document
     * manifest, instead of being attributed-but-unrendered (the by-slug flow
     * returns nothing for a non-article slug). Closes
     * W-doctype-renderer-contract AC#2 / D0085.
     *
     * Article members are intentionally excluded — they ride the by-slug flow
     * (`fetchCanonicalDocs` → `ARTICLE_BY_SLUG_*`) that the manifest, release
     * diff, and document overlay all key on. Each non-article member is fetched
     * under its release perspective (so it renders from the release version, the
     * point of pinning a release) and rendered once here; the per-task assembly
     * loop stitches the pre-rendered content in by document id. Returns the
     * rendered content keyed by base `documentId`, the per-perspective member-id
     * lists, and manifest entries to merge.
     *
     * Empty/unresolved members are warned and skipped (mirrors `resolveIdRefs`).
     * Members already rendered from an earlier perspective are not refetched.
     */
    private resolvePerspectiveDocContent;
    /**
     * Diff published-vs-perspective on the **document key** (base `documentId`,
     * D0084) over the type-agnostic `DOCS_METADATA_BY_IDS_QUERY`, so a non-article
     * doc modified inside a content release is detected — the article-`slug` join
     * over `ARTICLES_METADATA_BY_SLUGS_QUERY` could not see it.
     *
     * The in-scope set is the manifest the fetcher already resolved (every doc of
     * any type, with `_id`, `slug`, and the locator's `documentType`). Both
     * clients are queried for the same base document ids; `_rev` drives the
     * modified test exactly as the slug join did. Each affected population is
     * returned both as document-key entries (`*Docs`, authoritative) and as the
     * slug `string[]` view for display / console / back-compat — a slugless doc
     * surfaces its base `documentId` in the slug view so it stays visible.
     */
    private identifyAffectedDocs;
    /**
     * Compute the docs versioned in the release that are NOT canonical docs of
     * any in-scope task, via the full type-agnostic `sanity::partOfRelease`
     * footprint (`DOCS_IN_RELEASE_QUERY`) — so a non-article release edit outside
     * the evaluated set is surfaced, not just articles.
     *
     * Returns `undefined` (not `[]`) when the footprint query fails, preserving
     * the three-valued `ReleaseImpact.uncovered` contract: absent = not computed,
     * `[]` = checked and fully covered, non-empty = unmeasured docs named.
     */
    private identifyUncoveredReleaseDocs;
    private resolveDocumentOverlay;
    private fetchArticleById;
    private fetchArticleBySlug;
    private fetchArticleBySlugWithPerspective;
    private fetchCanonicalDocs;
    /**
     * Refetch the release-affected id-ref docs under the release perspective and
     * overwrite their resolved content + manifest entry, so they enter the
     * rendered per-task context from their RELEASE version rather than the
     * published baseline that {@link resolveIdRefs} fetched (D0084 /
     * doctype-agnostic-eval inventory #7).
     *
     * This is the id-keyed twin of the article affected-slug refetch in
     * {@link fetchCanonicalDocs}: the slug flow refetches a changed article via
     * `ARTICLE_BY_SLUG_WITH_PERSPECTIVE_QUERY`, but a non-article never enters
     * `docsBySlug` (it has no slug join and rides the `contentById` path), so it
     * cannot be refetched per-slug. Keying the refetch on the document id — the
     * cross-version document key the diff already computed — lets any document
     * type render from its release version with no slug dependence.
     *
     * Affected = the `added` ∪ `modified` document keys from the release diff. The
     * refetch matches each resolved id-ref entry to an affected doc on the base
     * `documentId` (the author may pin a published id while the diff normalizes a
     * versioned one), refetches the whole affected set in one `DOCS_BY_IDS_QUERY`
     * under a perspective-stacked client, and re-renders through the same renderer
     * registry as the baseline resolution. Both the eagerly-rendered `contentById`
     * entries and the lazily-rendered `typesReferenceById` raw docs are
     * overwritten in place (a `typesReference` is rendered per task, so only its
     * stashed doc + symbol index need the release version), along with the
     * matching manifest entry's `_rev`/locator — so the manifest carries the
     * release `_rev` and the document version is recorded. Unresolved/empty docs
     * are left untouched.
     */
    private refetchAffectedDocsByPerspective;
    /**
     * Generate feature-area context files (one per GROQ feature area query).
     *
     * This is NOT part of the DocFetcher port — it's an opt-in CLI feature
     * used by `ailf fetch-docs --include-feature-areas`.
     */
    fetchFeatureAreaContexts(source?: DocSourceConfig): Promise<void>;
    /**
     * Generate a full corpus context file (all articles in one markdown file).
     *
     * This is NOT part of the DocFetcher port — it's an opt-in CLI feature
     * used by `ailf fetch-docs --include-corpus`.
     */
    fetchFullCorpus(source?: DocSourceConfig): Promise<void>;
}
