#!/usr/bin/env python3
"""Validate the downloadable DISCOVER Mission Control Hermes build kit.

This validator deliberately uses only the Python standard library.  It checks
the build kit as a supply-chain artifact: immutable source provenance, domain
catalog counts, private-mode/P0 defaults, release inventory, checksums, and ZIP
safety.  It does *not* treat build instructions as evidence that Hermes has
already implemented or tested the future full-stack application.
"""

from __future__ import annotations

import argparse
import csv
import hashlib
import json
import re
import stat
import sys
import unicodedata
import uuid
import zipfile
from collections.abc import Iterable
from pathlib import Path, PurePosixPath
from typing import Any


SCRIPT_PATH = Path(__file__).resolve()
PACKAGE_NAME = (
    "DISCOVER-Healthcare-Research-Innovation-Leader-Mission-Control-"
    "Hermes-Build-Kit-v1.0.0"
)
RUNNING_FROM_EXTRACTED_KIT = (
    SCRIPT_PATH.parent.name == "tools"
    and (SCRIPT_PATH.parents[1] / "README-FIRST.md").is_file()
)
WORKSPACE_ROOT = SCRIPT_PATH.parents[1] if not RUNNING_FROM_EXTRACTED_KIT else None
DEFAULT_PACKAGE = (
    SCRIPT_PATH.parents[1]
    if RUNNING_FROM_EXTRACTED_KIT
    else SCRIPT_PATH.parents[1] / "deliverables" / PACKAGE_NAME
)

BUILD_KIT_ID = "HRILAIOS-DISCOVER-FUNCTIONAL-BUILD-KIT-1.0.0"
BUILD_KIT_VERSION = "1.0.0"
TARGET_PRODUCT_VERSION = "2.0.0"

PROMPT_SHA256 = "ce884cfc6345cc93a6baec0852cf44feb44d859234ef1dc2b5ac2138df18ebe2"
BASELINE_ZIP_SHA256 = "69a4dd86659b41136ce9ceb2ceb52512ab567637ab0be29fdc6f6ab6573223c1"
DOMAIN_ZIP_SHA256 = "79b4a3c6a974061e52348ea9c6b7e5027afc95982802397b4fe203933d2655c6"
BASELINE_MANIFEST_SHA256 = "22fed55fd789c933f4e7eb50266f88f8dda47f9fba91ebbe9c2cbe4f0ab99702"
BASELINE_CHECKSUMS_SHA256 = "e178993d6185b126cc915f0d3026ddb3b07e7c5c7dd432aaf581152db8a5a40e"

ORIGINAL_PROMPT = Path("source/original-functional-build-master-prompt.md")
BASELINE_SOURCE = Path("source/baseline-application")
DOMAIN_SOURCE = Path("source/discover-domain-pack")
BASELINE_ARCHIVE = Path("source/archives/DISCOVER-Nurse-AI-OS-Mission-Control-v2.0.0.zip")
DOMAIN_ARCHIVE = Path("source/archives/DISCOVER-Healthcare-Research-Innovation-Leader-Pack.zip")
LEGACY_REFERENCE_FILES = (
    "README.md",
    "Healthcare-Research-and-Innovation-Leader-Complete-AI-OS-with-DISCOVER-SuperPowers-Hermes-Program.md",
    "Healthcare-Research-and-Innovation-Leader-Complete-AI-OS-with-DISCOVER-SuperPowers-Setup-Guide.md",
    "Healthcare-Research-and-Innovation-Leader-Complete-AI-OS-with-DISCOVER-SuperPowers-Setup-Guide.docx",
)

REQUIRED_FILES = (
    ".env.example",
    "README-FIRST.md",
    "GIVE-THIS-PACKAGE-TO-HERMES.md",
    "INPUT-PRECEDENCE.md",
    "SOURCE-NOTES.md",
    "BUILD-STATUS.md",
    "CHANGELOG.md",
    "LICENSE-NOTICE.md",
    "VERSION",
    "RELEASE-MANIFEST.json",
    "SHA256SUMS.txt",
    "SOURCE-INVENTORY.json",
    "implementation/DISCOVER-Functional-Build-Master-Prompt.md",
    "implementation/DISCOVER-Product-Specification.md",
    "implementation/DISCOVER-Architecture-and-Data-Model.md",
    "implementation/DISCOVER-Governance-EDENA-and-Data-Boundaries.md",
    "implementation/DISCOVER-Agent-Team-and-Routing.md",
    "implementation/DISCOVER-Personalization-Mapping-Crosswalk.md",
    "implementation/DISCOVER-Starter-Workspace-Crosswalk.md",
    "implementation/DISCOVER-Capability-and-Badge-Evidence-Model.md",
    "implementation/DISCOVER-Guide-Page-Content.md",
    "implementation/DISCOVER-Baseline-Gap-Report.md",
    "implementation/DISCOVER-Technical-Implementation-Guide.md",
    "implementation/DISCOVER-User-Installation-Guide.md",
    "implementation/DISCOVER-Security-and-Privacy-Checklist.md",
    "implementation/DISCOVER-Synthetic-Sample-Mission.md",
    "implementation/DISCOVER-Control-Completeness-Matrix.csv",
    "implementation/DISCOVER-Acceptance-and-Test-Ledger.md",
    "implementation/HERMES-FINAL-HANDOFF-REPORT-TEMPLATE.md",
    "personalization/README.md",
    "personalization/DISCOVER-Discover-Packet.synthetic.example.json",
    "personalization/DISCOVER-Soul-Profile.synthetic.example.json",
    "personalization/DISCOVER-Mission-Profile.synthetic.example.json",
    "personalization/input-schemas/discover-packet-input.schema.json",
    "personalization/input-schemas/soul-profile-input.schema.json",
    "schemas/DISCOVER-Mission-Profile.schema.json",
    "config/DISCOVER-Capability-Mastery-Criteria.v1.json",
    ORIGINAL_PROMPT.as_posix(),
    BASELINE_ARCHIVE.as_posix(),
    DOMAIN_ARCHIVE.as_posix(),
    "tools/verify-build-kit.py",
    *(f"source/legacy-reference/{name}" for name in LEGACY_REFERENCE_FILES),
)

PILLAR_FILES = (
    "01-D-Direction-Discovery-and-Dignity.md",
    "02-I-Inquiry-Integrity-and-Information-Stewardship.md",
    "03-S-Study-Design-Safety-and-Scientific-Rigor.md",
    "04-C-Collaboration-Community-and-Capability.md",
    "05-OV-Operations-Value-and-Translation.md",
    "06-ER-Evidence-Responsible-Innovation-and-Renewal.md",
)

MASTER_PLACEHOLDERS = (
    "[PACKAGE_NAME]",
    "[SOURCE_DIRECTORY_OR_REPOSITORY]",
    "[DISCOVER_PACKET_PATH_OR_SOURCE]",
    "[SOUL_QUIZ_RESULTS_PATH_OR_SOURCE]",
    "[HERMES_PROFILE_NAME_OR_AUTO_DETECT]",
    "[USER_TYPE]",
    "[ROLE_LANES]",
    "[DOMAIN]",
    "[AGENT_LIST]",
    "[DATA_CLASSIFICATION]",
    "[DOMAIN_GUARDRAILS]",
    "[MACOS_WINDOWS_LINUX]",
    "[ZIP_INSTALLER_DESKTOP_APP_LOCAL_WEB_APP]",
)

EXPECTED_COUNTS = {
    "pillars": 6,
    "powers": 24,
    "workflows": 24,
    "templates": 30,
    "agents": 10,
    "schemas": 18,
    "legacy_criteria": 160,
}


class Validation:
    def __init__(self) -> None:
        self.passed: list[str] = []
        self.failed: list[str] = []
        self.warnings: list[str] = []

    def check(self, condition: bool, label: str, detail: object | None = None) -> bool:
        if condition:
            self.passed.append(label)
            print(f"PASS  {label}")
            return True
        suffix = f" — {detail}" if detail not in (None, "") else ""
        failure = f"{label}{suffix}"
        self.failed.append(failure)
        print(f"FAIL  {failure}")
        return False

    def warn(self, label: str, detail: object | None = None) -> None:
        suffix = f" — {detail}" if detail not in (None, "") else ""
        warning = f"{label}{suffix}"
        self.warnings.append(warning)
        print(f"WARN  {warning}")

    def require_text(self, text: str, needles: Iterable[str], label: str) -> bool:
        missing = [needle for needle in needles if needle.casefold() not in text.casefold()]
        return self.check(not missing, label, f"missing={missing}")


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def sha256_bytes(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def safe_archive_name(name: str) -> bool:
    if not name or "\x00" in name or "\\" in name:
        return False
    candidate = name[:-1] if name.endswith("/") else name
    if not candidate or name.startswith("/") or "//" in name:
        return False
    raw_parts = candidate.split("/")
    if any(part in {"", ".", ".."} or ":" in part for part in raw_parts):
        return False
    pure = PurePosixPath(candidate)
    if pure.is_absolute() or tuple(raw_parts) != pure.parts:
        return False
    return True


def relative_files(root: Path) -> dict[str, Path]:
    if not root.is_dir():
        return {}
    return {
        path.relative_to(root).as_posix(): path
        for path in sorted(root.rglob("*"))
        if path.is_file() and not path.is_symlink()
    }


def read_text(path: Path) -> str:
    return path.read_text(encoding="utf-8")


def parse_json_file(v: Validation, path: Path, label: str | None = None) -> Any | None:
    try:
        value = json.loads(read_text(path))
    except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
        v.check(False, label or f"JSON parses: {path}", error)
        return None
    v.check(True, label or f"JSON parses: {path}")
    return value


def parse_checksums(text: str) -> tuple[dict[str, str], list[str]]:
    parsed: dict[str, str] = {}
    errors: list[str] = []
    normalized_paths: set[str] = set()
    for number, raw in enumerate(text.splitlines(), 1):
        if not raw.strip():
            continue
        expected, separator, relative = raw.partition("  ")
        if separator != "  " or not re.fullmatch(r"[0-9a-f]{64}", expected):
            errors.append(f"line {number}: malformed checksum entry")
            continue
        if not safe_archive_name(relative) or relative.endswith("/"):
            errors.append(f"line {number}: unsafe path {relative!r}")
            continue
        if relative in parsed:
            errors.append(f"line {number}: duplicate path {relative!r}")
            continue
        collision_key = unicodedata.normalize("NFC", relative).casefold()
        if collision_key in normalized_paths:
            errors.append(f"line {number}: case/Unicode-colliding path {relative!r}")
            continue
        normalized_paths.add(collision_key)
        parsed[relative] = expected
    return parsed, errors


def manifest_file_entries(manifest: Any) -> tuple[dict[str, tuple[str | None, int | None]], list[str]]:
    """Return path -> (sha256, bytes) from common manifest layouts."""

    errors: list[str] = []
    if not isinstance(manifest, dict):
        return {}, ["release manifest root is not an object"]
    raw_files = manifest.get("files")
    if raw_files is None:
        raw_files = manifest.get("files_excluding_manifest_and_checksums")
    if raw_files is None:
        return {}, ["release manifest has no files inventory"]

    normalized: list[tuple[str, Any]] = []
    if isinstance(raw_files, list):
        for index, item in enumerate(raw_files):
            if isinstance(item, str):
                normalized.append((item, {}))
            elif isinstance(item, dict):
                path = item.get("path", item.get("name"))
                if not isinstance(path, str):
                    errors.append(f"files[{index}] has no string path")
                    continue
                normalized.append((path, item))
            else:
                errors.append(f"files[{index}] is not an object or path string")
    elif isinstance(raw_files, dict):
        normalized.extend((str(path), value) for path, value in raw_files.items())
    else:
        return {}, ["release manifest files inventory is not a list or object"]

    result: dict[str, tuple[str | None, int | None]] = {}
    normalized_paths: set[str] = set()
    for path, metadata in normalized:
        if not safe_archive_name(path) or path.endswith("/"):
            errors.append(f"unsafe manifest path {path!r}")
            continue
        if path in result:
            errors.append(f"duplicate manifest path {path!r}")
            continue
        collision_key = unicodedata.normalize("NFC", path).casefold()
        if collision_key in normalized_paths:
            errors.append(f"case/Unicode-colliding manifest path {path!r}")
            continue
        normalized_paths.add(collision_key)
        digest: str | None = None
        size: int | None = None
        if isinstance(metadata, str):
            digest = metadata
        elif isinstance(metadata, dict):
            digest_value = metadata.get("sha256", metadata.get("hash"))
            size_value = metadata.get("bytes", metadata.get("size"))
            digest = digest_value if isinstance(digest_value, str) else None
            size = size_value if isinstance(size_value, int) and not isinstance(size_value, bool) else None
        if digest is not None and not re.fullmatch(r"[0-9a-f]{64}", digest):
            errors.append(f"invalid SHA-256 for {path!r}")
            digest = None
        result[path] = (digest, size)
    return result, errors


def validate_checksum_tree(v: Validation, tree: Path, checksum_path: Path, label: str) -> None:
    if not v.check(checksum_path.is_file(), f"{label}: checksum file exists"):
        return
    parsed, errors = parse_checksums(read_text(checksum_path))
    v.check(not errors, f"{label}: checksum syntax and paths are safe", errors)
    actual = relative_files(tree)
    checksum_rel = checksum_path.relative_to(tree).as_posix()
    expected_names = set(actual) - {checksum_rel}
    v.check(set(parsed) == expected_names, f"{label}: checksum inventory is exact", {
        "missing": sorted(expected_names - set(parsed)),
        "extra": sorted(set(parsed) - expected_names),
    })
    mismatches = [name for name in sorted(set(parsed) & set(actual)) if sha256(actual[name]) != parsed[name]]
    v.check(not mismatches, f"{label}: every listed checksum matches", mismatches)


def validate_manifest_tree(
    v: Validation,
    tree: Path,
    manifest_path: Path,
    checksum_path: Path,
    label: str,
) -> Any | None:
    if not v.check(manifest_path.is_file(), f"{label}: release manifest exists"):
        return None
    manifest = parse_json_file(v, manifest_path, f"{label}: release manifest JSON parses")
    if manifest is None:
        return None
    entries, errors = manifest_file_entries(manifest)
    v.check(not errors, f"{label}: manifest file inventory is well formed", errors)
    actual = relative_files(tree)
    excluded = {
        manifest_path.relative_to(tree).as_posix(),
        checksum_path.relative_to(tree).as_posix(),
    }
    expected_names = set(actual) - excluded
    v.check(set(entries) == expected_names, f"{label}: manifest file inventory is exact", {
        "missing": sorted(expected_names - set(entries)),
        "extra": sorted(set(entries) - expected_names),
    })
    missing_hashes = [name for name, (digest, _size) in entries.items() if digest is None]
    v.check(not missing_hashes, f"{label}: every manifest file has SHA-256", missing_hashes)
    hash_mismatches: list[str] = []
    size_mismatches: list[str] = []
    for name in sorted(set(entries) & set(actual)):
        digest, size = entries[name]
        if digest is not None and sha256(actual[name]) != digest:
            hash_mismatches.append(name)
        if size is not None and actual[name].stat().st_size != size:
            size_mismatches.append(name)
    v.check(not hash_mismatches, f"{label}: manifest hashes match", hash_mismatches)
    v.check(not size_mismatches, f"{label}: manifest byte counts match", size_mismatches)
    return manifest


def zip_regular_tree(
    archive: Path,
    root_prefix: str,
    *,
    subtree_prefix: str = "",
) -> tuple[dict[str, tuple[bytes, int]], list[str]]:
    """Read regular members below a known ZIP root/subtree without extracting."""

    result: dict[str, tuple[bytes, int]] = {}
    errors: list[str] = []
    seen: set[str] = set()
    normalized_seen: set[str] = set()
    prefix = root_prefix.rstrip("/") + "/"
    subtree = subtree_prefix.strip("/")
    if subtree:
        prefix += subtree + "/"
    try:
        with zipfile.ZipFile(archive) as bundle:
            for info in bundle.infolist():
                name = info.filename
                if not safe_archive_name(name):
                    errors.append(f"unsafe ZIP member {name!r}")
                    continue
                if name in seen:
                    errors.append(f"duplicate ZIP member {name!r}")
                    continue
                seen.add(name)
                collision_key = unicodedata.normalize("NFC", name).casefold()
                if collision_key in normalized_seen:
                    errors.append(f"case/Unicode-colliding ZIP member {name!r}")
                normalized_seen.add(collision_key)
                mode = (info.external_attr >> 16) & 0xFFFF
                if stat.S_ISLNK(mode):
                    errors.append(f"symlink ZIP member {name!r}")
                    continue
                if info.is_dir() or not name.startswith(prefix):
                    continue
                relative = name[len(prefix):]
                if not relative or not safe_archive_name(relative):
                    errors.append(f"unsafe relative ZIP member {name!r}")
                    continue
                if relative in result:
                    errors.append(f"duplicate subtree member {relative!r}")
                    continue
                result[relative] = (bundle.read(info), stat.S_IMODE(mode))
    except (OSError, zipfile.BadZipFile, RuntimeError) as error:
        errors.append(f"cannot read ZIP: {error}")
    return result, errors


def compare_directory_to_archive(
    v: Validation,
    directory: Path,
    archive: Path,
    archive_root: str,
    label: str,
    *,
    archive_subtree: str = "",
) -> None:
    disk = relative_files(directory)
    archived, errors = zip_regular_tree(
        archive,
        archive_root,
        subtree_prefix=archive_subtree,
    )
    v.check(not errors, f"{label}: reference ZIP members are safe and unique", errors)
    v.check(set(disk) == set(archived), f"{label}: unpacked inventory equals reference ZIP", {
        "missing": sorted(set(archived) - set(disk)),
        "extra": sorted(set(disk) - set(archived)),
    })
    mismatches = [
        name
        for name in sorted(set(disk) & set(archived))
        if sha256(disk[name]) != sha256_bytes(archived[name][0])
    ]
    v.check(not mismatches, f"{label}: unpacked bytes equal reference ZIP", mismatches)


def validate_required_files(v: Validation, package: Path) -> None:
    missing: list[str] = []
    empty: list[str] = []
    for relative in REQUIRED_FILES:
        path = package / relative
        if not path.is_file():
            missing.append(relative)
        elif path.stat().st_size == 0:
            empty.append(relative)
    v.check(not missing, "All required build-kit documents and contracts exist", missing)
    v.check(not empty, "Required build-kit documents are nonempty", empty)

    version_path = package / "VERSION"
    if version_path.is_file():
        try:
            version = read_text(version_path).strip()
        except UnicodeDecodeError as error:
            v.check(False, "Build-kit VERSION is UTF-8", error)
        else:
            v.check(version == BUILD_KIT_VERSION, "Build-kit VERSION is exactly 1.0.0", version)


def validate_json(v: Validation, package: Path) -> None:
    json_files = sorted(package.rglob("*.json")) if package.is_dir() else []
    v.check(bool(json_files), "Build kit contains JSON contracts and examples")
    failures: list[str] = []
    for path in json_files:
        try:
            json.loads(read_text(path))
        except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
            failures.append(f"{path.relative_to(package).as_posix()}: {error}")
    v.check(not failures, f"All {len(json_files)} JSON files parse with the standard library", failures)


def validate_target_contracts(v: Validation, package: Path) -> None:
    """Check high-value target fixtures and ledgers without third-party dependencies."""

    schema_path = package / "schemas" / "DISCOVER-Mission-Profile.schema.json"
    profile_path = package / "personalization" / "DISCOVER-Mission-Profile.synthetic.example.json"
    try:
        schema = json.loads(read_text(schema_path))
        profile = json.loads(read_text(profile_path))
    except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
        v.check(False, "Mission Profile schema and synthetic fixture are readable JSON", error)
    else:
        required = set(schema.get("required", []))
        properties = set(schema.get("properties", {}))
        profile_keys = set(profile)
        v.check(not (required - profile_keys), "Synthetic Mission Profile contains every schema-required top-level field", sorted(required - profile_keys))
        v.check(not (profile_keys - properties), "Synthetic Mission Profile uses only schema-declared top-level fields", sorted(profile_keys - properties))
        definitions = schema.get("$defs") if isinstance(schema.get("$defs"), dict) else {}
        workspace_types = definitions.get("workspaceType", {}).get("enum", []) if isinstance(definitions.get("workspaceType"), dict) else []
        capability_ids = definitions.get("capabilityId", {}).get("enum", []) if isinstance(definitions.get("capabilityId"), dict) else []
        v.check(
            len(workspace_types) == 12 and len(set(workspace_types)) == 12
            and len(capability_ids) == 17 and len(set(capability_ids)) == 17
            and "private_whole_life" in workspace_types
            and "privacy-stewardship" in capability_ids,
            "Mission Profile schema freezes 12 workspace types and 17 capability recommendation IDs",
        )
        v.check(profile.get("schema") == "NAIO-DISCOVER-MISSION-PROFILE-1", "Synthetic Mission Profile has the exact contract ID")
        v.check(profile.get("profile_version") == "1.0-draft", "Synthetic Mission Profile has the exact draft version")
        try:
            uuid.UUID(str(profile.get("profile_id")))
            valid_uuid = True
        except (ValueError, TypeError, AttributeError):
            valid_uuid = False
        v.check(valid_uuid, "Synthetic Mission Profile uses a valid UUID")
        v.check(
            profile.get("status") == "proposed" and profile.get("approved_at") is None and profile.get("demo") is True,
            "Synthetic Mission Profile is proposed, unapproved, and unmistakably demo content",
        )

        workspaces = profile.get("role_workspaces")
        workspace_required = {
            "workspace_type", "label", "relationship", "priority", "authorization_status",
            "current_priorities", "goals", "situation_types", "recommended_assets",
        }
        workspace_errors: list[str] = []
        if not isinstance(workspaces, list) or not workspaces:
            workspace_errors.append("role_workspaces is not a nonempty list")
            workspaces = []
        for index, workspace in enumerate(workspaces):
            if not isinstance(workspace, dict):
                workspace_errors.append(f"workspace[{index}] is not an object")
                continue
            missing = workspace_required - set(workspace)
            if missing:
                workspace_errors.append(f"workspace[{index}] missing {sorted(missing)}")
        primary_count = sum(isinstance(item, dict) and item.get("relationship") == "primary" for item in workspaces)
        if primary_count != 1:
            workspace_errors.append(f"primary workspace count={primary_count}")
        v.check(not workspace_errors, "Synthetic Mission Profile has complete workspaces and exactly one Primary", workspace_errors)

        recommendation_errors: list[str] = []
        recommendation_sets: list[tuple[str, Any]] = [("profile", profile.get("recommended_assets"))]
        recommendation_sets.extend((f"workspace[{index}]", item.get("recommended_assets")) for index, item in enumerate(workspaces) if isinstance(item, dict))
        for label, assets in recommendation_sets:
            if not isinstance(assets, dict):
                recommendation_errors.append(f"{label}: recommended_assets missing")
                continue
            if assets.get("activation_state") != "recommendations_only_inactive":
                recommendation_errors.append(f"{label}: recommendations are not inactive")
            if assets.get("tool_ids") != []:
                recommendation_errors.append(f"{label}: tool_ids must be empty before capability discovery")
            for workflow in assets.get("discover_workflow_ids", []):
                if not isinstance(workflow, str) or re.fullmatch(r"WF-(?:0[1-9]|1[0-9]|2[0-4])", workflow) is None:
                    recommendation_errors.append(f"{label}: invalid target workflow {workflow!r}")
            for source_id in assets.get("source_recommendation_ids", []):
                if isinstance(source_id, str) and source_id.startswith("DSC-WF-"):
                    continue
            if any(isinstance(value, str) and value.startswith("DSC-WF-") for value in assets.get("discover_workflow_ids", [])):
                recommendation_errors.append(f"{label}: legacy DSC workflow placed in target workflow IDs")
        v.check(not recommendation_errors, "Mission Profile keeps valid DISCOVER target recommendations separate from legacy DSC provenance", recommendation_errors)

        provenance = profile.get("field_provenance")
        provenance_errors: list[str] = []
        provenance_required = {
            "field", "source_type", "source_ref", "source_field", "mapping_rule_id", "derivation",
            "uncertainty", "approval_status", "approved_at", "updated_at",
        }
        if not isinstance(provenance, list):
            provenance_errors.append("field_provenance is not an array")
            provenance = []
        for index, record in enumerate(provenance):
            if not isinstance(record, dict):
                provenance_errors.append(f"provenance[{index}] is not an object")
                continue
            missing = provenance_required - set(record)
            if missing:
                provenance_errors.append(f"provenance[{index}] missing {sorted(missing)}")
        covered = {record.get("field") for record in provenance if isinstance(record, dict)}
        approval_groups = {
            f"/{field}" for field in (
                "display_name", "mission_statement", "core_values", "current_priorities",
                "strengths_and_growth", "situation_context", "goals", "working_preferences",
                "role_workspaces", "authority_context", "ai_preferences", "memory_preferences",
                "governance_preferences", "recommended_assets", "presentation_preferences",
                "accessibility_preferences", "uncertainties",
            )
        }
        if approval_groups - covered:
            provenance_errors.append(f"uncovered approval groups {sorted(approval_groups - covered)}")
        v.check(not provenance_errors, "Synthetic Mission Profile has complete field-level approval-group provenance", provenance_errors)

    criteria_path = package / "config" / "DISCOVER-Capability-Mastery-Criteria.v1.json"
    try:
        criteria_model = json.loads(read_text(criteria_path))
    except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
        v.check(False, "Capability criteria model is readable JSON", error)
    else:
        domains = criteria_model.get("domains")
        criteria_rows: list[dict[str, Any]] = []
        level_errors: list[str] = []
        if not isinstance(domains, list):
            domains = []
            level_errors.append("domains is not an array")
        for domain in domains:
            if not isinstance(domain, dict):
                level_errors.append("domain is not an object")
                continue
            levels = domain.get("levels")
            if not isinstance(levels, dict) or set(levels) != {"basic", "intermediate", "advanced"}:
                level_errors.append(f"{domain.get('id')}: level set is invalid")
                continue
            for level, record in levels.items():
                rows = record.get("criteria") if isinstance(record, dict) else None
                if not isinstance(rows, list) or len(rows) != 3:
                    level_errors.append(f"{domain.get('id')}/{level}: criteria count is not 3")
                    continue
                criteria_rows.extend(item for item in rows if isinstance(item, dict))
        capstone = criteria_model.get("globalCapstone")
        capstone_rows = capstone.get("capstoneCriteria") if isinstance(capstone, dict) else None
        ids = [row.get("id") for row in criteria_rows if isinstance(row.get("id"), str)]
        capstone_ids = [row.get("id") for row in capstone_rows or [] if isinstance(row, dict) and isinstance(row.get("id"), str)]
        v.check(
            len(domains) == 17 and len(criteria_rows) == 153 and len(capstone_rows or []) == 6
            and len(set(ids + capstone_ids)) == 159 and not level_errors,
            "Capability model has 17 domains, 153 level criteria, and 6 unique global capstone criteria",
            {"domains": len(domains), "level_criteria": len(criteria_rows), "capstone": len(capstone_rows or []), "errors": level_errors},
        )
        criteria_text = json.dumps(criteria_model, sort_keys=True).casefold()
        v.check(
            criteria_model.get("modelId") == "discover-capability-mastery-criteria"
            and criteria_model.get("productId") == "discover-healthcare-research-innovation-leader-mission-control"
            and isinstance(criteria_model.get("scope"), dict)
            and criteria_model["scope"].get("roleLane") == "healthcare_research_innovation_leadership"
            and "data_privacy_stewardship" in {domain.get("id") for domain in domains if isinstance(domain, dict)}
            and not any(term in criteria_text for term in ("discovership", "steward mission control", "hospital_or_clinic_administration", "chart state", "align receipt")),
            "Capability model is pinned to DISCOVER and contains no mechanical-renaming or administrator-control remnants",
        )

    matrix_path = package / "implementation" / "DISCOVER-Control-Completeness-Matrix.csv"
    matrix_control_ids: set[str] = set()
    try:
        with matrix_path.open("r", encoding="utf-8", newline="") as stream:
            matrix_rows = list(csv.DictReader(stream))
    except (OSError, UnicodeDecodeError, csv.Error) as error:
        v.check(False, "Control-completeness matrix is readable CSV", error)
    else:
        expected_columns = {
            "control_id", "screen", "control", "intended_behavior", "implementation_target",
            "persisted_data", "required_permission", "offline_behavior", "error_behavior",
            "verification_test", "status",
        }
        columns = set(matrix_rows[0]) if matrix_rows else set()
        ids = [row.get("control_id", "") for row in matrix_rows]
        matrix_control_ids = {identifier for identifier in ids if identifier}
        empty_cells = [f"row {index + 2}/{key}" for index, row in enumerate(matrix_rows) for key in expected_columns if not row.get(key, "").strip()]
        v.check(
            len(matrix_rows) == 171 and columns == expected_columns and len(set(ids)) == 171
            and not empty_cells and {row.get("status") for row in matrix_rows} == {"required_not_implemented"},
            "Control matrix contains 171 unique complete seed controls at required_not_implemented",
            {"rows": len(matrix_rows), "columns": sorted(columns), "empty": empty_cells[:20]},
        )

    ledger_path = package / "implementation" / "DISCOVER-Acceptance-and-Test-Ledger.md"
    try:
        ledger_text = read_text(ledger_path)
    except (OSError, UnicodeDecodeError) as error:
        v.check(False, "Acceptance ledger is readable UTF-8", error)
    else:
        control_test_ids = re.findall(r"^\| (CTL-[A-Z0-9-]+) \|", ledger_text, re.M)
        integration_ids = re.findall(r"^\| (INT-[0-9]{3}) \|", ledger_text, re.M)
        ledger_ids = control_test_ids + integration_ids
        v.check(
            len(control_test_ids) == 171
            and len(integration_ids) == 40
            and len(ledger_ids) == 211
            and len(set(ledger_ids)) == 211
            and "all applicable 160 corpus criteria" in ledger_text
            and "Core operational; AI setup pending" in ledger_text,
            "Acceptance ledger has 171 control tests, 40 cross-cutting scenarios, and separately requires all 160 corpus criteria",
            {"control_tests": len(control_test_ids), "integrations": len(integration_ids), "unique": len(set(ledger_ids))},
        )
        v.check(
            {identifier.removeprefix("CTL-") for identifier in control_test_ids} == matrix_control_ids,
            "Every control-matrix row has exactly one control-derived acceptance test",
            {
                "missing_tests": sorted(matrix_control_ids - {identifier.removeprefix("CTL-") for identifier in control_test_ids}),
                "extra_tests": sorted({identifier.removeprefix("CTL-") for identifier in control_test_ids} - matrix_control_ids),
            },
        )


def validate_resolved_prompt(v: Validation, package: Path) -> None:
    resolved = package / "implementation" / "DISCOVER-Functional-Build-Master-Prompt.md"
    if resolved.is_file():
        text = read_text(resolved)
        unresolved = [token for token in MASTER_PLACEHOLDERS if token in text]
        v.check(not unresolved, "Resolved DISCOVER master prompt has no original placeholders", unresolved)
        v.require_text(
            text,
            (
                "DISCOVER",
                "Healthcare Research & Innovation Leader",
                "discover-healthcare-research-innovation-leader-mission-control",
                "healthcare_research_innovation_leadership",
                "Private Research & Innovation Leader OS",
                "FRAME",
                "TRACE",
                "PROBE",
                "ORBIT",
                "PERM-P0 Disabled",
            ),
            "Resolved master prompt names the DISCOVER product, lane, private mode, and P0 default",
        )

    unresolved_elsewhere: list[str] = []
    controlled_suffixes = {".md", ".json", ".txt", ".csv", ".yaml", ".yml"}
    for path in sorted(package.rglob("*")) if package.is_dir() else []:
        if not path.is_file() or path.suffix.lower() not in controlled_suffixes:
            continue
        relative = path.relative_to(package)
        if relative.parts and relative.parts[0] == "source":
            continue
        try:
            text = read_text(path)
        except UnicodeDecodeError:
            continue
        for match in re.finditer(r"\[[A-Z][A-Z0-9_]{2,}\]", text):
            unresolved_elsewhere.append(f"{relative.as_posix()}:{match.group(0)}")
    v.check(not unresolved_elsewhere, "Target-controlled documents have no unresolved master-style placeholders", unresolved_elsewhere)


def optional_workspace_roots(package: Path) -> list[Path]:
    """Find plausible build workspaces without making them a release dependency."""

    candidates: list[Path] = []
    if WORKSPACE_ROOT is not None:
        candidates.append(WORKSPACE_ROOT)
    if package.parent.name == "deliverables":
        candidates.append(package.parent.parent)
    current = Path.cwd().resolve()
    candidates.extend((current, *current.parents))
    unique: list[Path] = []
    seen: set[Path] = set()
    for candidate in candidates:
        try:
            resolved = candidate.resolve()
        except OSError:
            continue
        if resolved not in seen:
            seen.add(resolved)
            unique.append(resolved)
    return unique


def find_optional_workspace_file(package: Path, relatives: Iterable[str]) -> Path | None:
    for root in optional_workspace_roots(package):
        for relative in relatives:
            candidate = root / relative
            if candidate.is_file():
                return candidate
    return None


def validate_input_sources(v: Validation, package: Path, manifest: Any | None) -> None:
    packaged_prompt = package / ORIGINAL_PROMPT
    v.check(packaged_prompt.is_file(), "Exact original functional-build prompt is bundled")
    if packaged_prompt.is_file():
        v.check(sha256(packaged_prompt) == PROMPT_SHA256, "Bundled original prompt SHA-256 is exact", sha256(packaged_prompt))
    external_prompt = find_optional_workspace_file(
        package,
        ("upload/01-Mission-Control-Functional-Build-Master-Prompt.md",),
    )
    if external_prompt is not None:
        v.check(sha256(external_prompt) == PROMPT_SHA256, "Optional workspace prompt cross-check matches the expected SHA-256", sha256(external_prompt))
        if packaged_prompt.is_file():
            v.check(
                packaged_prompt.read_bytes() == external_prompt.read_bytes(),
                "Bundled original prompt is byte-identical to the optional workspace attachment",
            )
    else:
        v.warn("Optional workspace prompt cross-check unavailable; bundled hash remains authoritative")

    baseline = package / BASELINE_SOURCE
    baseline_archive = package / BASELINE_ARCHIVE
    v.check(baseline.is_dir(), "Complete DISCOVER baseline is bundled as an unpacked immutable source")
    v.check(baseline_archive.is_file(), "Original DISCOVER baseline ZIP is bundled for self-contained provenance")
    if baseline_archive.is_file():
        v.check(
            sha256(baseline_archive) == BASELINE_ZIP_SHA256,
            "Bundled DISCOVER baseline ZIP SHA-256 is exact",
            sha256(baseline_archive),
        )
    if baseline.is_dir() and baseline_archive.is_file():
        compare_directory_to_archive(
            v,
            baseline,
            baseline_archive,
            "DISCOVER-Nurse-AI-OS-Mission-Control-v2.0.0",
            "DISCOVER baseline",
        )
        baseline_manifest = baseline / "RELEASE-MANIFEST.json"
        baseline_checksums = baseline / "SHA256SUMS.txt"
        if baseline_manifest.is_file():
            v.check(
                sha256(baseline_manifest) == BASELINE_MANIFEST_SHA256,
                "Bundled baseline release-manifest hash is unchanged",
                sha256(baseline_manifest),
            )
        if baseline_checksums.is_file():
            v.check(
                sha256(baseline_checksums) == BASELINE_CHECKSUMS_SHA256,
                "Bundled baseline checksum-file hash is unchanged",
                sha256(baseline_checksums),
            )
        validate_checksum_tree(v, baseline, baseline_checksums, "DISCOVER baseline")
        validate_manifest_tree(v, baseline, baseline_manifest, baseline_checksums, "DISCOVER baseline")

    optional_baseline = find_optional_workspace_file(
        package,
        ("deliverables/DISCOVER-Nurse-AI-OS-Mission-Control-v2.0.0.zip",),
    )
    if optional_baseline is not None:
        v.check(
            sha256(optional_baseline) == BASELINE_ZIP_SHA256,
            "Optional workspace DISCOVER ZIP cross-check matches the expected SHA-256",
            sha256(optional_baseline),
        )
        if baseline_archive.is_file():
            v.check(
                baseline_archive.read_bytes() == optional_baseline.read_bytes(),
                "Bundled DISCOVER ZIP is byte-identical to the optional workspace source",
            )
    else:
        v.warn("Optional workspace DISCOVER ZIP cross-check unavailable; bundled archive remains authoritative")

    domain = package / DOMAIN_SOURCE
    domain_archive = package / DOMAIN_ARCHIVE
    v.check(domain.is_dir(), "Canonical DISCOVER domain corpus is bundled as an unpacked immutable source")
    v.check(domain_archive.is_file(), "Original DISCOVER domain ZIP is bundled for self-contained provenance")
    if domain_archive.is_file():
        v.check(
            sha256(domain_archive) == DOMAIN_ZIP_SHA256,
            "Bundled DISCOVER domain ZIP SHA-256 is exact",
            sha256(domain_archive),
        )
        if domain.is_dir():
            compare_directory_to_archive(
                v,
                domain,
                domain_archive,
                "Healthcare-Research-and-Innovation-Leader-Complete-AI-OS-with-DISCOVER-SuperPowers-Package-v1.0",
                "DISCOVER domain corpus",
                archive_subtree="Healthcare-Research-and-Innovation-Leader-DISCOVER-SuperPowers-Pack-v1.0",
            )
        archived_domain, archive_errors = zip_regular_tree(
            domain_archive,
            "Healthcare-Research-and-Innovation-Leader-Complete-AI-OS-with-DISCOVER-SuperPowers-Package-v1.0",
        )
        v.check(not archive_errors, "DISCOVER legacy-reference source ZIP is safe and unique", archive_errors)
        legacy = package / "source" / "legacy-reference"
        legacy_mismatches: list[str] = []
        for name in LEGACY_REFERENCE_FILES:
            disk_path = legacy / name
            archived = archived_domain.get(name)
            if not disk_path.is_file() or archived is None:
                legacy_mismatches.append(f"{name}: missing")
            elif sha256(disk_path) != sha256_bytes(archived[0]):
                legacy_mismatches.append(f"{name}: bytes differ")
        v.check(
            not legacy_mismatches,
            "Legacy research-and-innovation reference documents are byte-identical to the canonical ZIP",
            legacy_mismatches,
        )

    optional_domain = find_optional_workspace_file(
        package,
        (
            "deliverables/DISCOVER-Healthcare-Research-Innovation-Leader-Pack.zip",
            "deliverables/Healthcare-Research-and-Innovation-Leader-Complete-AI-OS-with-DISCOVER-SuperPowers-Package-v1.0.zip",
        ),
    )
    if optional_domain is not None:
        v.check(
            sha256(optional_domain) == DOMAIN_ZIP_SHA256,
            "Optional workspace DISCOVER ZIP cross-check matches the expected SHA-256",
            sha256(optional_domain),
        )
        if domain_archive.is_file():
            v.check(
                domain_archive.read_bytes() == optional_domain.read_bytes(),
                "Bundled DISCOVER ZIP is byte-identical to the optional workspace source",
            )
    else:
        v.warn("Optional workspace DISCOVER ZIP cross-check unavailable; bundled archive remains authoritative")

    if isinstance(manifest, dict):
        source_record = manifest.get("sources")
        source_text = json.dumps(source_record, sort_keys=True) if source_record is not None else ""
        missing_hashes = [
            expected
            for expected in (PROMPT_SHA256, BASELINE_ZIP_SHA256, DOMAIN_ZIP_SHA256)
            if expected not in source_text
        ]
        v.check(not missing_hashes, "Release manifest records all three source-archive hashes", missing_hashes)


def domain_counts(v: Validation, package: Path) -> dict[str, int]:
    domain = package / DOMAIN_SOURCE
    counts = {key: 0 for key in EXPECTED_COUNTS}
    if not domain.is_dir():
        v.check(False, "DISCOVER domain counts can be inspected", "domain source directory is missing")
        return counts

    pillar_dir = domain / "discover"
    pillar_names = sorted(path.name for path in pillar_dir.glob("*.md")) if pillar_dir.is_dir() else []
    counts["pillars"] = len(pillar_names)
    v.check(pillar_names == sorted(PILLAR_FILES), "Exactly the six canonical DISCOVER pillar files are present", pillar_names)

    pillar_text = "\n".join(read_text(pillar_dir / name) for name in PILLAR_FILES if (pillar_dir / name).is_file())
    powers = [int(value) for value in re.findall(r"^## PWR-([0-9]{2}) — .+$", pillar_text, re.M)]
    counts["powers"] = len(powers)
    v.check(powers == list(range(1, 25)), "Exactly 24 ordered DISCOVER SuperPowers are defined", powers)
    install_states = re.findall(r"^\*\*Install state:\*\* Available Inactive\s*$", pillar_text, re.M)
    v.check(len(install_states) == 24, "All 24 SuperPowers default to Available Inactive", len(install_states))

    workflow_path = domain / "workflows" / "01-DISCOVER-Runnable-Workflows.md"
    workflow_text = read_text(workflow_path) if workflow_path.is_file() else ""
    workflows = [int(value) for value in re.findall(r"^## WF-([0-9]{2}) — .+$", workflow_text, re.M)]
    counts["workflows"] = len(workflows)
    v.check(workflows == list(range(1, 25)), "Exactly 24 ordered DISCOVER workflows are defined", workflows)

    template_path = domain / "templates" / "01-DISCOVER-Functional-Templates.md"
    template_text = read_text(template_path) if template_path.is_file() else ""
    templates = [int(value) for value in re.findall(r"^## TPL-([0-9]{2}) — .+$", template_text, re.M)]
    counts["templates"] = len(templates)
    v.check(templates == list(range(1, 31)), "Exactly 30 ordered DISCOVER templates are defined", templates)

    schema_agent_path = domain / "workflows" / "03-DISCOVER-Schemas-and-Agents.md"
    schema_agent_text = read_text(schema_agent_path) if schema_agent_path.is_file() else ""
    agent_rows = re.findall(r"^\| `AGT-([0-9]{2})` \|.*$", schema_agent_text, re.M)
    counts["agents"] = len(agent_rows)
    v.check(agent_rows == [f"{number:02d}" for number in range(1, 11)], "Exactly 10 ordered canonical agents are defined", agent_rows)
    full_agent_rows = re.findall(r"^\| `AGT-[0-9]{2}` \|.*$", schema_agent_text, re.M)
    v.check(
        len(full_agent_rows) == 10 and all("PERM-P0 Disabled" in row for row in full_agent_rows),
        "All 10 canonical agents default to PERM-P0 Disabled",
    )

    schema_ids = re.findall(r"^\| SCH-([0-9]{2}) \| `([a-z][a-z0-9_]+)` \| `([a-f0-9]{64})` \|", schema_agent_text, re.M)
    counts["schemas"] = len(schema_ids)
    v.check(
        len(schema_ids) == 18
        and [row[0] for row in schema_ids] == [f"{number:02d}" for number in range(1, 19)]
        and len({row[1] for row in schema_ids}) == 18
        and len({row[2] for row in schema_ids}) == 18,
        "Exactly 18 unique ordered canonical domain schemas and hashes are defined",
        schema_ids,
    )

    assurance_path = domain / "tests" / "01-DISCOVER-Release-and-Runtime-Tests.md"
    assurance_text = read_text(assurance_path) if assurance_path.is_file() else ""
    criterion_rows = re.findall(r"^### `(RA-(?:[A-R][0-9]{2}|INT[0-9]{2}))-VAR@1\.0\.0`$", assurance_text, re.M)
    expected_criteria = [
        f"RA-{letter}{number:02d}"
        for letter in "ABCDEFGHIJKLMNOPQR"
        for number in range(1, 9)
    ] + [f"RA-INT{number:02d}" for number in range(1, 17)]
    counts["legacy_criteria"] = len(criterion_rows)
    v.check(
        len(criterion_rows) == 160 and set(criterion_rows) == set(expected_criteria),
        "Exactly the 160 canonical legacy release-assurance criteria are present",
        {"found": len(criterion_rows), "unique": len(set(criterion_rows))},
    )
    return counts


def normalized_key(value: str) -> str:
    return re.sub(r"[^a-z0-9]", "", value.casefold())


def manifest_count(counts: Any, aliases: Iterable[str]) -> int | None:
    if not isinstance(counts, dict):
        return None
    normalized = {normalized_key(str(key)): value for key, value in counts.items()}
    for alias in aliases:
        value = normalized.get(normalized_key(alias))
        if isinstance(value, int) and not isinstance(value, bool):
            return value
    return None


def validate_outer_manifest(
    v: Validation,
    package: Path,
    observed_counts: dict[str, int] | None = None,
) -> Any | None:
    manifest_path = package / "RELEASE-MANIFEST.json"
    checksums_path = package / "SHA256SUMS.txt"
    manifest = validate_manifest_tree(v, package, manifest_path, checksums_path, "Build kit")
    validate_checksum_tree(v, package, checksums_path, "Build kit")
    if not isinstance(manifest, dict):
        return manifest

    package_record = manifest.get("package", manifest.get("build_kit"))
    if isinstance(package_record, dict):
        package_id = package_record.get("id", package_record.get("package_id"))
        version = package_record.get("version")
        v.check(package_id == BUILD_KIT_ID, "Manifest package ID is exact", package_id)
        v.check(version == BUILD_KIT_VERSION, "Manifest build-kit version is exact", version)
    elif isinstance(package_record, str):
        package_folded = package_record.casefold()
        v.check("discover" in package_folded and BUILD_KIT_VERSION in package_record, "Manifest string package identity names DISCOVER v1.0.0", package_record)
    else:
        v.check(False, "Manifest package identity is an object or string", type(package_record).__name__)

    counts = manifest.get("counts")
    if counts is None and isinstance(package_record, dict):
        counts = package_record.get("counts")
    aliases = {
        "pillars": ("pillars", "discover_pillars"),
        "powers": ("powers", "superpowers", "discover_superpowers"),
        "workflows": ("workflows",),
        "templates": ("templates",),
        "agents": ("agents",),
        "schemas": ("schemas", "domain_schemas", "research_innovation_domain_schemas"),
        "legacy_criteria": (
            "legacy_criteria",
            "legacy_acceptance_criteria",
            "release_assurance_criteria",
            "legacy_discover_release_criteria",
        ),
    }
    manifest_values = {key: manifest_count(counts, names) for key, names in aliases.items()}
    expected = observed_counts or EXPECTED_COUNTS
    v.check(
        all(manifest_values[key] == expected[key] == EXPECTED_COUNTS[key] for key in EXPECTED_COUNTS),
        "Manifest records the exact observed DISCOVER catalog counts",
        manifest_values,
    )

    controls = manifest.get("controls", manifest.get("defaults"))
    controls_text = json.dumps(controls, sort_keys=True).casefold() if controls is not None else ""
    normalized_controls = controls_text.replace("_", " ").replace("-", " ")
    missing_controls = [
        phrase
        for phrase in ("private research innovation leader os", "perm p0 disabled", "available inactive", "preview only")
        if phrase not in normalized_controls
    ]
    v.check(not missing_controls, "Manifest records Private mode, P0 agents, and inactive powers", missing_controls)

    target_text = json.dumps(manifest, sort_keys=True)
    v.check(TARGET_PRODUCT_VERSION in target_text, "Manifest distinguishes target product version 2.0.0")
    return manifest


def validate_private_boundaries(v: Validation, package: Path) -> None:
    readme = package / "README-FIRST.md"
    governance = package / "implementation" / "DISCOVER-Governance-EDENA-and-Data-Boundaries.md"
    agent_team = package / "implementation" / "DISCOVER-Agent-Team-and-Routing.md"
    hermes = package / "GIVE-THIS-PACKAGE-TO-HERMES.md"
    paths = (readme, governance, agent_team, hermes)
    if not all(path.is_file() for path in paths):
        v.check(False, "Private-mode boundary documents are available", [str(path) for path in paths if not path.is_file()])
        return

    readme_text = read_text(readme)
    v.require_text(
        readme_text,
        (
            "Private Research & Innovation Leader OS",
            "`DATA-D0` public/synthetic and `DATA-D1` owner-authored nonsensitive planning information only after screening",
            "participant-level or patient-level data",
            "PHI",
            "biospecimens or genomic data",
            "confidential study, safety, integrity, personnel, sponsor, partner, vendor, intellectual-property, grant, budget, contract or security material",
            "All domain-action connectors, schedules, shares and external actions are absent from this target",
            "All 24 SuperPowers begin `Available Inactive`",
            "All 24 workflows begin `Preview Only`",
            "all ten agents begin `PERM-P0 Disabled`",
        ),
        "README states the complete Private Research & Innovation Leader OS starting boundary",
    )

    governance_text = read_text(governance)
    v.require_text(
        governance_text,
        (
            "Safe installation state",
            "powers `Available Inactive`; workflows `Preview Only`; agents `PERM-P0 Disabled`; connectors/actions Off",
            "DATA-DX-PROHIBITED",
            "reject before persistence",
            "patient/participant rows",
            "biospecimen, genomic, imaging, device, sensor, safety-event",
            "sponsor-confidential, vendor-confidential, contract-confidential",
            "credentials, secrets, tokens",
            "cross-partition request must block before lookup or dereference with zero field disclosure",
            "All ten agents install `PERM-P0 Disabled`",
            "FRAME",
            "TRACE",
            "PROBE",
            "ORBIT",
            "Safe Reset",
        ),
        "Governance contract preserves fail-closed research data, partition, routine and action boundaries",
    )

    agent_text = read_text(agent_team)
    agent_rows = re.findall(r"^\| `AGT-([0-9]{2})` \|.*$", agent_text, re.M)
    full_rows = re.findall(r"^\| `AGT-[0-9]{2}` \|.*$", agent_text, re.M)
    v.check(
        agent_rows == [f"{number:02d}" for number in range(1, 11)]
        and len(full_rows) == 10
        and all("`PERM-P0 Disabled`" in row for row in full_rows),
        "Implementation agent catalog has exactly 10 agents, all P0 Disabled",
        agent_rows,
    )

    hermes_text = read_text(hermes)
    v.require_text(
        hermes_text,
        (
            "Install exactly 24 SuperPowers as `Available Inactive`",
            "24 workflows",
            "30 templates",
            "ten agents as `PERM-P0 Disabled`",
            "Run the original DISCOVER 160-criterion suite",
            "Never substitute canned model text, fake citations, mock tool events",
        ),
        "Hermes program preserves exact catalog installation and truthful runtime defaults",
    )

    status_path = package / "BUILD-STATUS.md"
    if status_path.is_file():
        status_text = read_text(status_path)
        v.require_text(
            status_text,
            (
                "Not operational",
                "has not yet been built and tested",
                "does not prove",
            ),
            "Build status does not pre-claim an implemented full-stack DISCOVER runtime",
        )


def expected_disk_mode(path: Path) -> int:
    if path.is_dir():
        return 0o755
    suffix = path.suffix.casefold()
    executable_suffix = suffix in {".sh", ".command"}
    packaged_verifier = path.name == "verify-build-kit.py" and path.parent.name == "tools"
    return 0o755 if executable_suffix or packaged_verifier else 0o644


def validate_disk_modes(v: Validation, package: Path) -> None:
    symlinks: list[str] = []
    bad_modes: list[str] = []
    if package.exists():
        paths = [package, *sorted(package.rglob("*"))]
    else:
        paths = []
    for path in paths:
        relative = "." if path == package else path.relative_to(package).as_posix()
        if path.is_symlink():
            symlinks.append(relative)
            continue
        if not (path.is_file() or path.is_dir()):
            bad_modes.append(f"{relative}: unsupported filesystem object")
            continue
        mode = stat.S_IMODE(path.stat().st_mode)
        expected = expected_disk_mode(path)
        extractor_dropped_only_execute_bits = (
            RUNNING_FROM_EXTRACTED_KIT
            and expected == 0o755
            and mode == 0o644
            and path.is_file()
        )
        if mode != expected and not extractor_dropped_only_execute_bits:
            bad_modes.append(f"{relative}: {mode:04o}, expected {expected:04o}")
    v.check(not symlinks, "Package directory contains no symlinks", symlinks)
    v.check(not bad_modes, "Package filesystem modes are normalized (0644 files, 0755 dirs/launchers)", bad_modes)


def validate_outer_zip(v: Validation, package: Path, zip_path: Path) -> None:
    if not v.check(zip_path.is_file() and zip_path.stat().st_size > 0, "Final downloadable ZIP exists and is nonempty", zip_path):
        return
    try:
        bundle = zipfile.ZipFile(zip_path)
    except (OSError, zipfile.BadZipFile) as error:
        v.check(False, "Final ZIP opens", error)
        return

    with bundle:
        infos = bundle.infolist()
        names = [info.filename for info in infos]
        v.check(bool(infos), "Final ZIP contains members")
        unsafe = [name for name in names if not safe_archive_name(name)]
        v.check(not unsafe, "ZIP has no absolute, traversal, drive, backslash, or NUL paths", unsafe)
        duplicates = sorted({name for name in names if names.count(name) > 1})
        v.check(not duplicates, "ZIP has no duplicate member names", duplicates)
        collision_map: dict[str, list[str]] = {}
        for name in names:
            key = unicodedata.normalize("NFC", name).casefold()
            collision_map.setdefault(key, []).append(name)
        collisions = [items for items in collision_map.values() if len(items) > 1]
        v.check(not collisions, "ZIP has no case-insensitive or Unicode-normalization collisions", collisions)

        roots = {PurePosixPath(name).parts[0] for name in names if safe_archive_name(name)}
        v.check(roots == {PACKAGE_NAME}, "ZIP has exactly one expected root directory", sorted(roots))

        symlinks: list[str] = []
        unsafe_modes: list[str] = []
        zip_files: dict[str, zipfile.ZipInfo] = {}
        for info in infos:
            mode_with_type = (info.external_attr >> 16) & 0xFFFF
            mode = stat.S_IMODE(mode_with_type)
            if stat.S_ISLNK(mode_with_type):
                symlinks.append(info.filename)
                continue
            if info.is_dir():
                if mode != 0o755:
                    unsafe_modes.append(f"{info.filename}: {mode:04o}, expected 0755")
                continue
            if not info.filename.startswith(f"{PACKAGE_NAME}/"):
                continue
            relative = info.filename[len(PACKAGE_NAME) + 1:]
            if not relative:
                continue
            zip_files[relative] = info
            disk_path = package / relative
            if mode == 0:
                unsafe_modes.append(f"{info.filename}: missing POSIX mode")
            elif mode & (stat.S_ISUID | stat.S_ISGID | stat.S_ISVTX | stat.S_IWGRP | stat.S_IWOTH):
                unsafe_modes.append(f"{info.filename}: unsafe mode {mode:04o}")
            elif disk_path.is_file() and mode != expected_disk_mode(disk_path):
                unsafe_modes.append(
                    f"{info.filename}: ZIP {mode:04o} != expected {expected_disk_mode(disk_path):04o}"
                )
        v.check(not symlinks, "ZIP contains no symlink entries", symlinks)
        v.check(not unsafe_modes, "ZIP preserves normalized safe POSIX modes", unsafe_modes)

        disk_files = relative_files(package)
        v.check(set(zip_files) == set(disk_files), "ZIP file inventory exactly matches the release directory", {
            "missing": sorted(set(disk_files) - set(zip_files)),
            "extra": sorted(set(zip_files) - set(disk_files)),
        })
        mismatches: list[str] = []
        for name in sorted(set(zip_files) & set(disk_files)):
            try:
                archived_hash = sha256_bytes(bundle.read(zip_files[name]))
            except (RuntimeError, zipfile.BadZipFile) as error:
                mismatches.append(f"{name}: {error}")
                continue
            if archived_hash != sha256(disk_files[name]):
                mismatches.append(name)
        v.check(not mismatches, "Every ZIP member is byte-identical to the release directory", mismatches)
        bad_crc = bundle.testzip()
        v.check(bad_crc is None, "ZIP CRC verification passes", bad_crc)


def validate_package_name(v: Validation, package: Path, pre_assembly: bool) -> None:
    if pre_assembly:
        v.check(bool(package.name), "Pre-assembly package directory has a usable name", package.name)
        if package.name != PACKAGE_NAME:
            v.warn("Pre-assembly directory will require the exact versioned name at release", package.name)
    else:
        v.check(package.name == PACKAGE_NAME, "Release directory has the exact versioned package name", package.name)
    v.check(package.is_dir(), "Versioned release directory exists", package)


def print_summary(v: Validation) -> int:
    total = len(v.passed) + len(v.failed)
    print()
    print("DISCOVER HERMES BUILD-KIT VALIDATION SUMMARY")
    print(f"Passed:   {len(v.passed)}")
    print(f"Failed:   {len(v.failed)}")
    print(f"Warnings: {len(v.warnings)}")
    print(f"Checks:   {total}")
    if v.failed:
        print("Result:   FAIL")
        print("Failures:")
        for failure in v.failed:
            print(f"  - {failure}")
        return 1
    print("Result:   PASS")
    return 0


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--package", type=Path, default=DEFAULT_PACKAGE, help="unpacked release directory")
    parser.add_argument(
        "--zip",
        dest="zip_path",
        type=Path,
        default=None,
        help="optional final release ZIP; provide explicitly to validate archive root, modes, and bytes",
    )
    parser.add_argument(
        "--pre-assembly",
        action="store_true",
        help="allow a staging-directory name while enforcing all content and integrity checks",
    )
    args = parser.parse_args(argv)

    package = args.package.resolve()
    zip_path = args.zip_path.resolve() if args.zip_path is not None else None
    pre_assembly = args.pre_assembly or package.name == "work_discover_buildkit"
    v = Validation()

    validate_package_name(v, package, pre_assembly)
    if not package.is_dir():
        return print_summary(v)

    validate_required_files(v, package)
    validate_json(v, package)
    validate_target_contracts(v, package)
    validate_resolved_prompt(v, package)

    observed_counts = domain_counts(v, package)
    manifest = validate_outer_manifest(v, package, observed_counts)
    validate_input_sources(v, package, manifest)
    validate_private_boundaries(v, package)
    validate_disk_modes(v, package)
    if zip_path is not None:
        validate_outer_zip(v, package, zip_path)
    else:
        v.warn("Final outer ZIP validation skipped; pass --zip to enable archive verification")
    return print_summary(v)


if __name__ == "__main__":
    sys.exit(main())
