/**
 * Authoritative ownership index for immutable-keyed assets.
 *
 * Assets are stored under an immutable key `{assetId}/{filename}` (assetId =
 * UUIDv4), so the storage location no longer encodes the owning entity's
 * mutable `(type, id)`. This table is the single source of truth for:
 *   - per-repo authorization on the serving route (`repo`), and
 *   - "which assets does entity E own?" for delete/transfer (`owner_type`,
 *     `owner_id`).
 *
 * It is written authoritatively at upload and updated by every mutation path
 * (rename/changeType/move/transfer/delete). It is NOT rebuilt from entity
 * bodies: deriving ownership from user-editable `/api/assets/{id}` embeds would
 * let a user in repo A claim an asset owned by repo B by pasting its URL —
 * a privilege-escalation vector. Treat this db as durable critical state
 * (it lives on the same persistent workspace volume as auth.db / secrets.db),
 * not a regenerable cache.
 *
 * `migration_ledger` is a transient table used only by the one-time
 * old→new key migration to stay idempotent; it is dropped after cutover.
 */
export interface AssetIndexRow {
    assetId: string;
    repo: string;
    ownerType: string;
    ownerId: string;
    filename: string;
    contentType: string;
    mediaType: string;
    name: string | null;
    /** Auth user id (as string) of whoever uploaded the asset, or null for
     *  rows predating this column / system / agent uploads. Drives the
     *  "delete restricted to uploader + workspace owner" rule on the workspace
     *  asset store. NOT an ownership field (that's owner_type/owner_id) — purely
     *  provenance for delete authz. */
    uploaderUserId: string | null;
    /** Caption for the asset (alt-text / OCR / vision description). DB-only and
     *  durable next to the bytes, so it survives deletion of any entity that
     *  merely embeds the asset (asset-as-embed model). Populated by the async
     *  caption pass, not synchronously at upload — null until then. */
    caption: string | null;
    /** Byte size of the stored blob. Recorded at upload so the asset manager's
     *  ownerless listing can show size without resolving an entity (the legacy
     *  `asset` entity carried `asset_size` in frontmatter; free-standing assets
     *  carry it here). Null for rows predating this column. */
    size: number | null;
    createdAt: string;
}
export type AssetIndexInput = Omit<AssetIndexRow, 'createdAt' | 'uploaderUserId' | 'caption' | 'size'> & {
    createdAt?: string;
    /** Optional — omit/null when the uploader isn't an identified web user
     *  (CLI/import/agent). Existing put() callers stay source-compatible. */
    uploaderUserId?: string | null;
    /** Optional — set at upload when known (buffer length). Existing callers
     *  that omit it record null. */
    size?: number | null;
};
/** Owner-reassignment changes for a mutation. Any subset may be supplied. */
export interface OwnerReassignment {
    ownerType?: string;
    ownerId?: string;
    repo?: string;
}
export declare class AssetIndexService {
    private db;
    constructor(workspacePath: string);
    /** Keep assets.db (+ WAL/SHM) out of the workspace config repo — it's
     *  local, durable on the workspace volume, and not safely rebuildable from
     *  bodies, so it's never committed. Mirrors auth.db / secrets.db. */
    private ensureGitignore;
    /** Insert or replace the row for an assetId (idempotent on re-run).
     *  `caption` is intentionally NOT in the ON CONFLICT update set: it's
     *  written later by the async caption pass via `setCaption`, and a re-put
     *  (e.g. migration) must not clobber an existing caption back to null. */
    put(input: AssetIndexInput): void;
    /** Set (or clear) the caption for an asset. Written by the async caption
     *  pass; isolated from `put` so it never touches ownership/size columns. */
    setCaption(assetId: string, caption: string | null): void;
    /** True if `user` may delete the given asset: the original uploader, or a
     *  workspace owner. Unknown-uploader rows (null) are owner-only. This is the
     *  D2 rule for the workspace asset store; folder-scoped assets continue to
     *  use folder write access. `isWorkspaceOwner` is passed in to keep this
     *  service free of an auth-service dependency. */
    canDelete(assetId: string, userId: number | undefined, isWorkspaceOwner: boolean): boolean;
    get(assetId: string): AssetIndexRow | null;
    /**
     * Assets owned by `(repo, ownerType, ownerId)`. The `repo` scope is
     * mandatory: entity ids are unique only within (repo, type), NOT across the
     * workspace — `note/foo` can exist in two repos. Matching on owner alone
     * would let a delete/rename in repo A touch repo B's identically-named
     * entity's assets (cross-repo data loss).
     */
    listByOwner(repo: string, ownerType: string, ownerId: string): AssetIndexRow[];
    listByRepo(repo: string): AssetIndexRow[];
    /**
     * Reassign every asset owned by `(repo, fromType, fromId)`. Used by mutations:
     *   - rename:      reassignOwner(repo, type, oldId, { ownerId: newId })
     *   - changeType:  reassignOwner(repo, oldType, id, { ownerType: newType })
     *   - cross-repo move/transfer: reassignOwner(sourceRepo, type, id, { repo: target })
     * `repo` scopes the match (see listByOwner) — without it a rename in one repo
     * would reassign a same-named entity's assets in another. Returns rows updated.
     * No blob movement — keys are immutable.
     */
    reassignOwner(repo: string, fromType: string, fromId: string, changes: OwnerReassignment): number;
    /**
     * Relocate a single free-standing asset to another folder, addressed by its
     * immutable assetId (the asset manager's move). No blob movement — the key is
     * `{assetId}/{filename}`, independent of repo; only the index row's scope
     * changes. The relocated asset is normalized to folder-owned by its new repo
     * (ownerType='folder', ownerId=targetRepo) — a free-standing asset's owner IS
     * its folder, so this keeps the row coherent rather than stranding a stale
     * owner_type/owner_id in the destination. Returns true if a row was updated.
     * The caller gates this to free-standing assets.
     */
    moveByAssetId(assetId: string, targetRepo: string): boolean;
    delete(assetId: string): void;
    ledgerGet(repo: string, oldKey: string): string | null;
    ledgerPut(repo: string, oldKey: string, assetId: string): void;
    ledgerEntries(): Array<{
        repo: string;
        oldKey: string;
        assetId: string;
    }>;
    dropLedger(): void;
    close(): void;
    private toRow;
}
