← Files LuciaARCHIVED FILE

modules/comunicazione-professionale/scripts/record_history_pseudonymization.py

12.4 KB · Oct 2, 2026 · 00:30 UTC

↓ Download file

#!/usr/bin/env python3
"""Record complete model-pseudonymized derivatives of selected communications."""

from __future__ import annotations

import argparse
import logging
import shutil
import tempfile
from pathlib import Path
from typing import Any

from workflow_core import (
    atomic_write_json,
    atomic_write_text,
    canonical_digest,
    file_digest,
    load_json,
    prompt_template_digest,
    utc_now,
    validate_history_pseudonymization_payload,
    validate_input_integrity,
    workflow_lock,
)

__all__ = ["record_history_pseudonymization", "main"]

LOGGER = logging.getLogger(__name__)


def _assessment_required_model_task_packet(
    packet: dict[str, Any], *, record_path: Path, record: dict[str, Any]
) -> dict[str, Any]:
    """Keep generation blocked until an independent derivative-only review."""

    updated = dict(packet)
    updated["history_context"] = {
        "status": "privacy_assessment_required",
        "pending_record_path": str(record_path.resolve()),
        "pending_record_digest": record["record_digest"],
        "history_ids": [item["history_id"] for item in record["history_items"]],
        "raw_history_paths_included": False,
        "identity_mapping_included": False,
        "purpose": "studio_voice_and_format_learning_only",
        "privacy_assessment_packet_path": str(
            (record_path.parent / "history_privacy_assessment_packet.json").resolve()
        ),
    }
    return updated


def record_history_pseudonymization(
    run_dir: Path,
    pseudonymization_path: Path,
    *,
    provider: str,
    model: str,
    session_id: str,
    recorded_by: str,
) -> Path:
    """Validate, split, and bind one isolated semantic pseudonymization result."""

    root = run_dir.resolve()
    with workflow_lock(root):
        validate_input_integrity(root)
        source_register = load_json(root / "source_register.json")
        if not source_register["history"]:
            raise ValueError(
                "History pseudonymization is not applicable without selected history"
            )
        final_record_path = root / "history_pseudonymization_record.json"
        if final_record_path.exists():
            raise ValueError(
                "History pseudonymization is already recorded for this run"
            )
        pending_documents_dir = root / "history-pseudonymized-candidate"
        pending_map_path = root / "history_identity_map.pending.json"
        pending_record_path = root / "history_pseudonymization_record.pending.json"
        assessment_packet_path = root / "history_privacy_assessment_packet.json"
        for stale_path in (
            pending_map_path,
            pending_record_path,
            assessment_packet_path,
        ):
            stale_path.unlink(missing_ok=True)
        if pending_documents_dir.exists():
            if pending_documents_dir.is_symlink() or not pending_documents_dir.is_dir():
                raise ValueError("Unsafe pending history candidate path")
            shutil.rmtree(pending_documents_dir)
        result = load_json(pseudonymization_path)
        source = pseudonymization_path.expanduser().resolve(strict=True)
        if source.is_relative_to(root):
            raise ValueError(
                "History-pseudonymization input must be outside the run before recording"
            )
        validate_history_pseudonymization_payload(
            result,
            run_dir=root,
            source_register=source_register,
        )
        normalized_session_id = session_id.strip()
        if len(normalized_session_id) < 8:
            raise ValueError(
                "History-pseudonymization session id must identify one exact host session"
            )

        staging_dir = Path(
            tempfile.mkdtemp(prefix=".history-pseudonymization.", dir=root)
        )
        installed_documents = False
        installed_map = False
        installed_record = False
        installed_packet = False
        try:
            documents_dir = staging_dir / "documents"
            document_records: list[dict[str, Any]] = []
            for item in result["history_items"]:
                destination = documents_dir / f"{item['history_id']}.txt"
                atomic_write_text(destination, item["pseudonymized_document"])
                document_records.append(
                    {
                        "history_id": item["history_id"],
                        "channel": item["channel"],
                        "path": str(
                            (
                                root / "history-pseudonymized" / destination.name
                            ).resolve()
                        ),
                        "sha256": file_digest(destination),
                        "size_bytes": destination.stat().st_size,
                        "transformations_summary": item["transformations_summary"],
                        "residual_identification_risk": item[
                            "residual_identification_risk"
                        ],
                    }
                )

            current_local_map = load_json(root / "history_identity_map.json")
            local_map_path = staging_dir / "history_identity_map.json"
            local_map = {
                "schema_version": 1,
                "workflow": "comunicazione-professionale",
                "run_id": result["run_id"],
                "input_digest": result["input_digest"],
                "local_only": True,
                "never_include_in_downstream_model_context": True,
                "mechanical_mapping": current_local_map["entries"],
                "semantic_mapping": result["identity_mapping"],
            }
            stable_local_map = {
                key: value
                for key, value in local_map.items()
                if key != "mapping_digest"
            }
            local_map["mapping_digest"] = canonical_digest(stable_local_map)
            atomic_write_json(local_map_path, local_map)

            provenance = {
                "provider": provider,
                "model": model,
                "template_version": "professional-communication-history-pseudonymization-v1",
                "template_sha256": prompt_template_digest(
                    "history_pseudonymization",
                    "professional-communication-history-pseudonymization-v1",
                ),
                "session_id": normalized_session_id,
                "execution_mode": "isolated_host_session_attestation",
                "provider_authenticated": False,
                "recorded_by": recorded_by,
                "recorded_at": utc_now(),
            }
            record: dict[str, Any] = {
                "schema_version": 1,
                "workflow": "comunicazione-professionale",
                "run_id": result["run_id"],
                "input_digest": result["input_digest"],
                "purpose": result["purpose"],
                "history_items": document_records,
                "pseudonymization_assessment": result["pseudonymization_assessment"],
                "limitations": result["limitations"],
                "identity_mapping_path": str(
                    (root / "history_identity_map.json").resolve()
                ),
                "identity_mapping_digest": local_map["mapping_digest"],
                "identity_mapping_in_downstream_context": False,
                "model_provenance": provenance,
            }
            record["record_digest"] = canonical_digest(record)
            atomic_write_json(staging_dir / "record.json", record)

            assessment_packet = {
                "schema_version": 1,
                "workflow": "comunicazione-professionale",
                "run_id": result["run_id"],
                "input_digest": result["input_digest"],
                "pseudonymization_record_digest": record["record_digest"],
                "purpose": "independent_residual_identification_review_before_downstream_use",
                "history_documents": [
                    {
                        **{
                            key: row[key]
                            for key in (
                                "history_id",
                                "channel",
                                "sha256",
                                "size_bytes",
                            )
                        },
                        "path": str(
                            (pending_documents_dir / Path(row["path"]).name).resolve()
                        ),
                    }
                    for row in document_records
                ],
                "raw_history_paths_included": False,
                "mechanically_stripped_paths_included": False,
                "identity_mapping_included": False,
                "pseudonymization_transcript_included": False,
                "model_pass_template": {
                    "version": "professional-communication-history-privacy-assessment-v1",
                    "path": str(
                        (
                            Path(__file__).resolve().parents[1]
                            / "prompts"
                            / "history-privacy-assessment-v1.md"
                        ).resolve()
                    ),
                    "sha256": prompt_template_digest(
                        "history_privacy_assessment",
                        "professional-communication-history-privacy-assessment-v1",
                    ),
                },
                "artifact_schema": str(
                    (
                        Path(__file__).resolve().parents[1]
                        / "schemas"
                        / "history_privacy_assessment.schema.json"
                    ).resolve()
                ),
            }
            assessment_packet["packet_digest"] = canonical_digest(assessment_packet)
            atomic_write_json(staging_dir / "assessment-packet.json", assessment_packet)

            final_documents_dir = root / "history-pseudonymized"
            if final_documents_dir.exists():
                raise ValueError("Pseudonymized history directory already exists")
            documents_dir.replace(pending_documents_dir)
            installed_documents = True
            local_map_path.replace(pending_map_path)
            installed_map = True
            (staging_dir / "record.json").replace(pending_record_path)
            installed_record = True
            (staging_dir / "assessment-packet.json").replace(assessment_packet_path)
            installed_packet = True
            packet_path = root / "model_task_packet.json"
            packet = load_json(packet_path)
            atomic_write_json(
                packet_path,
                _assessment_required_model_task_packet(
                    packet,
                    record_path=pending_record_path,
                    record=record,
                ),
            )
        except (OSError, ValueError):
            if installed_documents and pending_documents_dir.exists():
                shutil.rmtree(pending_documents_dir)
            if installed_record:
                pending_record_path.unlink(missing_ok=True)
            if installed_map:
                pending_map_path.unlink(missing_ok=True)
            if installed_packet:
                assessment_packet_path.unlink(missing_ok=True)
            raise
        finally:
            if staging_dir.exists():
                shutil.rmtree(staging_dir)
        return pending_record_path


def main(argv: list[str] | None = None) -> int:
    """Record one ready history-pseudonymization result."""

    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--run-dir", type=Path, required=True)
    parser.add_argument("--pseudonymization", type=Path, required=True)
    parser.add_argument("--provider", required=True)
    parser.add_argument("--model", required=True)
    parser.add_argument("--session-id", required=True)
    parser.add_argument("--recorded-by", required=True)
    args = parser.parse_args(argv)
    try:
        path = record_history_pseudonymization(
            args.run_dir,
            args.pseudonymization,
            provider=args.provider,
            model=args.model,
            session_id=args.session_id,
            recorded_by=args.recorded_by,
        )
    except (OSError, ValueError) as exc:
        LOGGER.error("HISTORY_PSEUDONYMIZATION_RECORD_FAILED: %s", exc)
        return 1
    LOGGER.info("Recorded history pseudonymization: %s", path)
    return 0


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(message)s")
    raise SystemExit(main())

SHA-256: 248a5f5baad2b95639d0a336365a6337ae9bd4fe7b1181ca33d221b0bb7871cc