import type { CacheRefScope } from '@kici-dev/engine';
import type { CacheStorage } from '../storage/types.js';
/**
 * Cluster-wide default quota: 5 GiB. Serves as the fallback when an org has no
 * per-org override in `org_settings.user_cache_quota_bytes`. The cluster-wide
 * value is itself operator-configurable via KICI_USER_CACHE_QUOTA_BYTES.
 */
export declare const DEFAULT_USER_CACHE_QUOTA_BYTES: number;
/**
 * Cluster-wide default entry TTL: 7 days. Fallback when an org has no per-org
 * override in `org_settings.user_cache_ttl_ms`. The cluster-wide value is
 * operator-configurable via KICI_USER_CACHE_TTL_MS.
 */
export declare const DEFAULT_USER_CACHE_TTL_MS: number;
/**
 * Per-org override of the cache quota + TTL, read from `org_settings` at
 * operation time. A field left `undefined` means "no per-org override" — the
 * cluster-wide default applies. Injected into `UserCache` so the cache stays
 * decoupled from the DB layer (and unit-testable with a stub reader).
 */
export interface UserCacheOrgLimits {
    quotaBytes?: number;
    ttlMs?: number;
}
/** Resolves the per-org cache limits for an org id (e.g. an `org_settings` read). */
export type UserCacheOrgLimitsReader = (orgId: string) => Promise<UserCacheOrgLimits>;
/** Identifies the org + repo + write scope a cache operation targets. */
export interface UserCacheRef {
    org: string;
    repo: string;
    scope: CacheRefScope;
    /** Required when scope === 'isolated' — the per-run isolation namespace. */
    runId?: string;
}
/** Outcome of a restore: whether an entry matched and how to fetch it. */
export interface UserCacheRestoreResult {
    hit: boolean;
    matchedKey?: string;
    downloadUrl?: string;
    tarHash?: string;
}
/** Outcome of begin-save: a presigned PUT to a temp key, or skip when the key already exists. */
export interface UserCacheBeginSaveResult {
    skip: boolean;
    uploadUrl?: string;
    tempKey?: string;
}
export declare class UserCache {
    private readonly storage;
    /** Cluster-wide default quota (the `KICI_USER_CACHE_QUOTA_BYTES` value). */
    private readonly defaultQuotaBytes;
    /** Cluster-wide default TTL (the `KICI_USER_CACHE_TTL_MS` value). */
    private readonly defaultTtlMs;
    /** Optional per-org override reader; absent = always use the cluster defaults. */
    private readonly orgLimitsReader?;
    constructor(opts: {
        storage: CacheStorage;
        /** Cluster-wide default quota (env-var default). */
        quotaBytes?: number;
        /** Cluster-wide default TTL (env-var default). */
        ttlMs?: number;
        /** Per-org override reader (org_settings). When unset, defaults apply. */
        orgLimitsReader?: UserCacheOrgLimitsReader;
    });
    /**
     * Resolve the effective quota + TTL for an org: the per-org override from
     * `org_settings` when present, otherwise the cluster-wide default. A reader
     * failure falls back to the defaults (logged) — the cache must never fail a
     * restore/save because the settings lookup hiccupped.
     */
    private resolveLimits;
    /**
     * Sanitize a path segment so a key can never escape its org/repo/scope
     * namespace. Beyond stripping disallowed characters, a segment consisting
     * only of dots (`.`, `..`, …) is replaced wholesale: such a segment is a
     * dot-segment that HTTP/S3 path canonicalization collapses (`a/./b` → `a/b`,
     * `a/../b` → `b`), which both corrupts the namespace and breaks the SigV4
     * signature on a pre-signed PUT/GET. Repo identifiers like `.` (the internal
     * provider's repo id) hit exactly this case, so the all-dots guard keeps the
     * object key canonical and the namespace boundary intact.
     */
    private seg;
    /** Org-level prefix: the per-tenant isolation boundary and quota scope. */
    private orgPrefix;
    /** Org + repo prefix shared by every scope of a repo. */
    private repoPrefix;
    /** Namespace prefix for the WRITE scope of a ref (shared OR per-run isolated). */
    private writePrefix;
    /** Namespace prefixes the ref may READ, in priority order. Isolated reads its own run scope, then shared. */
    private readPrefixes;
    private finalKey;
    /** Restore: try the exact key across read prefixes, then restoreKeys prefix scan (newest wins). */
    restore(ref: UserCacheRef & {
        key: string;
        restoreKeys?: string[];
    }): Promise<UserCacheRestoreResult>;
    /** Try the exact key in read-prefix priority order. */
    private restoreExact;
    /** restoreKeys prefix fallback (ordered); within a prefix, list() returns newest-first. */
    private restoreByPrefix;
    /** Begin a save: presigned PUT to a temp key, or skip=true when the immutable key exists. */
    beginSave(ref: UserCacheRef & {
        key: string;
    }): Promise<UserCacheBeginSaveResult>;
    /** Commit a save: copy temp -> final, init metadata, store companion hash/size, delete temp, enforce quota. */
    commitSave(ref: UserCacheRef & {
        key: string;
        tarHash: string;
        sizeBytes: number;
        tempKey?: string;
    }): Promise<void>;
    private readHash;
    /** Evict oldest entries for the org until total tarball size <= the per-org quota. */
    private enforceQuota;
}
//# sourceMappingURL=user-cache.d.ts.map