← Files LuciaARCHIVED FILE

modules/studio-archive/scripts/folder_intake.py

14.7 KB · Nov 11, 2026 · 18:09 UTC

↓ Download file

See the change to this file →

"""Authorized, receipted intake of one external tree into client working files."""

from __future__ import annotations

import json
import os
import re
import stat
import unicodedata
from pathlib import Path
from typing import Any
from uuid import uuid4

import client_ledger as ledger

__all__ = ["copy_source_folder"]

_PENDING = ".vera-folder-intake-pending.json"
_SCHEMA = "vera.client_folder_intake.v1"
_MAX_ENTRIES = 5000
_MAX_BYTES = 2 * 1024 * 1024 * 1024


def _safe_name(name: str) -> None:
    """Keep source names portable without renaming or interpreting documents."""
    if (
        not name
        or len(name) > 255
        or name in {".", "..", _PENDING}
        or re.search(r'[<>:"/\\\\|?*\x00-\x1f\x7f]', name)
        or name.endswith((" ", "."))
        or name.split(".")[0].upper()
        in {
            "CON",
            "PRN",
            "AUX",
            "NUL",
            *(f"COM{i}" for i in range(1, 10)),
            *(f"LPT{i}" for i in range(1, 10)),
        }
    ):
        raise ledger.LedgerError("Folder intake contains an unsafe portable name.")


def _inventory(root: Path, *, pending_allowed: bool = False) -> dict[str, Any]:
    """Hash a complete bounded tree; links and special files fail closed."""
    ledger._ordinary_directory(root, label="folder intake directory")
    directories: list[str] = []
    files: list[dict[str, Any]] = []
    total = 0
    queue = [root]
    portable_paths: set[str] = set()
    while queue:
        directory = queue.pop()
        if (
            ledger._ordinary_directory(directory, label="intake subdirectory")
            != directory
        ):
            raise ledger.LedgerError("Folder intake cannot follow symbolic links.")
        for path in sorted(directory.iterdir()):
            if pending_allowed and path == root / _PENDING:
                continue
            _safe_name(path.name)
            observed = path.lstat()
            if path.is_symlink():
                raise ledger.LedgerError("Folder intake cannot follow symbolic links.")
            relative = path.relative_to(root).as_posix()
            portable = unicodedata.normalize("NFC", relative).casefold()
            if portable in portable_paths:
                raise ledger.LedgerError(
                    "Folder intake names collide on a portable filesystem."
                )
            portable_paths.add(portable)
            if stat.S_ISDIR(observed.st_mode):
                directories.append(relative)
                queue.append(path)
            elif stat.S_ISREG(observed.st_mode):
                if observed.st_size > _MAX_BYTES - total:
                    raise ledger.LedgerError("Folder intake exceeds its 2 GB boundary.")
                size, digest = ledger._stable_file_identity(path, label="intake source")
                total += size
                files.append(
                    {"relative_path": relative, "byte_count": size, "sha256": digest}
                )
            else:
                raise ledger.LedgerError(
                    "Folder intake accepts only ordinary files and directories."
                )
            if len(files) + len(directories) > _MAX_ENTRIES or total > _MAX_BYTES:
                raise ledger.LedgerError("Folder intake exceeds 5,000 entries or 2 GB.")
    return {
        "directories": sorted(directories),
        "files": sorted(files, key=lambda x: x["relative_path"]),
    }


def copy_source_folder(
    client_root: Path,
    client_id: str,
    engagement_id: str,
    source: Path,
    destination_name: str | None = None,
) -> dict[str, Any]:
    """Preserve one selected tree outside Vera/, with explicit interrupted retry."""
    root = ledger._ordinary_directory(client_root, label="selected client folder")
    selected = source.expanduser()
    if not selected.is_absolute():
        raise ledger.LedgerError("Select an absolute folder intake source.")
    resolved = ledger._ordinary_directory(selected, label="folder intake source")
    if resolved != selected:
        raise ledger.LedgerError("Folder intake source cannot contain symbolic links.")
    name = destination_name if destination_name is not None else source.name
    _safe_name(name)
    if name.casefold() == ledger.LEDGER_DIRECTORY.casefold():
        raise ledger.LedgerError(
            "Folder intake cannot replace the managed Vera directory."
        )
    inventory = _inventory(resolved)
    if not inventory["files"]:
        raise ledger.LedgerError("Folder intake has no ordinary source files.")
    request = {
        "schema_version": _SCHEMA,
        "client_id": client_id,
        "engagement_id": engagement_id,
        "source_name": resolved.name,
        "destination_name": name,
        "inventory": inventory,
    }
    intake_id = "intake_" + ledger._canonical_json_sha256(request)[:24]
    maximum_receipt = ledger._sealed(
        {
            "request": request,
            "intake_id": intake_id,
            "status": "completed",
            "created_at": ledger._now_iso(),
            "completed_at": ledger._now_iso(),
        }
    )
    if (
        len(
            json.dumps(
                maximum_receipt, ensure_ascii=False, indent=2, sort_keys=True
            ).encode("utf-8")
        )
        + 1
        > ledger._MAX_JSON_BYTES
    ):
        raise ledger.LedgerError("Folder intake receipt exceeds its bounded size.")
    destination = root / name
    with ledger._engagement_lock(root, engagement_id):
        client = ledger.load_client_manifest(root)
        engagement = ledger.load_engagement_manifest(root, engagement_id)
        if client["client_id"] != client_id or engagement["client_id"] != client_id:
            raise ledger.LedgerError(
                "Folder intake client and engagement do not match."
            )
        if engagement["status"] != "open":
            raise ledger.LedgerError(
                "A closed engagement cannot receive a folder intake."
            )
        store = ledger._ordinary_directory(
            ledger._engagement_root(root, engagement_id) / "folder_intakes",
            label="folder intake receipts",
            create=True,
        )
        receipt_path = store / (intake_id + ".json")
        reused = receipt_path.exists() or receipt_path.is_symlink()
        if reused:
            receipt = ledger._validate_seal(
                ledger._read_json(receipt_path, label="folder intake receipt"),
                label="folder intake receipt",
            )
            if receipt["request"] != request or receipt["intake_id"] != intake_id:
                raise ledger.LedgerError(
                    "Folder intake receipt does not match this request."
                )
        else:
            if destination.exists() or destination.is_symlink():
                raise ledger.LedgerError(
                    "The delivery folder already exists; choose a separate delivery name."
                )
            receipt = {
                "request": request,
                "intake_id": intake_id,
                "status": "prepared",
                "created_at": ledger._now_iso(),
            }
            ledger._write_json(receipt_path, ledger._sealed(receipt))
        marker = destination / _PENDING
        if not destination.exists() and not destination.is_symlink():
            if reused and receipt["status"] == "completed":
                raise ledger.LedgerError("Previously copied intake folder is missing.")
            destination.mkdir(mode=0o700)
            # A crash between exclusive mkdir and this marker is intentionally
            # uncertain. A retry never adopts an unmarked pre-existing folder.
            with marker.open("x", encoding="utf-8") as handle:
                handle.write(intake_id)
                handle.flush()
                os.fsync(handle.fileno())
        ledger._ordinary_directory(destination, label="intake destination")
        if destination.resolve(strict=True) != destination:
            raise ledger.LedgerError(
                "Folder intake destination cannot contain symbolic links."
            )
        if receipt["status"] == "completed":
            if _inventory(destination, pending_allowed=True) != inventory:
                raise ledger.LedgerError(
                    "Previously copied intake files changed or moved."
                )
            if marker.exists() or marker.is_symlink():
                ledger._ordinary_file(marker, label="folder intake pending marker")
                if marker.read_text(encoding="utf-8") != intake_id:
                    raise ledger.LedgerError(
                        "Folder intake marker belongs to another request."
                    )
                marker.unlink()
            return _result(
                receipt_path, destination, intake_id, inventory, "already_copied"
            )
        ledger._ordinary_file(marker, label="folder intake pending marker")
        if marker.read_text(encoding="utf-8") != intake_id:
            raise ledger.LedgerError(
                "Folder intake has an uncertain or foreign destination."
            )
        expected = {row["relative_path"]: row for row in inventory["files"]}
        publication = store / (intake_id + "-publication.json")
        pending = None
        if publication.exists() or publication.is_symlink():
            progress = ledger._validate_seal(
                ledger._read_json(publication, label="intake publication"),
                label="intake publication",
            )
            if progress.get("intake_id") != intake_id:
                raise ledger.LedgerError(
                    "Folder intake publication belongs to another request."
                )
            pending = progress.get("pending_file")
        if pending is not None:
            if not isinstance(pending, str) or pending not in expected:
                raise ledger.LedgerError(
                    "Folder intake pending publication is invalid."
                )
            partial = destination / pending
            if (
                ledger._ordinary_directory(
                    partial.parent, label="pending intake parent"
                )
                != partial.parent
            ):
                raise ledger.LedgerError("Folder intake cannot follow symbolic links.")
            if partial.exists() or partial.is_symlink():
                observed_bytes = ledger._stable_file_identity(
                    partial, label="pending intake file"
                )
                row = expected[pending]
                if observed_bytes != (row["byte_count"], row["sha256"]):
                    # Never discard bytes written during an interrupted copy,
                    # including a later user edit before publication committed.
                    evidence_root = ledger._ordinary_directory(
                        store / (intake_id + "-evidence"),
                        label="intake recovery evidence",
                        create=True,
                    )
                    evidence = ledger._ordinary_directory(
                        evidence_root / uuid4().hex,
                        label="intake recovery evidence",
                        create=True,
                    )
                    # Move the actual entry rather than deleting it after a
                    # copy: an edit between verification and unlink would be
                    # lost. A concurrent writer retains the moved inode too.
                    preserved = partial.rename(evidence / "partial-copy")
                    ledger._stable_file_identity(
                        preserved, label="preserved intake recovery evidence"
                    )
        observed = _inventory(destination, pending_allowed=True)
        if not set(observed["directories"]).issubset(inventory["directories"]) or any(
            expected.get(row["relative_path"]) != row for row in observed["files"]
        ):
            raise ledger.LedgerError(
                "Pending intake contains unexpected or changed files."
            )
        for relative in inventory["directories"]:
            path = destination / relative
            if not path.exists() and not path.is_symlink():
                path.mkdir(mode=0o700, parents=True)
            if (
                ledger._ordinary_directory(path, label="intake destination directory")
                != path
            ):
                raise ledger.LedgerError(
                    "Folder intake destination contains symbolic links."
                )
        for row in inventory["files"]:
            target = destination / row["relative_path"]
            original = resolved / row["relative_path"]
            if target.exists() or target.is_symlink():
                size, digest = ledger._stable_file_identity(
                    target, label="retained intake file"
                )
            else:
                ledger._write_json(
                    publication,
                    ledger._sealed(
                        {
                            "intake_id": intake_id,
                            "pending_file": row["relative_path"],
                        }
                    ),
                )
                size, digest = ledger._stable_copy(
                    original,
                    target,
                    exclusive=True,
                    temporary_directory=store,
                )
            if (size, digest) != (row["byte_count"], row["sha256"]):
                raise ledger.LedgerError(
                    "Copied folder intake file does not match its source."
                )
            publication.unlink(missing_ok=True)
        if (
            _inventory(resolved) != inventory
            or _inventory(destination, pending_allowed=True) != inventory
        ):
            raise ledger.LedgerError(
                "Folder intake source or destination changed during copying."
            )
        receipt.pop("content_sha256", None)
        receipt.update(status="completed", completed_at=ledger._now_iso())
        ledger._write_json(receipt_path, ledger._sealed(receipt))
        marker.unlink()
        return _result(receipt_path, destination, intake_id, inventory, "copied")


def _result(
    receipt: Path,
    destination: Path,
    intake_id: str,
    inventory: dict[str, Any],
    status: str,
) -> dict[str, Any]:
    return {
        "status": status,
        "intake_id": intake_id,
        "receipt_path": str(receipt),
        "destination_path": str(destination),
        "file_count": len(inventory["files"]),
        "directory_count": len(inventory["directories"]),
        "byte_count": sum(row["byte_count"] for row in inventory["files"]),
        "original_preserved": True,
        "tree_preserved": True,
        "recovery_evidence_path": (
            str(receipt.parent / (intake_id + "-evidence"))
            if (receipt.parent / (intake_id + "-evidence")).is_dir()
            else None
        ),
        "document_meaning_qualified": False,
    }

SHA-256: 834a3d5462167d9109346ff6623e0e09365324a5a969c6cb8d283e1ac0deb111