"""Owner/customer-only Telegram bootstrap authority for DualCoach v1."""

from __future__ import annotations

import fcntl
import hashlib
import hmac
import json
import os
import re
import secrets
import stat
import tempfile
from contextlib import contextmanager
from dataclasses import dataclass, replace
from datetime import datetime, timedelta, timezone
from enum import StrEnum
from pathlib import Path
from typing import Callable, Final, Iterator, Mapping, cast
from urllib.parse import quote

ROOM_BOOTSTRAP_RELATIVE_PATH: Final = Path(
    "data/onboarding/telegram-customer-bootstrap-v1"
)
_INVITE_TTL: Final = timedelta(hours=24)
_TOKEN_RE: Final = re.compile(r"^rc1_(?P<sid>[A-Za-z0-9_-]{22})$")
_SID_RE: Final = re.compile(r"^[A-Za-z0-9_-]{22}$")
_SESSION_RE: Final = re.compile(r"^cb_[A-Za-z0-9_-]{22}$")
_BOT_RE: Final = re.compile(r"^[A-Za-z0-9_]{5,64}$")
_DIGEST_RE: Final = re.compile(r"^[a-f0-9]{64}$")
_ID_RE: Final = re.compile(r"^-?[0-9]+$")
_LEDGER_SCHEMA: Final = "telegram-customer-bootstrap-v1"
_TERMINAL_STATES: Final = frozenset({"ACTIVE", "CANCELLED", "EXPIRED", "FAILED"})

def room_bootstrap_state_dir(profile_root: Path | str) -> Path:
    return Path(profile_root) / ROOM_BOOTSTRAP_RELATIVE_PATH

class BootstrapError(ValueError):
    pass

class BootstrapConflict(BootstrapError):
    pass

class GenerationConflict(BootstrapError):
    pass

class BootstrapState(StrEnum):
    PREPARED = "PREPARED"
    REGISTERING = "REGISTERING"
    AWAITING_CONSENT = "AWAITING_CONSENT"
    AWAITING_ACTIVATION = "AWAITING_ACTIVATION"
    ACTIVE = "ACTIVE"
    CANCELLED = "CANCELLED"
    EXPIRED = "EXPIRED"
    FAILED = "FAILED"

class Role(StrEnum):
    CUSTOMER = "customer"

class RecoverySlot(StrEnum):
    CONSENT_CARD = "consent_card"

@dataclass(frozen=True, slots=True)
class RoleClaim:
    role: Role
    user_id: str
    chat_id: str
    topic_id: str
    message_id: str

@dataclass(frozen=True, slots=True)
class RecoveryAttempt:
    session_id: str
    slot: RecoverySlot
    chat_id: str
    generation: int
    created_at: datetime

@dataclass(frozen=True, slots=True)
class CustomerDraft:
    customer_key: str
    display_name: str
    starts_on: str
    daily_time: str
    weekly_weekday: int
    monthly_day: int
    calories_kcal: int
    protein_g: int
    meals: tuple[str, ...]
    primary_goal: str = "미정"
    dietary_restrictions: tuple[str, ...] = ()
    allergies: tuple[str, ...] = ()
    food_preferences: tuple[str, ...] = ()
    supplements: tuple[str, ...] = ()
    digestion_context: str | None = None
    sleep_goal_hours: float | None = None
    recovery_goal: str | None = None
    training_context: str | None = None
    carbohydrate_g: int | None = None
    fat_g: int | None = None
    water_liters: float | None = None
    customer_user_id: str | None = None

    @property
    def canonical_json(self) -> str:
        return _canonical_json(self.to_dict())

    @property
    def digest(self) -> str:
        return _sha256(self.canonical_json.encode("utf-8"))

    def to_dict(self) -> dict[str, object]:
        return {
            "customer_key": self.customer_key,
            "display_name": self.display_name,
            "starts_on": self.starts_on,
            "daily_time": self.daily_time,
            "weekly_weekday": self.weekly_weekday,
            "monthly_day": self.monthly_day,
            "calories_kcal": self.calories_kcal,
            "protein_g": self.protein_g,
            "meals": list(self.meals),
            "primary_goal": self.primary_goal,
            "dietary_restrictions": list(self.dietary_restrictions),
            "allergies": list(self.allergies),
            "food_preferences": list(self.food_preferences),
            "supplements": list(self.supplements),
            "digestion_context": self.digestion_context,
            "sleep_goal_hours": self.sleep_goal_hours,
            "recovery_goal": self.recovery_goal,
            "training_context": self.training_context,
            "carbohydrate_g": self.carbohydrate_g,
            "fat_g": self.fat_g,
            "water_liters": self.water_liters,
            "customer_user_id": self.customer_user_id,
        }

@dataclass(frozen=True, slots=True)
class RoomBootstrapSession:
    session_id: str
    sid_hash: str
    customer_draft: CustomerDraft
    customer_draft_digest: str
    bot_username: str
    state: BootstrapState
    generation: int
    created_at: datetime
    updated_at: datetime
    expires_at: datetime
    owner_id: str
    role_claims: tuple[RoleClaim, ...] = ()
    consent_publication_attempt: int = 0
    consent_card_message_id: str | None = None
    recovery_attempt_generation: int = 0
    recovery_attempts: tuple[RecoveryAttempt, ...] = ()
    failure_code: str | None = None

    @property
    def customer_key(self) -> str:
        return self.customer_draft.customer_key

    @property
    def terminal(self) -> bool:
        return self.state.value in _TERMINAL_STATES

    @property
    def chat_id(self) -> None:
        """Private-DM bootstrap has no shared room authority."""
        return None

    def role_claim(self, role: Role) -> RoleClaim | None:
        return next((claim for claim in self.role_claims if claim.role is role), None)

    def recovery_attempt(self, slot: RecoverySlot) -> RecoveryAttempt | None:
        return next(
            (attempt for attempt in self.recovery_attempts if attempt.slot is slot),
            None,
        )

@dataclass(frozen=True, slots=True)
class PreparedRehearsalCustomerInvite:
    session: RoomBootstrapSession
    customer_link: str
    expires_at: datetime

_DRAFT_REQUIRED: Final = frozenset(
    {
        "customer_key",
        "display_name",
        "starts_on",
        "daily_time",
        "weekly_weekday",
        "monthly_day",
        "calories_kcal",
        "protein_g",
        "meals",
    }
)
_DRAFT_OPTIONAL: Final = frozenset(CustomerDraft.__dataclass_fields__) - _DRAFT_REQUIRED

def load_customer_draft(path: Path | str) -> CustomerDraft:
    source = Path(path)
    _require_private_regular_file(source, "customer draft")
    try:
        value = json.loads(source.read_text(encoding="utf-8"))
    except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise BootstrapError("customer draft JSON is invalid") from exc
    if not isinstance(value, dict) or any(not isinstance(key, str) for key in value):
        raise BootstrapError("customer draft must be an object")
    keys = set(value)
    if keys - _DRAFT_REQUIRED - _DRAFT_OPTIONAL:
        raise BootstrapError("customer draft has unknown fields")
    if _DRAFT_REQUIRED - keys:
        raise BootstrapError("customer draft is missing fields")
    return _draft_from_mapping(value)

class RoomBootstrapStore:
    """Private, digest-authenticated generation-CAS bootstrap ledger."""

    def __init__(
        self,
        state_dir: Path | str,
        *,
        now: Callable[[], datetime] | None = None,
    ) -> None:
        self.state_dir = Path(state_dir)
        self.ledger_path = self.state_dir / "ledger.json"
        self.lock_path = self.state_dir / "ledger.lock"
        self._now = now or (lambda: datetime.now(timezone.utc))
        self._prepare_paths()
        with self._locked():
            if self.ledger_path.exists():
                self._read_unlocked()
            else:
                self._write_unlocked(())

    def prepare_rehearsal_customer_invite(
        self,
        draft: CustomerDraft,
        *,
        bot_username: str,
        owner_id: str,
    ) -> PreparedRehearsalCustomerInvite:
        username = bot_username.removeprefix("@").strip()
        if _BOT_RE.fullmatch(username) is None:
            raise BootstrapError("invalid Telegram bot username")
        owner = _telegram_id(owner_id, "owner")
        if draft.customer_user_id is not None:
            raise BootstrapError("customer identity must be claimed through the DM link")
        sid = secrets.token_urlsafe(16)
        if _SID_RE.fullmatch(sid) is None:
            raise RuntimeError("generated customer bootstrap token is invalid")
        now = self._utc_now()
        session = RoomBootstrapSession(
            session_id="cb_" + secrets.token_urlsafe(16),
            sid_hash=_sha256(sid.encode("ascii")),
            customer_draft=draft,
            customer_draft_digest=draft.digest,
            bot_username=username,
            state=BootstrapState.PREPARED,
            generation=1,
            created_at=now,
            updated_at=now,
            expires_at=now + _INVITE_TTL,
            owner_id=owner,
        )
        with self._locked():
            sessions = list(self._expire_values(self._read_unlocked(), now))
            if any(
                not item.terminal and item.customer_key == draft.customer_key
                for item in sessions
            ):
                raise BootstrapConflict(
                    "customer already has a nonterminal bootstrap session"
                )
            sessions.append(session)
            self._write_unlocked(sessions)
        return PreparedRehearsalCustomerInvite(
            session=session,
            customer_link=(
                f"https://t.me/{username}?start=rc1_{quote(sid, safe='-_')}"
            ),
            expires_at=session.expires_at,
        )

    def rehearsal_customer_invite_session(self, token: str) -> RoomBootstrapSession:
        match = _TOKEN_RE.fullmatch(token) if isinstance(token, str) else None
        if match is None:
            raise BootstrapError("customer bootstrap invite is invalid")
        self.expire_unbound()
        session = self.get_by_sid(match.group("sid"))
        if (
            session is None
            or session.state is not BootstrapState.PREPARED
            or session.role_claims
            or session.customer_draft.customer_user_id is not None
        ):
            raise BootstrapError("customer bootstrap invite is unavailable")
        return session

    def claim_rehearsal_customer_invite(
        self,
        token: str,
        *,
        user_id: str,
        chat_id: str,
        message_id: str,
    ) -> RoomBootstrapSession:
        user = _telegram_id(user_id, "customer")
        chat = _telegram_id(chat_id, "customer chat")
        message = _telegram_id(message_id, "customer message")
        if user != chat:
            raise BootstrapError("customer bootstrap invite requires a private DM")
        session = self.rehearsal_customer_invite_session(token)

        def mutate(current: RoomBootstrapSession, now: datetime) -> RoomBootstrapSession:
            if (
                current.state is not BootstrapState.PREPARED
                or current.role_claims
                or current.customer_draft.customer_user_id is not None
                or user == current.owner_id
            ):
                raise BootstrapError("customer bootstrap invite is unavailable")
            draft = replace(current.customer_draft, customer_user_id=user)
            return replace(
                current,
                customer_draft=draft,
                customer_draft_digest=draft.digest,
                state=BootstrapState.REGISTERING,
                generation=current.generation + 1,
                updated_at=now,
                role_claims=(RoleClaim(Role.CUSTOMER, user, chat, "0", message),),
            )

        return self._mutate(session.session_id, session.generation, mutate)

    def list_sessions(self) -> tuple[RoomBootstrapSession, ...]:
        with self._locked():
            return self._read_unlocked()

    def get(self, session_id: str) -> RoomBootstrapSession:
        with self._locked():
            return self._find(self._read_unlocked(), session_id)

    def get_by_sid(self, sid: str) -> RoomBootstrapSession | None:
        if _SID_RE.fullmatch(sid) is None:
            return None
        digest = _sha256(sid.encode("ascii"))
        with self._locked():
            matches = [
                item
                for item in self._read_unlocked()
                if hmac.compare_digest(item.sid_hash, digest)
            ]
        if len(matches) > 1:
            raise BootstrapConflict("customer bootstrap token is ambiguous")
        return matches[0] if matches else None

    def get_by_sid_hash(self, sid_hash: str) -> RoomBootstrapSession | None:
        if _DIGEST_RE.fullmatch(sid_hash) is None:
            return None
        with self._locked():
            matches = [
                item
                for item in self._read_unlocked()
                if hmac.compare_digest(item.sid_hash, sid_hash)
            ]
        if len(matches) > 1:
            raise BootstrapConflict("customer bootstrap hash is ambiguous")
        return matches[0] if matches else None

    def get_by_chat(self, chat_id: str) -> RoomBootstrapSession | None:
        chat = _telegram_id(chat_id, "chat")
        with self._locked():
            matches = [
                item
                for item in self._read_unlocked()
                if not item.terminal
                and any(claim.chat_id == chat for claim in item.role_claims)
            ]
        if len(matches) > 1:
            raise BootstrapConflict("chat has multiple bootstrap sessions")
        return matches[0] if matches else None

    def transition(
        self,
        session_id: str,
        *,
        expected_generation: int,
        target: BootstrapState,
    ) -> RoomBootstrapSession:
        allowed = {
            BootstrapState.REGISTERING: frozenset({BootstrapState.AWAITING_CONSENT}),
            BootstrapState.AWAITING_CONSENT: frozenset(
                {BootstrapState.AWAITING_ACTIVATION}
            ),
            BootstrapState.AWAITING_ACTIVATION: frozenset({BootstrapState.ACTIVE}),
        }

        def mutate(session: RoomBootstrapSession, now: datetime) -> RoomBootstrapSession:
            if target not in allowed.get(session.state, frozenset()):
                raise BootstrapError("customer bootstrap transition is invalid")
            return replace(
                session,
                state=target,
                generation=session.generation + 1,
                updated_at=now,
            )

        return self._mutate(session_id, expected_generation, mutate)

    def activate_bound_customer(
        self,
        session_id: str,
        *,
        expected_generation: int,
        customer_key: str,
        customer_user_id: str,
        owner_id: str,
    ) -> RoomBootstrapSession:
        """Commit the identity-bound activation lifecycle exactly once."""
        if type(expected_generation) is not int or expected_generation < 1:
            raise GenerationConflict("customer bootstrap generation is invalid")
        customer = _telegram_id(customer_user_id, "customer")
        owner = _telegram_id(owner_id, "owner")
        now = self._utc_now()
        with self._locked():
            sessions = list(self._read_unlocked())
            current = self._find(tuple(sessions), session_id)
            claim = current.role_claim(Role.CUSTOMER)
            identity_matches = (
                current.customer_key == customer_key
                and current.customer_draft.customer_user_id == customer
                and current.owner_id == owner
                and claim is not None
                and len(current.role_claims) == 1
                and claim.user_id == customer
                and claim.chat_id == customer
                and claim.topic_id == "0"
            )
            if not identity_matches:
                raise BootstrapConflict(
                    "customer bootstrap activation identity is unavailable"
                )
            active_matches = tuple(
                item
                for item in sessions
                if item.customer_key == customer_key
                and item.state is BootstrapState.ACTIVE
            )
            if current.state is BootstrapState.ACTIVE:
                if (
                    expected_generation not in {current.generation - 1, current.generation}
                    or active_matches != (current,)
                ):
                    raise GenerationConflict(
                        "customer bootstrap activation authority is stale"
                    )
                return current
            if (
                current.state is not BootstrapState.AWAITING_ACTIVATION
                or current.generation != expected_generation
                or active_matches
            ):
                raise GenerationConflict(
                    "customer bootstrap activation authority is stale"
                )
            updated = replace(
                current,
                state=BootstrapState.ACTIVE,
                generation=current.generation + 1,
                updated_at=now,
            )
            sessions[sessions.index(current)] = updated
            self._write_unlocked(sessions)
            return updated

    def bind_private_owner(
        self,
        session_id: str,
        *,
        owner_id: str,
        expected_generation: int,
    ) -> RoomBootstrapSession:
        owner = _telegram_id(owner_id, "owner")

        def mutate(session: RoomBootstrapSession, now: datetime) -> RoomBootstrapSession:
            if (
                session.state is not BootstrapState.AWAITING_CONSENT
                or session.owner_id != owner
            ):
                raise BootstrapError("canonical owner binding is unavailable")
            return session

        return self._mutate(session_id, expected_generation, mutate)

    def reserve_consent_publication(
        self,
        session_id: str,
        *,
        expected_generation: int,
    ) -> RoomBootstrapSession:
        def mutate(session: RoomBootstrapSession, now: datetime) -> RoomBootstrapSession:
            if session.state is not BootstrapState.AWAITING_CONSENT:
                raise BootstrapError("consent publication requires awaiting consent")
            if session.recovery_attempts:
                raise BootstrapConflict("consent publication outcome is unresolved")
            return replace(
                session,
                consent_publication_attempt=session.consent_publication_attempt + 1,
                consent_card_message_id=None,
                generation=session.generation + 1,
                updated_at=now,
            )

        return self._mutate(session_id, expected_generation, mutate)

    def reserve_recovery_attempt(
        self,
        session_id: str,
        *,
        slot: RecoverySlot,
        chat_id: str,
        expected_generation: int,
    ) -> RecoveryAttempt:
        if slot is not RecoverySlot.CONSENT_CARD:
            raise BootstrapError("unsupported customer bootstrap recovery slot")
        chat = _telegram_id(chat_id, "chat")
        captured: list[RecoveryAttempt] = []

        def mutate(session: RoomBootstrapSession, now: datetime) -> RoomBootstrapSession:
            customer = session.role_claim(Role.CUSTOMER)
            if (
                session.state is not BootstrapState.AWAITING_CONSENT
                or session.recovery_attempts
                or customer is None
                or customer.chat_id != chat
                or session.consent_card_message_id is not None
            ):
                raise BootstrapConflict("consent provider authority is unavailable")
            attempt = RecoveryAttempt(
                session.session_id,
                slot,
                chat,
                session.recovery_attempt_generation + 1,
                now,
            )
            captured.append(attempt)
            return replace(
                session,
                recovery_attempt_generation=attempt.generation,
                recovery_attempts=(attempt,),
                failure_code=None,
                updated_at=now,
            )

        self._mutate(session_id, expected_generation, mutate)
        return captured[0]

    def bind_recovery_receipt(
        self,
        session_id: str,
        *,
        slot: RecoverySlot,
        chat_id: str,
        attempt_generation: int,
        receipt_id: str,
    ) -> RoomBootstrapSession:
        chat = _telegram_id(chat_id, "chat")
        receipt = _telegram_id(receipt_id, "receipt")

        def mutate(session: RoomBootstrapSession, now: datetime) -> RoomBootstrapSession:
            self._require_attempt(session, slot, chat, attempt_generation)
            if session.consent_card_message_id is not None:
                raise BootstrapConflict("consent receipt is already bound")
            return replace(
                session,
                consent_card_message_id=receipt,
                recovery_attempts=(),
                failure_code=None,
                updated_at=now,
            )

        current = self.get(session_id)
        return self._mutate(session_id, current.generation, mutate)

    def acknowledge_recovery_no_side_effect(
        self,
        session_id: str,
        *,
        slot: RecoverySlot,
        chat_id: str,
        attempt_generation: int,
    ) -> RoomBootstrapSession:
        chat = _telegram_id(chat_id, "chat")

        def mutate(session: RoomBootstrapSession, now: datetime) -> RoomBootstrapSession:
            self._require_attempt(session, slot, chat, attempt_generation)
            return replace(session, recovery_attempts=(), updated_at=now)

        current = self.get(session_id)
        return self._mutate(session_id, current.generation, mutate)

    def reconcile_committed_consent(
        self,
        session_id: str,
        *,
        expected_generation: int,
        publication_attempt: int,
        consent_card_message_id: str,
    ) -> RoomBootstrapSession:
        receipt = _telegram_id(consent_card_message_id, "consent receipt")

        def mutate(session: RoomBootstrapSession, now: datetime) -> RoomBootstrapSession:
            if (
                session.state is not BootstrapState.AWAITING_CONSENT
                or session.recovery_attempts
                or publication_attempt != session.consent_publication_attempt
                or receipt != session.consent_card_message_id
            ):
                raise GenerationConflict("committed consent authority is stale")
            return replace(
                session,
                state=BootstrapState.AWAITING_ACTIVATION,
                consent_card_message_id=None,
                generation=session.generation + 1,
                updated_at=now,
            )

        return self._mutate(session_id, expected_generation, mutate)

    def expire_unbound(self) -> tuple[RoomBootstrapSession, ...]:
        now = self._utc_now()
        with self._locked():
            before = self._read_unlocked()
            after = self._expire_values(before, now)
            if after != before:
                self._write_unlocked(after)
            return after

    def unresolved_attempts(
        self,
        session_id: str | None = None,
    ) -> tuple[RecoveryAttempt, ...]:
        sessions = self.list_sessions()
        if session_id is not None:
            sessions = (self._find(sessions, session_id),)
        return tuple(
            attempt for session in sessions for attempt in session.recovery_attempts
        )

    @staticmethod
    def _require_attempt(
        session: RoomBootstrapSession,
        slot: RecoverySlot,
        chat_id: str,
        generation: int,
    ) -> RecoveryAttempt:
        matches = tuple(
            attempt
            for attempt in session.recovery_attempts
            if attempt.slot is slot
            and attempt.chat_id == chat_id
            and attempt.generation == generation
        )
        if len(matches) != 1:
            raise GenerationConflict("consent provider authority is stale")
        return matches[0]

    def _mutate(
        self,
        session_id: str,
        expected_generation: int,
        mutation: Callable[[RoomBootstrapSession, datetime], RoomBootstrapSession],
    ) -> RoomBootstrapSession:
        if type(expected_generation) is not int or expected_generation < 1:
            raise GenerationConflict("customer bootstrap generation is invalid")
        now = self._utc_now()
        with self._locked():
            sessions = list(self._read_unlocked())
            current = self._find(tuple(sessions), session_id)
            if current.generation != expected_generation:
                raise GenerationConflict("customer bootstrap generation is stale")
            updated = mutation(current, now)
            if updated.session_id != current.session_id:
                raise BootstrapError("customer bootstrap identity changed")
            index = sessions.index(current)
            sessions[index] = updated
            self._write_unlocked(sessions)
            return updated

    @staticmethod
    def _find(
        sessions: tuple[RoomBootstrapSession, ...],
        session_id: str,
    ) -> RoomBootstrapSession:
        matches = tuple(item for item in sessions if item.session_id == session_id)
        if len(matches) != 1:
            raise BootstrapError("customer bootstrap session is unavailable")
        return matches[0]

    @staticmethod
    def _expire_values(
        sessions: tuple[RoomBootstrapSession, ...],
        now: datetime,
    ) -> tuple[RoomBootstrapSession, ...]:
        return tuple(
            replace(
                session,
                state=BootstrapState.EXPIRED,
                generation=session.generation + 1,
                updated_at=now,
            )
            if session.state is BootstrapState.PREPARED and session.expires_at <= now
            else session
            for session in sessions
        )

    def _prepare_paths(self) -> None:
        if self.state_dir.is_symlink():
            raise BootstrapError("customer bootstrap state directory is a symlink")
        self.state_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
        self.state_dir.chmod(0o700)
        for path in (self.ledger_path, self.lock_path):
            if path.is_symlink():
                raise BootstrapError("customer bootstrap authority path is a symlink")
        descriptor = os.open(
            self.lock_path,
            os.O_CREAT | os.O_RDWR | os.O_CLOEXEC | getattr(os, "O_NOFOLLOW", 0),
            0o600,
        )
        os.fchmod(descriptor, 0o600)
        os.close(descriptor)

    @contextmanager
    def _locked(self) -> Iterator[None]:
        descriptor = os.open(
            self.lock_path,
            os.O_RDWR | os.O_CLOEXEC | getattr(os, "O_NOFOLLOW", 0),
        )
        try:
            fcntl.flock(descriptor, fcntl.LOCK_EX)
            yield
        finally:
            fcntl.flock(descriptor, fcntl.LOCK_UN)
            os.close(descriptor)

    def _read_unlocked(self) -> tuple[RoomBootstrapSession, ...]:
        _require_private_regular_file(self.ledger_path, "customer bootstrap ledger")
        try:
            value = json.loads(self.ledger_path.read_text(encoding="utf-8"))
        except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
            raise BootstrapError("customer bootstrap ledger is invalid") from exc
        if not isinstance(value, dict) or set(value) != {"schema", "sessions", "digest"}:
            raise BootstrapError("customer bootstrap ledger shape is invalid")
        payload = {"schema": value["schema"], "sessions": value["sessions"]}
        if (
            value["schema"] != _LEDGER_SCHEMA
            or not isinstance(value["sessions"], list)
            or value["digest"] != _sha256(_canonical_bytes(payload))
        ):
            raise BootstrapError("customer bootstrap ledger digest is invalid")
        sessions = tuple(_session_from_mapping(item) for item in value["sessions"])
        if len({item.session_id for item in sessions}) != len(sessions):
            raise BootstrapError("customer bootstrap session identity is duplicated")
        return sessions

    def _write_unlocked(
        self,
        sessions: tuple[RoomBootstrapSession, ...] | list[RoomBootstrapSession],
    ) -> None:
        payload: dict[str, object] = {
            "schema": _LEDGER_SCHEMA,
            "sessions": [_session_to_dict(session) for session in sessions],
        }
        document = {**payload, "digest": _sha256(_canonical_bytes(payload))}
        descriptor, temporary_name = tempfile.mkstemp(
            prefix=".ledger.", dir=self.state_dir
        )
        try:
            os.fchmod(descriptor, 0o600)
            with os.fdopen(descriptor, "wb", closefd=True) as stream:
                stream.write(_canonical_bytes(document))
                stream.flush()
                os.fsync(stream.fileno())
            os.replace(temporary_name, self.ledger_path)
            self.ledger_path.chmod(0o600)
            directory = os.open(self.state_dir, os.O_RDONLY | os.O_DIRECTORY)
            try:
                os.fsync(directory)
            finally:
                os.close(directory)
        finally:
            try:
                os.unlink(temporary_name)
            except FileNotFoundError:
                pass

    def _utc_now(self) -> datetime:
        value = self._now()
        if not isinstance(value, datetime) or value.tzinfo is None:
            raise BootstrapError("customer bootstrap clock must be timezone-aware")
        return value.astimezone(timezone.utc)

class RoomBootstrapTransport:
    """Container for the owner/customer-only durable bootstrap store."""

    def __init__(
        self,
        store: RoomBootstrapStore,
        *,
        owner_id: str,
        customer_topic_name: str = "고객 체크인",
    ) -> None:
        if not isinstance(store, RoomBootstrapStore):
            raise TypeError("customer bootstrap transport requires its sealed store")
        self.store = store
        self.owner_id = _telegram_id(owner_id, "owner")
        self.customer_topic_name = str(customer_topic_name)

def _session_to_dict(session: RoomBootstrapSession) -> dict[str, object]:
    return {
        "session_id": session.session_id,
        "sid_hash": session.sid_hash,
        "customer_draft": session.customer_draft.to_dict(),
        "customer_draft_digest": session.customer_draft_digest,
        "bot_username": session.bot_username,
        "state": session.state.value,
        "generation": session.generation,
        "created_at": session.created_at.isoformat(),
        "updated_at": session.updated_at.isoformat(),
        "expires_at": session.expires_at.isoformat(),
        "owner_id": session.owner_id,
        "role_claims": [
            {
                "role": claim.role.value,
                "user_id": claim.user_id,
                "chat_id": claim.chat_id,
                "topic_id": claim.topic_id,
                "message_id": claim.message_id,
            }
            for claim in session.role_claims
        ],
        "consent_publication_attempt": session.consent_publication_attempt,
        "consent_card_message_id": session.consent_card_message_id,
        "recovery_attempt_generation": session.recovery_attempt_generation,
        "recovery_attempts": [
            {
                "session_id": attempt.session_id,
                "slot": attempt.slot.value,
                "chat_id": attempt.chat_id,
                "generation": attempt.generation,
                "created_at": attempt.created_at.isoformat(),
            }
            for attempt in session.recovery_attempts
        ],
        "failure_code": session.failure_code,
    }

def _strict_string(value: object, label: str) -> str:
    if not isinstance(value, str):
        raise BootstrapError(f"customer bootstrap {label} is invalid")
    return value

def _required_string(value: Mapping[str, object], field: str) -> str:
    return _strict_string(value[field], field)

def _required_int(value: Mapping[str, object], field: str) -> int:
    candidate = value[field]
    if type(candidate) is not int:
        raise BootstrapError(f"customer bootstrap {field} is invalid")
    return candidate

def _optional_int(value: Mapping[str, object], field: str) -> int | None:
    candidate = value[field]
    if candidate is None:
        return None
    if type(candidate) is not int:
        raise BootstrapError(f"customer bootstrap {field} is invalid")
    return candidate

def _optional_float(value: Mapping[str, object], field: str) -> float | None:
    candidate = value[field]
    if candidate is None:
        return None
    if isinstance(candidate, bool) or not isinstance(candidate, (int, float)):
        raise BootstrapError(f"customer bootstrap {field} is invalid")
    return float(candidate)

def _optional_string(value: Mapping[str, object], field: str) -> str | None:
    candidate = value[field]
    if candidate is None:
        return None
    return _strict_string(candidate, field)

def _required_datetime(value: Mapping[str, object], field: str) -> datetime:
    return datetime.fromisoformat(_required_string(value, field))

def _string_tuple(value: Mapping[str, object], field: str) -> tuple[str, ...]:
    candidate = value[field]
    if not isinstance(candidate, list) or any(
        not isinstance(item, str) for item in candidate
    ):
        raise BootstrapError(f"customer bootstrap {field} is invalid")
    return tuple(cast(list[str], candidate))

def _mapping_sequence(
    value: object,
    *,
    label: str,
    expected: set[str],
) -> tuple[Mapping[str, object], ...]:
    if not isinstance(value, list):
        raise BootstrapError(f"{label} are invalid")
    result: list[Mapping[str, object]] = []
    for item in value:
        if (
            not isinstance(item, Mapping)
            or any(not isinstance(key, str) for key in item)
            or set(item) != expected
        ):
            raise BootstrapError(f"{label} are invalid")
        result.append(cast(Mapping[str, object], item))
    return tuple(result)

def _session_from_mapping(value: object) -> RoomBootstrapSession:
    if not isinstance(value, Mapping) or any(
        not isinstance(key, str) for key in value
    ):
        raise BootstrapError("customer bootstrap session is invalid")
    document = cast(Mapping[str, object], value)
    expected = set(_session_to_dict(_placeholder_session()))
    if set(document) != expected:
        raise BootstrapError("customer bootstrap session fields are invalid")
    try:
        draft_value = document["customer_draft"]
        if (
            not isinstance(draft_value, Mapping)
            or any(not isinstance(key, str) for key in draft_value)
            or set(draft_value) != set(CustomerDraft.__dataclass_fields__)
        ):
            raise BootstrapError("persisted customer draft fields are invalid")
        draft = _draft_from_mapping(draft_value)
        claim_values = _mapping_sequence(
            document["role_claims"],
            label="customer role claims",
            expected={"role", "user_id", "chat_id", "topic_id", "message_id"},
        )
        claims = tuple(
            RoleClaim(
                Role(_required_string(item, "role")),
                _telegram_id(item["user_id"], "claim user"),
                _telegram_id(item["chat_id"], "claim chat"),
                _telegram_id(item["topic_id"], "claim topic"),
                _telegram_id(item["message_id"], "claim message"),
            )
            for item in claim_values
        )
        attempt_values = _mapping_sequence(
            document["recovery_attempts"],
            label="customer recovery attempts",
            expected={"session_id", "slot", "chat_id", "generation", "created_at"},
        )
        attempts = tuple(
            RecoveryAttempt(
                _required_string(item, "session_id"),
                RecoverySlot(_required_string(item, "slot")),
                _telegram_id(item["chat_id"], "attempt chat"),
                _required_int(item, "generation"),
                _required_datetime(item, "created_at"),
            )
            for item in attempt_values
        )
        consent_card_message_id = document["consent_card_message_id"]
        failure_code = document["failure_code"]
        session = RoomBootstrapSession(
            session_id=_required_string(document, "session_id"),
            sid_hash=_required_string(document, "sid_hash"),
            customer_draft=draft,
            customer_draft_digest=_required_string(
                document, "customer_draft_digest"
            ),
            bot_username=_required_string(document, "bot_username"),
            state=BootstrapState(_required_string(document, "state")),
            generation=_required_int(document, "generation"),
            created_at=_required_datetime(document, "created_at"),
            updated_at=_required_datetime(document, "updated_at"),
            expires_at=_required_datetime(document, "expires_at"),
            owner_id=_telegram_id(document["owner_id"], "owner"),
            role_claims=claims,
            consent_publication_attempt=_required_int(
                document, "consent_publication_attempt"
            ),
            consent_card_message_id=(
                None
                if consent_card_message_id is None
                else _telegram_id(consent_card_message_id, "consent receipt")
            ),
            recovery_attempt_generation=_required_int(
                document, "recovery_attempt_generation"
            ),
            recovery_attempts=attempts,
            failure_code=(
                None
                if failure_code is None
                else _strict_string(failure_code, "failure_code")
            ),
        )
    except (KeyError, TypeError, ValueError) as exc:
        raise BootstrapError("customer bootstrap session value is invalid") from exc
    if (
        _SESSION_RE.fullmatch(session.session_id) is None
        or _DIGEST_RE.fullmatch(session.sid_hash) is None
        or session.customer_draft_digest != session.customer_draft.digest
        or session.generation < 1
        or session.consent_publication_attempt < 0
        or session.recovery_attempt_generation < 0
        or any(item.session_id != session.session_id for item in session.recovery_attempts)
        or any(item.created_at.tzinfo is None for item in session.recovery_attempts)
        or any(
            timestamp.tzinfo is None
            for timestamp in (session.created_at, session.updated_at, session.expires_at)
        )
    ):
        raise BootstrapError("customer bootstrap session authority is invalid")
    return session

def _placeholder_session() -> RoomBootstrapSession:
    now = datetime(2000, 1, 1, tzinfo=timezone.utc)
    draft = CustomerDraft("x", "x", "2000-01-01", "08:00", 0, 1, 1, 1, ("x",))
    return RoomBootstrapSession(
        "cb_0000000000000000000000",
        "0" * 64,
        draft,
        draft.digest,
        "ownerbot",
        BootstrapState.PREPARED,
        1,
        now,
        now,
        now,
        "1",
    )

def _draft_from_mapping(value: object) -> CustomerDraft:
    if not isinstance(value, Mapping) or any(not isinstance(key, str) for key in value):
        raise BootstrapError("customer draft is invalid")
    document = cast(Mapping[str, object], value)
    required = _DRAFT_REQUIRED
    allowed = required | _DRAFT_OPTIONAL
    if set(document) - allowed or required - set(document):
        raise BootstrapError("customer draft fields are invalid")
    defaults = CustomerDraft(
        "placeholder",
        "placeholder",
        "2000-01-01",
        "08:00",
        0,
        1,
        1,
        1,
        ("placeholder",),
    ).to_dict()
    values = cast(Mapping[str, object], {**defaults, **dict(document)})
    try:
        draft = CustomerDraft(
            customer_key=_required_string(values, "customer_key"),
            display_name=_required_string(values, "display_name"),
            starts_on=_required_string(values, "starts_on"),
            daily_time=_required_string(values, "daily_time"),
            weekly_weekday=_required_int(values, "weekly_weekday"),
            monthly_day=_required_int(values, "monthly_day"),
            calories_kcal=_required_int(values, "calories_kcal"),
            protein_g=_required_int(values, "protein_g"),
            meals=_string_tuple(values, "meals"),
            primary_goal=_required_string(values, "primary_goal"),
            dietary_restrictions=_string_tuple(values, "dietary_restrictions"),
            allergies=_string_tuple(values, "allergies"),
            food_preferences=_string_tuple(values, "food_preferences"),
            supplements=_string_tuple(values, "supplements"),
            digestion_context=_optional_string(values, "digestion_context"),
            sleep_goal_hours=_optional_float(values, "sleep_goal_hours"),
            recovery_goal=_optional_string(values, "recovery_goal"),
            training_context=_optional_string(values, "training_context"),
            carbohydrate_g=_optional_int(values, "carbohydrate_g"),
            fat_g=_optional_int(values, "fat_g"),
            water_liters=_optional_float(values, "water_liters"),
            customer_user_id=_optional_string(values, "customer_user_id"),
        )
    except (TypeError, ValueError) as exc:
        raise BootstrapError("customer draft value is invalid") from exc
    if (
        not draft.customer_key
        or not draft.display_name
        or not draft.meals
        or type(draft.weekly_weekday) is not int
        or not 0 <= draft.weekly_weekday <= 6
        or type(draft.monthly_day) is not int
        or not 1 <= draft.monthly_day <= 28
        or type(draft.calories_kcal) is not int
        or draft.calories_kcal <= 0
        or type(draft.protein_g) is not int
        or draft.protein_g <= 0
    ):
        raise BootstrapError("customer draft value is invalid")
    return draft

def _require_private_regular_file(path: Path, label: str) -> None:
    if path.is_symlink():
        raise BootstrapError(f"{label} must not be a symlink")
    try:
        info = path.stat()
    except OSError as exc:
        raise BootstrapError(f"{label} is unavailable") from exc
    if (
        not stat.S_ISREG(info.st_mode)
        or info.st_nlink != 1
        or stat.S_IMODE(info.st_mode) & 0o077
        or info.st_size > 1_048_576
    ):
        raise BootstrapError(f"{label} must be one private regular file")

def _telegram_id(value: object, label: str) -> str:
    text = str(value)
    if _ID_RE.fullmatch(text) is None or str(int(text)) != text:
        raise BootstrapError(f"{label} Telegram identity is invalid")
    return text

def _canonical_json(value: object) -> str:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        allow_nan=False,
    )

def _canonical_bytes(value: object) -> bytes:
    return _canonical_json(value).encode("utf-8")

def _sha256(value: bytes) -> str:
    return hashlib.sha256(value).hexdigest()

__all__ = [
    "BootstrapConflict",
    "BootstrapError",
    "BootstrapState",
    "CustomerDraft",
    "GenerationConflict",
    "PreparedRehearsalCustomerInvite",
    "RecoveryAttempt",
    "RecoverySlot",
    "Role",
    "RoleClaim",
    "RoomBootstrapSession",
    "RoomBootstrapStore",
    "RoomBootstrapTransport",
    "load_customer_draft",
    "room_bootstrap_state_dir",
]


#!/usr/bin/env python3
"""Drive one isolated profile through the production source Golden Path.

The driver emits no lifecycle phase declarations.  Its only durable output is the
profile and the native ledgers written by production APIs, plus raw external-I/O
and immutable source-provenance inputs used by the verifier.
"""
from __future__ import annotations

import argparse
import asyncio
import hashlib
import importlib.util
import json
import os
import shutil
import stat
import sys
import tempfile
import threading
from dataclasses import asdict
from datetime import UTC, date, datetime, time, timezone
from pathlib import Path
from types import SimpleNamespace
from typing import Any, TypedDict, cast
from unittest.mock import patch
from zoneinfo import ZoneInfo

_PROVENANCE_SPEC = importlib.util.spec_from_file_location(
    "golden_installed_wheel_provenance",
    Path(__file__).with_name("installed_wheel_provenance.py"),
)
if _PROVENANCE_SPEC is None or _PROVENANCE_SPEC.loader is None:
    raise RuntimeError("installed provenance helper is unavailable")
_PROVENANCE_MODULE = importlib.util.module_from_spec(_PROVENANCE_SPEC)
_PROVENANCE_SPEC.loader.exec_module(_PROVENANCE_MODULE)
collect_installed_runtime = _PROVENANCE_MODULE.collect_installed_runtime
loaded_module_receipts = _PROVENANCE_MODULE.loaded_module_receipts
verify_packaged_module_parity = _PROVENANCE_MODULE.verify_packaged_module_parity
portable_installed_runtime = _PROVENANCE_MODULE.portable_installed_runtime
nonportable_installed_record_audit = (
    _PROVENANCE_MODULE.nonportable_installed_record_audit
)

REPO = Path(__file__).resolve().parents[1]
DEFAULT_SOURCE = Path("/home/cube/.cache/task26-strict-successor-1786976146/src-p")
ANSWERS = (
    "1990년 1월 15일입니다.",
    "남성입니다.",
    "키는 180cm입니다.",
    "현재 체중은 80kg입니다.",
    "보통 수준입니다.",
    "주 3회 60분 근력 운동을 하고 평일에는 하루 8천 보 정도 걷습니다.",
    "현재 체중을 유지하고 싶습니다.",
    "유지가 목표라 목표 체중은 정하지 않겠습니다.",
    "유지가 목표라 목표 날짜도 정하지 않겠습니다.",
    "음식 알레르기는 없습니다.",
    "음식 불내증은 없습니다.",
    "종교적 또는 윤리적으로 제외하는 음식은 없습니다.",
    "싫어해서 피하는 음식은 없습니다.",
    "특별한 식단 선호는 없습니다.",
    "진단받은 질환은 없습니다.",
    "복용 중인 약이나 보충제는 없습니다.",
    "임신 또는 수유에 해당하지 않습니다.",
    "섭식장애 위험이나 과거력은 없습니다.",
    "가스레인지, 전자레인지, 냉장고를 사용할 수 있고 기본 조리가 가능합니다.",
    "식비 예산은 보통입니다.",
    "하루 세 끼를 먹습니다.",
    "평일 점심은 12시, 저녁은 운동 후 8시쯤이며 그 외 제약은 없습니다.",
)
CUSTOMER = "client_001"
OWNER_ID = 100
CUSTOMER_ID = 200

def canonical(value: object) -> bytes:
    return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()

def digest(value: object) -> str:
    return hashlib.sha256(canonical(value)).hexdigest()

def load_product_binding(path: Path | None) -> tuple[str, dict[str, object]]:
    if path is None:
        raise ValueError("private structured product/candidate binding is required")
    from gateway.platforms.task26_candidate_derivation import (
        qualification_tool_hashes,
        trust_boundary_digest,
        validate_product_binding,
    )
    from gateway.platforms.task26_final_state import TRUST_BOUNDARY

    selected = path.absolute()
    info = selected.lstat()
    if (
        stat.S_ISLNK(info.st_mode)
        or not stat.S_ISREG(info.st_mode)
        or info.st_nlink != 1
        or stat.S_IMODE(info.st_mode) not in {0o600, 0o400}
    ):
        raise ValueError("product/candidate binding file is not private")
    document = json.loads(selected.read_text(encoding="utf-8"))
    validated = validate_product_binding(
        document,
        expected_tool_hashes=qualification_tool_hashes(
            scripts_dir=Path(__file__).resolve().parent,
            package_root=REPO,
        ),
        expected_trust_boundary_sha256=trust_boundary_digest(TRUST_BOUNDARY),
    )
    return str(validated["candidate_digest"]), validated

def private_write(path: Path, value: object) -> None:
    path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
    path.parent.chmod(0o700)
    data = canonical(value) + b"\n"
    fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC | os.O_CLOEXEC | os.O_NOFOLLOW, 0o600)
    try:
        os.write(fd, data)
        os.fsync(fd)
    finally:
        os.close(fd)

def copy_verification_tools(profile: Path) -> None:
    target = profile / "verification-tools"
    target.mkdir(mode=0o700)
    target.chmod(0o700)
    for name in (
        "source_golden_path.py",
        "task26_build_ty_executable_wheel.py",
        "task26_frozen_bootstrap.py",
        "task26_hermetic_ty_runner.py",
        "task26_local_telegram_qa.py",
        "task26_ty_surface_gate.py",
        "verify_source_golden_path.py",
        "independent_verify_candidate.py",
        "installed_wheel_provenance.py",
    ):
        destination = target / name
        destination.write_bytes(Path(__file__).with_name(name).read_bytes())
        destination.chmod(0o600)

def source_provenance(source: Path) -> dict[str, object]:
    files: list[dict[str, object]] = []
    for path in sorted(source.rglob("*.py")):
        if path.is_symlink() or not path.is_file() or any(part in {".venv", "__pycache__"} for part in path.parts):
            continue
        relative = path.relative_to(source).as_posix()
        files.append({"path": relative, "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), "size": path.stat().st_size})
    return {
        "schema": "source-golden-provenance-v1",
        "source_root": str(source),
        "files": files,
        "tree_digest": digest(files),
    }

class FakeTelegram:
    def __init__(self, raw_log: Path) -> None:
        self.raw_log = raw_log
        self.message_id = 1000
        self._bot = SimpleNamespace(get_chat_member=self.get_chat_member)

    async def get_chat_member(self, **_kwargs: object) -> object:
        return SimpleNamespace(status="member")

    async def _send_nutrition_topic(self, **kwargs: object) -> object:
        self.message_id += 1
        row = {
            "kind": "onboarding_publication",
            "sequence": self.message_id,
            "chat_id": str(kwargs.get("chat_id")),
            "topic_id": str(kwargs.get("topic_id")),
            "text_sha256": hashlib.sha256(str(kwargs.get("text", "")).encode()).hexdigest(),
        }
        with self.raw_log.open("ab") as stream:
            stream.write(canonical(row) + b"\n")
            stream.flush()
            os.fsync(stream.fileno())
        return SimpleNamespace(message_id=self.message_id)

class Query:
    def __init__(self, actor: int, callback_id: str) -> None:
        self.id = callback_id
        self.from_user = SimpleNamespace(id=actor)

    async def answer(self, text: str | None = None, **_kwargs: object) -> None:
        del text

    async def edit_message_reply_markup(self, **_kwargs: object) -> None:
        return None

class Message:
    def __init__(self, actor: int, message_id: int, text: str = "", *, reply_to: int | None = None) -> None:
        self.text = text
        self.message_id = message_id
        self.message_thread_id = 0
        self.chat_id = actor
        self.chat = SimpleNamespace(id=actor, type="private")
        self.from_user = SimpleNamespace(id=actor)
        self.date = datetime.now(UTC)
        self.reply_to_message = SimpleNamespace(message_id=reply_to) if reply_to is not None else None

    async def reply_text(self, _text: str) -> None:
        return None

class UnavailableMaliciousProvider:
    async def reconcile(self, _answers: object, *, consent_granted: bool) -> object:
        from gateway.platforms.nutrition_onboarding_reconciliation import ReconciliationUnavailable
        if not consent_granted:
            raise AssertionError("provider called without consent")
        raise ReconciliationUnavailable("malicious provider returned untrusted lifecycle text")

class GenerationPins(TypedDict):
    expected_generation: int
    expected_record_digest: str
    expected_checkin_revision: str
    expected_draft_revision: str

def pins(value: object) -> GenerationPins:
    generation = getattr(value, "generation", None)
    record = getattr(value, "record_digest", getattr(value, "generation_record_digest", None))
    checkin = getattr(value, "checkin_revision", getattr(value, "generation_checkin_revision", None))
    draft = getattr(value, "draft_revision", getattr(value, "generation_draft_revision", None))
    if type(generation) is not int or not all(isinstance(item, str) for item in (record, checkin, draft)):
        raise RuntimeError("generation pins are unavailable")
    return {
        "expected_generation": generation,
        "expected_record_digest": cast(str, record),
        "expected_checkin_revision": cast(str, checkin),
        "expected_draft_revision": cast(str, draft),
    }

def membership_evidence(
    profile: Path,
    session: object,
    candidate_digest: str,
    product_binding: dict[str, object],
) -> tuple[Path, Path]:
    registry_path = profile / "customers/registry.json"
    config = profile / "config.yaml"
    config.write_text("platforms: {}\n", encoding="utf-8")
    config.chmod(0o600)
    derivation_inputs = cast(
        dict[str, object], product_binding["derivation_inputs"]
    )
    deployment = {
        "schema": "task26-source-deployment-receipt-v2",
        "candidate_digest": candidate_digest,
        "candidate_product_binding_sha256": product_binding["binding_sha256"],
        "candidate_core_digest": candidate_digest,
        "candidate_inventory_digest": candidate_digest,
        "hermes_wheel_sha256": derivation_inputs["hermes_wheel_sha256"],
        "profile_wheel_sha256": derivation_inputs["profile_wheel_sha256"],
        "product_binding": product_binding,
    }
    deployment_path = profile / "data/source-deployment-receipt.json"
    private_write(deployment_path, deployment)
    now = datetime.now(UTC).isoformat()
    row = {
        "schema": "telegram-staff-membership-evidence-v1",
        "phase": "pre_activation",
        **{
            key: value
            for key, value in deployment.items()
            if key not in {"product_binding", "schema"}
        },
        "deployment_receipt_path": str(deployment_path),
        "deployment_receipt_sha256": hashlib.sha256(deployment_path.read_bytes()).hexdigest(),
        "customer_id": CUSTOMER,
        "customer_user_id": str(CUSTOMER_ID),
        "bootstrap_session_id": str(getattr(session, "session_id")),
        "bootstrap_generation": int(getattr(session, "generation")),
        "registry_sha256": hashlib.sha256(registry_path.read_bytes()).hexdigest(),
        "config_sha256": hashlib.sha256(config.read_bytes()).hexdigest(),
        "staff_chat_inventory": [{"chat_id": str(OWNER_ID), "kind": "private_staff_dm", "staff_user_id": str(OWNER_ID), "roles": ["owner"], "sources": ["registry.owner"]}],
        "staff_chat_inventory_sha256": digest([{"chat_id": str(OWNER_ID), "kind": "private_staff_dm", "staff_user_id": str(OWNER_ID), "roles": ["owner"], "sources": ["registry.owner"]}]),
        "subscription_epoch_id": "source-golden-private-epoch",
        "subscription_armed_at_utc": now,
        "request_started_at_utc": now,
        "observed_at_utc": now,
        "activation_journal_sha256": "0" * 64,
        "membership_results": [],
        "private_dm_results": [{"chat_id": str(OWNER_ID), "staff_user_id": str(OWNER_ID), "identity_separated": True}],
    }
    row["evidence_sha256"] = digest(row)
    evidence_path = profile / "data/source-membership-evidence.json"
    private_write(evidence_path, row)
    return evidence_path, deployment_path

def checklist(profile: Path) -> Path:
    path = profile / "data/source-activation-checklist.json"
    private_write(path, {"checklist": {"token_rotated": True, "missend_test_passed": True, "provider_terms_checked": {"checked": True, "version": "privacy-v1"}, "withdrawal_deletion_doc": True, "retention_backup_doc": True, "manual_fallback_doc": True}})
    return path

async def exercise_background_authority_races(
    candidate_digest: str,
) -> dict[str, object]:
    """Produce native scheduler race observations from the production lease boundary."""
    from gateway.config import PlatformConfig
    from gateway.platforms.task26_runtime_authority import (
        CandidateAuthoritySource,
        append_external_authority,
        canonical as authority_canonical,
        digest as authority_digest,
    )
    from gateway.platforms.telegram import TelegramAdapter

    root = Path(tempfile.mkdtemp(prefix="task26-golden-background-authority-"))
    root.chmod(0o700)
    source_id = "task26-source-golden-background-races"
    append_external_authority(
        root,
        source_id=source_id,
        candidate_digest=hashlib.sha256(b"background-historical").hexdigest(),
        action="qualify",
        historical_pass_digest=hashlib.sha256(b"background-old-pass").hexdigest(),
        reason="background historical",
    )
    state = append_external_authority(
        root,
        source_id=source_id,
        candidate_digest=candidate_digest,
        action="qualify",
        historical_pass_digest=hashlib.sha256(b"background-current-pass").hexdigest(),
        reason="background current",
    )
    pin: dict[str, object] = {
        "schema": "task26-authority-pin-v1",
        "authority_root": str(root.resolve()),
        "source_id": source_id,
        "genesis_sha256": state["genesis_sha256"],
        "registry_head_sha256": state["registry_head_sha256"],
        "ledger_head_sha256": state["ledger_head_sha256"],
        "event_count": 2,
    }
    pin["pin_sha256"] = authority_digest(pin)
    pin_path = root.parent / f".{root.name}.pin.json"
    pin_path.write_bytes(authority_canonical(pin) + b"\n")
    pin_path.chmod(0o600)
    source = CandidateAuthoritySource(
        pin_path,
        forbidden_roots=(REPO,),
    )
    with source.authorize(candidate_digest, "service_activation") as snapshot:
        pass
    adapter = TelegramAdapter(
        PlatformConfig(enabled=True, token="background-race", extra={})
    )
    adapter._task26_authority_source = source
    adapter._task26_candidate_digest = candidate_digest
    adapter._task26_service_authority_snapshot = snapshot
    adapter._task26_runtime_required = True
    adapter._task26_service_revoked = False

    cancel_entered = asyncio.Event()
    cancel_release = asyncio.Event()

    async def cancelled_operation() -> None:
        async with adapter._authorize_task26_background_operation(
            "background_generation"
        ):
            cancel_entered.set()
            await cancel_release.wait()

    cancelled = asyncio.create_task(cancelled_operation())
    await asyncio.wait_for(cancel_entered.wait(), timeout=2)
    cancelled.cancel()
    try:
        await cancelled
    except asyncio.CancelledError:
        cancellation_released = True
    else:
        cancellation_released = False

    operation_entered = asyncio.Event()
    operation_release = asyncio.Event()
    revocation_rendezvous = threading.Barrier(2)
    revoked = threading.Event()

    async def completing_operation() -> None:
        async with adapter._authorize_task26_background_operation(
            "scheduled_transport"
        ):
            operation_entered.set()
            await operation_release.wait()

    def revoke() -> None:

[1252 more lines in file. Use offset=401 to continue.]


== v1.0 release top-level ==
0 dualcoach/releases/v1.0.0/delivered/qualification/profile-pytest.stderr
14 dualcoach/releases/v1.0.0/delivered/installed-golden-bundle/config.yaml
14 dualcoach/releases/v1.0.0/delivered/source-golden-bundle/config.yaml
19 dualcoach/releases/v1.0.0/delivered/receipts/ruff.log
19 dualcoach/releases/v1.0.0/delivered/receipts/ty-static.log
94 dualcoach/releases/v1.0.0/delivered/seals/site.txt
114 dualcoach/releases/v1.0.0/delivered/receipts/direct-url-attacks.log
115 dualcoach/releases/v1.0.0/delivered/receipts/ty-target-attacks.log
166 dualcoach/releases/v1.0.0/delivered/installed-golden-bundle/raw-customer-transport-calls.jsonl
166 dualcoach/releases/v1.0.0/delivered/source-golden-bundle/raw-customer-transport-calls.jsonl
180 dualcoach/releases/v1.0.0/delivered/receipts/related-lifecycle.log
190 dualcoach/releases/v1.0.0/delivered/receipts/installed-driver.stderr
190 dualcoach/releases/v1.0.0/delivered/receipts/source-driver.stderr
247 dualcoach/releases/v1.0.0/delivered/receipts/final-state-v7.log
254 dualcoach/releases/v1.0.0/delivered/prefreeze-modes.json
260 dualcoach/releases/v1.0.0/delivered/receipts/parser-installed.log
260 dualcoach/releases/v1.0.0/delivered/receipts/parser-source.log
371 dualcoach/releases/v1.0.0/delivered/source-golden-bundle/driver-input.json
377 dualcoach/releases/v1.0.0/delivered/installed-golden-bundle/driver-input.json
387 dualcoach/releases/v1.0.0/delivered/receipts/root-a-install.log
387 dualcoach/releases/v1.0.0/delivered/receipts/root-b-install.log
431 dualcoach/releases/v1.0.0/delivered/receipts/focused-task26.log
461 dualcoach/releases/v1.0.0/delivered/receipts/disposable-cleanup.json
495 dualcoach/releases/v1.0.0/delivered/receipts/reproducible-products.json
567 dualcoach/releases/v1.0.0/delivered/phase2-command-inputs.json
644 dualcoach/releases/v1.0.0/delivered/receipts/historical-provenance.json
705 dualcoach/releases/v1.0.0/delivered/receipts/final-state-v7-receipt.json
751 dualcoach/releases/v1.0.0/delivered/receipts/ty-build-a.json
751 dualcoach/releases/v1.0.0/delivered/receipts/ty-build-b.json
751 dualcoach/releases/v1.0.0/delivered/seals/corrected-ty-wheel-build-provenance.json
849 dualcoach/releases/v1.0.0/delivered/SEAL.json
896 dualcoach/releases/v1.0.0/delivered/seals/trust-boundary.json
901 dualcoach/releases/v1.0.0/delivered/qualification/profile-pytest.stdout
1021 dualcoach/releases/v1.0.0/delivered/receipts/source-driver.json
1027 dualcoach/releases/v1.0.0/delivered/receipts/installed-driver.json
1268 dualcoach/releases/v1.0.0/delivered/qualification/profile-pytest-runner.py
1295 dualcoach/releases/v1.0.0/delivered/seals/hermetic-ty-sealed-attestation.json
1651 dualcoach/releases/v1.0.0/delivered/receipts/authority.json
1653 dualcoach/releases/v1.0.0/delivered/seals/direct-url-portable-projection-v2.json
1909 dualcoach/releases/v1.0.0/delivered/seals/original-raw-record-audit.json
2039 dualcoach/releases/v1.0.0/delivered/receipts/expanded-task26-qualified.log
2174 dualcoach/releases/v1.0.0/delivered/qualification/profile-qualification-receipt.json
2629 dualcoach/releases/v1.0.0/delivered/candidate.json
3067 dualcoach/releases/v1.0.0/delivered/installed-golden-bundle/preexecution-product-binding.json
3067 dualcoach/releases/v1.0.0/delivered/preexecution-product-binding.json
3067 dualcoach/releases/v1.0.0/delivered/source-golden-bundle/preexecution-product-binding.json
3304 dualcoach/releases/v1.0.0/delivered/outer-candidate-manifest.json
3643 dualcoach/releases/v1.0.0/delivered/gateway/_commit_receipts.py
3700 dualcoach/releases/v1.0.0/delivered/source-golden-bundle/observer-receipts.jsonl
3721 dualcoach/releases/v1.0.0/delivered/installed-golden-bundle/observer-receipts.jsonl
4034 dualcoach/releases/v1.0.0/delivered/receipts/source-verifier.json
4081 dualcoach/releases/v1.0.0/delivered/receipts/installed-verifier.json
4212 dualcoach/releases/v1.0.0/delivered/installed-golden-bundle/raw-onboarding-calls.jsonl
4212 dualcoach/releases/v1.0.0/delivered/source-golden-bundle/raw-onboarding-calls.jsonl
5674 dualcoach/releases/v1.0.0/delivered/wheelhouse/annotated_doc-0.0.4-py3-none-any.whl
5782 dualcoach/releases/v1.0.0/delivered/cross-mode-candidate-parity.json
6275 dualcoach/releases/v1.0.0/delivered/seals/historical.json
6615 dualcoach/releases/v1.0.0/delivered/receipts/hermetic-ty-attestation.json
6682 dualcoach/releases/v1.0.0/delivered/verification-tools/task26_build_ty_executable_wheel.py
6825 dualcoach/releases/v1.0.0/delivered/wheelhouse/eval_type_backport-0.4.0-py3-none-any.whl
7839 dualcoach/releases/v1.0.0/delivered/wheelhouse/iniconfig-2.3.0-py3-none-any.whl
7925 dualcoach/releases/v1.0.0/delivered/receipts/two-root-install-parity.json
8091 dualcoach/releases/v1.0.0/delivered/wheelhouse/termcolor-3.3.0-py3-none-any.whl
9005 dualcoach/releases/v1.0.0/delivered/verification-tools/task26_seal_delivered_bundle.py
9135 dualcoach/releases/v1.0.0/delivered/verification-tools/task26_ty_surface_gate.py
9318 dualcoach/releases/v1.0.0/delivered/wheelhouse/httpx_sse-0.4.3-py3-none-any.whl
9633 dualcoach/releases/v1.0.0/delivered/wheelhouse/aiosignal-1.4.0-py3-none-any.whl
10203 dualcoach/releases/v1.0.0/delivered/receipts/profile-build-a.log
10203 dualcoach/releases/v1.0.0/delivered/receipts/profile-build-b.log
10354 dualcoach/releases/v1.0.0/delivered/wheelhouse/mdurl-0.1.2-py3-none-any.whl
10573 dualcoach/releases/v1.0.0/delivered/wheelhouse/zipp-4.1.0-py3-none-any.whl
10586 dualcoach/releases/v1.0.0/delivered/wheelhouse/sniffio-1.3.1-py3-none-any.whl
11423 dualcoach/releases/v1.0.0/delivered/wheelhouse/six-1.17.0-py3-none-any.whl
13111 dualcoach/releases/v1.0.0/delivered/wheelhouse/socksio-1.0.0-py3-none-any.whl
13618 dualcoach/releases/v1.0.0/delivered/verification-tools/task26_local_telegram_qa.py
13626 dualcoach/releases/v1.0.0/delivered/verification-tools/task26_hermetic_ty_runner.py
14023 dualcoach/releases/v1.0.0/delivered/wheelhouse/annotated_types-0.7.0-py3-none-any.whl
14033 dualcoach/releases/v1.0.0/delivered/wheelhouse/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.whl
14388 dualcoach/releases/v1.0.0/delivered/wheelhouse/ptyprocess-0.7.0-py3-none-any.whl
14643 dualcoach/releases/v1.0.0/delivered/wheelhouse/sse_starlette-3.3.2-py3-none-any.whl
14870 dualcoach/releases/v1.0.0/delivered/wheelhouse/jsonpath_python-1.1.6-py3-none-any.whl
14995 dualcoach/releases/v1.0.0/delivered/wheelhouse/typing_inspection-0.4.2-py3-none-any.whl
15456 dualcoach/releases/v1.0.0/delivered/wheelhouse/pytest_asyncio-1.3.0-py3-none-any.whl
16265 dualcoach/releases/v1.0.0/delivered/verification-tools/task26_frozen_bootstrap.py
18873 dualcoach/releases/v1.0.0/delivered/wheelhouse/jsonschema_specifications-2025.9.1-py3-none-any.whl
19433 dualcoach/releases/v1.0.0/delivered/installed-golden-bundle/source-provenance.json
19433 dualcoach/releases/v1.0.0/delivered/source-golden-bundle/source-provenance.json
19824 dualcoach/releases/v1.0.0/delivered/wheelhouse/jmespath-1.1.0-py3-none-any.whl
20353 dualcoach/releases/v1.0.0/delivered/gateway/commit_observer.py
20625 dualcoach/releases/v1.0.0/delivered/wheelhouse/distro-1.9.0-py3-none-any.whl
20879 dualcoach/releases/v1.0.0/delivered/wheelhouse/pluggy-1.6.0-py3-none-any.whl
21267 dualcoach/releases/v1.0.0/delivered/receipts/install.log
21724 dualcoach/releases/v1.0.0/delivered/candidate-authority/qualification-ledger.json
22504 dualcoach/releases/v1.0.0/delivered/wheelhouse/python_dotenv-1.2.2-py3-none-any.whl
22777 dualcoach/releases/v1.0.0/delivered/wheelhouse/shellingham-1.5.4-py3-none-any.whl
22909 dualcoach/releases/v1.0.0/delivered/wheelhouse/docstring_parser-0.18.0-py3-none-any.whl
22928 dualcoach/releases/v1.0.0/delivered/wheelhouse/MarkupSafe-3.0.3-cp312-cp312-manylinux_2_17_x86_64.whl
25698 dualcoach/releases/v1.0.0/delivered/candidate-authority/registry.json
25853 dualcoach/releases/v1.0.0/delivered/wheelhouse/croniter-6.0.0-py3-none-any.whl
27131 dualcoach/releases/v1.0.0/delivered/wheelhouse/referencing-0.37.0-py3-none-any.whl
28253 dualcoach/releases/v1.0.0/delivered/wheelhouse/importlib_metadata-8.7.1-py3-none-any.whl
28739 dualcoach/releases/v1.0.0/delivered/wheelhouse/aiohappyeyeballs-2.7.1-py3-none-any.whl
29282 dualcoach/releases/v1.0.0/delivered/wheelhouse/tenacity-9.1.4-py3-none-any.whl
29639 dualcoach/releases/v1.0.0/delivered/wheelhouse/python_multipart-0.0.27-py3-none-any.whl
31618 dualcoach/releases/v1.0.0/delivered/wheelhouse/PyJWT-2.13.0-py3-none-any.whl
37880 dualcoach/releases/v1.0.0/delivered/wheelhouse/h11-0.16.0-py3-none-any.whl
40013 dualcoach/releases/v1.0.0/delivered/wheelhouse/tabulate-0.10.0-py3-none-any.whl
40734 dualcoach/releases/v1.0.0/delivered/receipts/full-gateway-qualified.log
40782 dualcoach/releases/v1.0.0/delivered/verification-tools/independent_verify_candidate.py
44335 dualcoach/releases/v1.0.0/delivered/verification-tools/installed_wheel_provenance.py
45037 dualcoach/releases/v1.0.0/delivered/wheelhouse/typing_extensions-4.15.0-py3-none-any.whl
46732 dualcoach/releases/v1.0.0/delivered/seals/wheelhouse-inventory.json
48548 dualcoach/releases/v1.0.0/delivered/wheelhouse/pycparser-3.0-py3-none-any.whl
57167 dualcoach/releases/v1.0.0/delivered/wheelhouse/honcho_ai-2.0.1-py3-none-any.whl
57681 dualcoach/releases/v1.0.0/delivered/wheelhouse/pathspec-1.1.1-py3-none-any.whl
59317 dualcoach/releases/v1.0.0/delivered/wheelhouse/pydantic_settings-2.13.1-py3-none-any.whl
63946 dualcoach/releases/v1.0.0/delivered/wheelhouse/propcache-0.5.2-cp312-cp312-manylinux_2_17_x86_64.whl
65414 dualcoach/releases/v1.0.0/delivered/wheelhouse/requests-2.33.0-py3-none-any.whl
66230 dualcoach/releases/v1.0.0/delivered/wheelhouse/edge_tts-7.2.7-py3-none-any.whl
66741 dualcoach/releases/v1.0.0/delivered/wheelhouse/opentelemetry_api-1.39.1-py3-none-any.whl
67498 dualcoach/releases/v1.0.0/delivered/prefreeze-inventory.json
67958 dualcoach/releases/v1.0.0/delivered/wheelhouse/attrs-25.4.0-py3-none-any.whl
69133 dualcoach/releases/v1.0.0/delivered/wheelhouse/uvicorn-0.41.0-py3-none-any.whl
72675 dualcoach/releases/v1.0.0/delivered/wheelhouse/idna-3.15-py3-none-any.whl
73140 dualcoach/releases/v1.0.0/delivered/verification-tools/verify_source_golden_path.py
73163 dualcoach/releases/v1.0.0/delivered/wheelhouse/starlette-1.0.1-py3-none-any.whl
73611 dualcoach/releases/v1.0.0/delivered/verification-tools/source_golden_path.py
73857 dualcoach/releases/v1.0.0/delivered/wheelhouse/httpx-0.28.1-py3-none-any.whl
74722 dualcoach/releases/v1.0.0/delivered/wheelhouse/packaging-26.0-py3-none-any.whl
78753 dualcoach/releases/v1.0.0/delivered/wheelhouse/tqdm-4.67.3-py3-none-any.whl
79129 dualcoach/releases/v1.0.0/delivered/wheelhouse/httpcore-1.0.9-py3-none-any.whl
87028 dualcoach/releases/v1.0.0/delivered/wheelhouse/s3transfer-0.16.1-py3-none-any.whl
87694 dualcoach/releases/v1.0.0/delivered/wheelhouse/markdown_it_py-4.0.0-py3-none-any.whl
90998 dualcoach/releases/v1.0.0/delivered/wheelhouse/jsonschema-4.26.0-py3-none-any.whl
91430 dualcoach/releases/v1.0.0/delivered/postfreeze-seal.json
91856 dualcoach/releases/v1.0.0/delivered/delivered-bundle-seal.json
94536 dualcoach/releases/v1.0.0/delivered/wheelhouse/wcwidth-0.6.0-py3-none-any.whl
108571 dualcoach/releases/v1.0.0/delivered/wheelhouse/Markdown-3.10.2-py3-none-any.whl
108616 dualcoach/releases/v1.0.0/delivered/wheelhouse/click-8.3.1-py3-none-any.whl
109386 dualcoach/releases/v1.0.0/delivered/wheelhouse/fastapi-0.133.1-py3-none-any.whl
113928 dualcoach/releases/v1.0.0/delivered/wheelhouse/anyio-4.12.1-py3-none-any.whl
116280 dualcoach/releases/v1.0.0/delivered/wheelhouse/fire-0.7.1-py3-none-any.whl
121960 dualcoach/releases/v1.0.0/delivered/wheelhouse/ruamel_yaml-0.18.17-py3-none-any.whl
131436 dualcoach/releases/v1.0.0/delivered/wheelhouse/urllib3-2.7.0-py3-none-any.whl
134496 dualcoach/releases/v1.0.0/delivered/wheelhouse/certifi-2026.5.20-py3-none-any.whl
135243 dualcoach/releases/v1.0.0/delivered/wheelhouse/Jinja2-3.1.6-py3-none-any.whl
140910 dualcoach/releases/v1.0.0/delivered/wheelhouse/boto3-1.42.89-py3-none-any.whl
143318 dualcoach/releases/v1.0.0/delivered/wheelhouse/yarl-1.24.2-cp312-cp312-manylinux_2_17_x86_64.whl
144622 dualcoach/releases/v1.0.0/delivered/seals/runtime-portable.json
153310 dualcoach/releases/v1.0.0/delivered/wheelhouse/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_17_x86_64.whl
155716 dualcoach/releases/v1.0.0/delivered/wheelhouse/psutil-7.2.2-cp36-abi3-manylinux_2_12_x86_64.whl
182183 dualcoach/releases/v1.0.0/delivered/wheelhouse/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.whl
205800 dualcoach/releases/v1.0.0/delivered/sealed-expected-state.json
219597 dualcoach/releases/v1.0.0/delivered/wheelhouse/cffi-2.0.0-cp312-cp312-manylinux_2_17_x86_64.whl
220438 dualcoach/releases/v1.0.0/delivered/wheelhouse/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl
230324 dualcoach/releases/v1.0.0/delivered/wheelhouse/python_dateutil-2.9.0.post0-py3-none-any.whl
233940 dualcoach/releases/v1.0.0/delivered/wheelhouse/mcp-1.26.0-py3-none-any.whl
244344 dualcoach/releases/v1.0.0/delivered/wheelhouse/frozenlist-1.8.0-cp312-cp312-manylinux_2_5_x86_64.whl
260469 dualcoach/releases/v1.0.0/delivered/wheelhouse/multidict-6.7.1-cp312-cp312-manylinux_2_17_x86_64.whl
283488 dualcoach/releases/v1.0.0/delivered/qualification/profile-collection-manifest.json
290366 dualcoach/releases/v1.0.0/delivered/receipts/hermes-build-a.log
290366 dualcoach/releases/v1.0.0/delivered/receipts/hermes-build-b.log
310798 dualcoach/releases/v1.0.0/delivered/wheelhouse/rich-14.3.3-py3-none-any.whl
314877 dualcoach/releases/v1.0.0/delivered/artifacts/physique_checkin_cli-0.1.0-py3-none-any.whl
352852 dualcoach/releases/v1.0.0/delivered/wheelhouse/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.whl
359037 dualcoach/releases/v1.0.0/delivered/wheelhouse/typer-0.27.0-py3-none-any.whl
365873 dualcoach/releases/v1.0.0/delivered/receipts/runtime-probe.json
365968 dualcoach/releases/v1.0.0/delivered/seals/runtime-with-origin.json
375134 dualcoach/releases/v1.0.0/delivered/wheelhouse/pytest-9.0.2-py3-none-any.whl
387591 dualcoach/releases/v1.0.0/delivered/wheelhouse/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.whl
391808 dualcoach/releases/v1.0.0/delivered/wheelhouse/prompt_toolkit-3.0.52-py3-none-any.whl
448553 dualcoach/releases/v1.0.0/delivered/wheelhouse/tornado-6.5.7-cp39-abi3-manylinux_2_5_x86_64.whl
454794 dualcoach/releases/v1.0.0/delivered/wheelhouse/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
472302 dualcoach/releases/v1.0.0/delivered/wheelhouse/anthropic-0.87.0-py3-none-any.whl
472612 dualcoach/releases/v1.0.0/delivered/wheelhouse/pydantic-2.13.4-py3-none-any.whl
509067 dualcoach/releases/v1.0.0/delivered/wheelhouse/pytz-2025.2-py3-none-any.whl
517555 dualcoach/releases/v1.0.0/delivered/wheelhouse/httptools-0.7.1-cp312-cp312-manylinux_2_5_x86_64.whl
523583 dualcoach/releases/v1.0.0/delivered/wheelhouse/elevenlabs-1.59.0-py3-none-any.whl
560372 dualcoach/releases/v1.0.0/delivered/installed-golden-bundle/installed-provenance.json
737642 dualcoach/releases/v1.0.0/delivered/wheelhouse/python_telegram_bot-22.6-py3-none-any.whl
788323 dualcoach/releases/v1.0.0/delivered/wheelhouse/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux_2_17_x86_64.whl
807803 dualcoach/releases/v1.0.0/delivered/wheelhouse/PyYAML-6.0.3-cp312-cp312-manylinux_2_17_x86_64.whl
856500 dualcoach/releases/v1.0.0/delivered/wheelhouse/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.whl
890744 dualcoach/releases/v1.0.0/delivered/wheelhouse/davey-0.1.6-cp312-cp312-manylinux_2_17_x86_64.whl
1112370 dualcoach/releases/v1.0.0/delivered/wheelhouse/mistralai-2.4.8-py3-none-any.whl
1120581 dualcoach/releases/v1.0.0/delivered/wheelhouse/openai-2.24.0-py3-none-any.whl
1225614 dualcoach/releases/v1.0.0/delivered/wheelhouse/Pygments-2.19.2-py3-none-any.whl
1227890 dualcoach/releases/v1.0.0/delivered/wheelhouse/discord_py-2.7.1-py3-none-any.whl
1537000 dualcoach/releases/v1.0.0/delivered/wheelhouse/brotlicffi-1.2.0.1-cp38-abi3-manylinux_2_17_x86_64.whl
2034543 dualcoach/releases/v1.0.0/delivered/wheelhouse/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.whl
2268431 dualcoach/releases/v1.0.0/delivered/wheelhouse/aiohttp-3.14.1-cp312-cp312-manylinux_2_17_x86_64.whl
4310962 dualcoach/releases/v1.0.0/delivered/wheelhouse/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl
4426080 dualcoach/releases/v1.0.0/delivered/wheelhouse/uvloop-0.22.1-cp312-cp312-manylinux_2_17_x86_64.whl
4457274 dualcoach/releases/v1.0.0/delivered/wheelhouse/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl
6897501 dualcoach/releases/v1.0.0/delivered/wheelhouse/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.whl
8511603 dualcoach/releases/v1.0.0/delivered/artifacts/hermes_agent-0.17.0-py3-none-any.whl
10473221 dualcoach/releases/v1.0.0/delivered/wheelhouse/ty-0.0.21-py3-none-manylinux_2_17_x86_64.whl
14950556 dualcoach/releases/v1.0.0/delivered/wheelhouse/botocore-1.42.97-py3-none-any.whl
== references to v1 real-evidence seals ==
dualcoach/releases/v1.1.0/base-release.json:5:  "base_f1_f5_consolidated_sha256": "4f578c4752a9728d40308a0faa52b4ad22349af8483e9d522c0fb343ba70a2a4",
dualcoach/releases/v1.1.0/base-release.json:6:  "base_final_report_sha256": "31d7daa60dad93dce41761b2c36f1fe3ec38e8f9257761fd9b6771d83ac32a76",
dualcoach/releases/v1.1.0/base-release.json:11:  "base_task27_manifest_sha256": "50eb92a4ef7fde599aad82f445f5ba12a456895a9050319642163303ef7aed9d",
dualcoach/releases/v1.1.0/qualification-receipt.json:55:    "base_f1_f5_consolidated_sha256": "4f578c4752a9728d40308a0faa52b4ad22349af8483e9d522c0fb343ba70a2a4",
dualcoach/releases/v1.1.0/qualification-receipt.json:56:    "base_final_report_sha256": "31d7daa60dad93dce41761b2c36f1fe3ec38e8f9257761fd9b6771d83ac32a76",
dualcoach/releases/v1.1.0/qualification-receipt.json:57:    "base_task27_manifest_sha256": "50eb92a4ef7fde599aad82f445f5ba12a456895a9050319642163303ef7aed9d",
./dualcoach/releases/v1.1.0/base-release.json:5:  "base_f1_f5_consolidated_sha256": "4f578c4752a9728d40308a0faa52b4ad22349af8483e9d522c0fb343ba70a2a4",
./dualcoach/releases/v1.1.0/base-release.json:6:  "base_final_report_sha256": "31d7daa60dad93dce41761b2c36f1fe3ec38e8f9257761fd9b6771d83ac32a76",
./dualcoach/releases/v1.1.0/base-release.json:11:  "base_task27_manifest_sha256": "50eb92a4ef7fde599aad82f445f5ba12a456895a9050319642163303ef7aed9d",
./dualcoach/releases/v1.1.0/qualification-receipt.json:55:    "base_f1_f5_consolidated_sha256": "4f578c4752a9728d40308a0faa52b4ad22349af8483e9d522c0fb343ba70a2a4",
./dualcoach/releases/v1.1.0/qualification-receipt.json:56:    "base_final_report_sha256": "31d7daa60dad93dce41761b2c36f1fe3ec38e8f9257761fd9b6771d83ac32a76",
./dualcoach/releases/v1.1.0/qualification-receipt.json:57:    "base_task27_manifest_sha256": "50eb92a4ef7fde599aad82f445f5ba12a456895a9050319642163303ef7aed9d",
./tests/gateway/test_telegram_mention_boundaries.py:57:    """A real Telegram mention always comes with a MENTION entity — detect those."""


== golden scripts CLI surface ==
usage: source_golden_path.py [-h]
                             [--source SOURCE | --installed-venv INSTALLED_VENV]
                             [--installed-site-packages INSTALLED_SITE_PACKAGES]
                             [--profile-wheel PROFILE_WHEEL]
                             [--hermes-wheel HERMES_WHEEL]
                             [--product-binding PRODUCT_BINDING]
                             [--evidence-root EVIDENCE_ROOT]

options:
  -h, --help            show this help message and exit
  --source SOURCE
  --installed-venv INSTALLED_VENV
  --installed-site-packages INSTALLED_SITE_PACKAGES
  --profile-wheel PROFILE_WHEEL
  --hermes-wheel HERMES_WHEEL
  --product-binding PRODUCT_BINDING
  --evidence-root EVIDENCE_ROOT
== verifier CLI surface ==
Traceback (most recent call last):
  File "/home/cube/projects/richard/hermes-agent-dualcoach-v1.1/dualcoach/releases/v1.1.0/installed-golden-bundle/verification-tools/verify_source_golden_path.py", line 18, in <module>
    from gateway.platforms.task26_runtime_authority import validate_snapshot
ModuleNotFoundError: No module named 'gateway'

Command exited with code 1


Summary:
No prior history.

---

**Turn Context (split turn):**

## Original Request
Conduct a read-only F4 hands-on review of v1.1: locally re-run installed-wheel/manual checks for 86400-second TTL, private-DM claim, replay rejection, bad input; verify fresh installed Golden Path and loopback Telegram transcript; verify immutable v1 real Telegram evidence is byte-identical and inherited only as base evidence. No live/external actions. Return PASS/FAIL, commands/results/gaps, JSON-ready summary; only use verdict `PASS_PRESERVED_BASE_REAL_SURFACE_PLUS_V1_1_OFFLINE_PARITY` if supported.

## Early Progress
- Identified task as `st_01a01fa3`; inspected task metadata and sibling F1/F2/F3/F5 scopes.
- Initial repository/evidence searches in `/home/cube/projects/richard/traning coach` were noisy due to extensive `.gjc`/task26 material.
- Located the actual v1.1 worktree and release:
  - Worktree: `/home/cube/projects/richard/hermes-agent-dualcoach-v1.1`
  - Release root: `dualcoach/releases/v1.1.0`
  - Candidate: `0e383539aea1b83205771772f1e8b417840defe60f5ea6ce184f80e3af8d25f9`
- Read key release artifacts/receipts, including `base-release.json`, `qualification-receipt.json`, `qualification-seal.json`, installed Golden receipt, loopback Telegram QA receipt, and full Gateway receipt.
- No live Telegram/provider/customer/service actions were performed; all inspection so far was local/read-only.

## Context for Suffix
- v1.1 qualification states:
  - Hermes wheel SHA-256: `ec160d3d0e29bc747f463923d31bfe736aefb5840a3d32fcb04842aa5139ddb0`
  - Profile wheel unchanged: `a56da2417df0912f3fe407c0b78befd7362207d1c8a35271000af46701ba79d2`
  - 992 wheel members; only changed members are `gateway/platforms/telegram_customer_bootstrap.py` and wheel `RECORD`.
  - Manual exact-wheel claimed state `REGISTERING`, replay text `customer bootstrap invite is unavailable`, TTL `86400`.
  - Fresh source and installed Golden Path receipts claim `ACTUAL_*_GOLDEN_PATH_PASS`.
- `scripts/verify_dualcoach_v11_candidate.py` is the narrow delta verifier; it expects v1 TTL `timedelta(minutes=30)` changed to v1.1 `timedelta(hours=24)` and validates wheel RECORD/member integrity.
- Installed Golden bundle loopback transcript receipt:
  `dualcoach/releases/v1.1.0/installed-golden-bundle/data/task26-local-http-telegram-api-qa-installed.json`
  records installed runtime, real local loopback socket on `127.0.0.1`, `external_traffic:false`, seven API calls, `getMe`/`getUpdates` observed, zero mutations, revocation/cleanup PASS.
- Base v1 evidence is explicitly modeled as immutable delta base:
  - Base candidate `d1109d8f...`
  - Base tag `dualcoach-v1.0.0`, commit `d7425705...`
  - Base Hermes wheel SHA `f8b3c779...`
  - `base-release.json` declares `qualification_model: delta_over_immutable_v1`.
- Prior task27 F4 receipt establishes preserved historical real Telegram surface only, with no successor execution: one historical customer delivery and owner explicit send; it must not be represented as v1.1 external execution.
- Worktree `git status` showed release files as added/untracked (`A`); avoid changing them.

<read-files>
.omo/evidence/task26/task26-combined-v38-delivered-st_01a019d7/SEAL.json
.omo/evidence/task26/task26-combined-v38-delivered-st_01a019d7/candidate.json
.omo/evidence/task27/task27-v38-final-verification-f4-supplemental.json
.omo/evidence/task27/task27-v38-final-verification-f4.json
.omo/senpi-task/tasks/st_01a01fa0.json
.omo/senpi-task/tasks/st_01a01fa1.json
.omo/senpi-task/tasks/st_01a01fa2.json
.omo/senpi-task/tasks/st_01a01fa3.json
.omo/senpi-task/tasks/st_01a01fa4.json
/home/cube/projects/richard/hermes-agent-dualcoach-v1.1/dualcoach/releases/v1.1.0/base-release.json
/home/cube/projects/richard/hermes-agent-dualcoach-v1.1/dualcoach/releases/v1.1.0/full-gateway-receipt.json
/home/cube/projects/richard/hermes-agent-dualcoach-v1.1/dualcoach/releases/v1.1.0/installed-golden-bundle/data/task26-local-http-telegram-api-qa-installed.json
/home/cube/projects/richard/hermes-agent-dualcoach-v1.1/dualcoach/releases/v1.1.0/qualification-receipt.json
/home/cube/projects/richard/hermes-agent-dualcoach-v1.1/dualcoach/releases/v1.1.0/qualification-seal.json
/home/cube/projects/richard/hermes-agent-dualcoach-v1.1/dualcoach/releases/v1.1.0/receipts/installed-result.json
/home/cube/projects/richard/hermes-agent-dualcoach-v1.1/dualcoach/releases/v1.1.0/receipts/installed-verify.json
/home/cube/projects/richard/hermes-agent-dualcoach-v1.1/scripts/verify_dualcoach_v11_candidate.py
</read-files>


**Inspecting golden path arguments**

**Planning offline wheel installation in temp venv**

**Confirming user rerun intent for installed checks**

**Inspecting golden path arguments**

**Planning token expiry and claim tests**

**Standardizing bad input error message**

I read this as a read-only F4 release review - I will re-run the isolated installed-wheel checks and Golden/loopback verification, then report an evidence-backed verdict.

bash

cd /home/cube/projects/richard/hermes-agent-dualcoach-v1.1 && printf '%s\n' '== runtime and available installed evidence ==' && python3 --version && find dualcoach/releases/v1.1.0 -type d \( -name '*venv*' -o -name 'site-packages' \) -printf '%p\n' | sort && printf '%s\n' '== golden runtime inputs ==' && find dualcoach/releases/v1.1.0 -maxdepth 3 -type f \( -name '*binding*.json' -o -name '*.whl' \) -printf '%s %p\n' | sort -n | head -1000

cd /home/cube/projects/richard/hermes-agent-dualcoach-v1.1 && printf '%s\n' '== immutable v1 evidence candidates and hashes ==' && for f in \
  .omo/evidence/task26/task26-combined-v38-delivered-st_01a019d7/SEAL.json \
  .omo/evidence/task26/task26-combined-v38-delivered-st_01a019d7/candidate.json \
  .omo/evidence/task27/task27-v38-final-verification-f4.json \
  .omo/evidence/task27/task27-v38-final-verification-f4-supplemental.json \
  dualcoach/releases/v1.0.0/delivered/receipts/historical-provenance.json \
  dualcoach/releases/v1.0.0/delivered/seals/historical.json; do
  if [ -f "$f" ]; then sha256sum "$f"; fi
done
printf '%s\n' '== v1 release integrity ==' && sha256sum dualcoach/releases/v1.0.0/delivered/{SEAL.json,candidate.json,delivered-bundle-seal.json} && git tag --contains d7425705d5c9c1537d14289dd78943e81c5aed4e 2>/dev/null || true

cd /home/cube/projects/richard/hermes-agent-dualcoach-v1.1 && printf '%s\n' '== candidate delta/artifact inventory ==' && sha256sum dualcoach/releases/v1.1.0/artifacts/*.whl && python3 - <<'PY'
from pathlib import Path
import zipfile, hashlib
for whl in sorted(Path('dualcoach/releases/v1.1.0/artifacts').glob('*.whl')):
    with zipfile.ZipFile(whl) as z:
        names=sorted(z.namelist())
        print(whl.name, 'members=',len(names), 'record=', [n for n in names if n.endswith('/RECORD')])
        print('bootstrap=', hashlib.sha256(z.read('gateway/platforms/telegram_customer_bootstrap.py')).hexdigest())
PY


getMe deleteWebhook deleteWebhook setMyCommands setMyCommands getUpdates setMyCommands


customer bootstrap invite is unavailable

resource-contention diagnostic, not a release gate

two internal timeout failures and one 140-second file ceiling


"""Verify the narrow product delta from DualCoach v1.0.0 to v1.1.0."""

from __future__ import annotations

import argparse
import base64
import csv
import hashlib
import io
import json
import zipfile
from dataclasses import dataclass
from pathlib import Path
from typing import Final

_TTL_MEMBER: Final = "gateway/platforms/telegram_customer_bootstrap.py"
_RECORD_MEMBER: Final = "hermes_agent-0.17.0.dist-info/RECORD"
_EXPECTED_CHANGED_MEMBERS: Final = (_TTL_MEMBER, _RECORD_MEMBER)
_OLD_TTL: Final = b"_INVITE_TTL: Final = timedelta(minutes=30)"
_NEW_TTL: Final = b"_INVITE_TTL: Final = timedelta(hours=24)"
_DIGEST_LENGTH: Final = 64

@dataclass(frozen=True, slots=True)
class WheelDelta:
    """Verified member-level difference between two Hermes wheels."""

    member_count: int
    changed_members: tuple[str, ...]

def _canonical(value: dict[str, object]) -> bytes:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        allow_nan=False,
    ).encode()

def _read_object(path: Path) -> dict[str, object]:
    value = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(value, dict) or any(not isinstance(key, str) for key in value):
        raise ValueError(f"{path.name} is not an object")
    return value

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

def _record_digest(encoded: str) -> str:
    algorithm, separator, value = encoded.partition("=")
    if algorithm != "sha256" or separator != "=":
        raise ValueError("wheel RECORD digest is invalid")
    padded = value + "=" * (-len(value) % 4)
    digest = base64.urlsafe_b64decode(padded)
    if len(digest) * 2 != _DIGEST_LENGTH:
        raise ValueError("wheel RECORD digest length is invalid")
    return digest.hex()

def _verify_record(archive: zipfile.ZipFile, names: tuple[str, ...]) -> None:
    if len(names) != len(set(names)) or _RECORD_MEMBER not in names:
        raise ValueError("wheel member inventory is invalid")
    rows = csv.reader(io.StringIO(archive.read(_RECORD_MEMBER).decode("utf-8")))
    seen: set[str] = set()
    for row in rows:
        if len(row) != 3:
            raise ValueError("wheel RECORD row is invalid")
        relative, encoded_hash, raw_size = row
        if relative in seen or relative not in names:
            raise ValueError("wheel RECORD path is invalid")
        seen.add(relative)
        if relative == _RECORD_MEMBER:
            if encoded_hash or raw_size:
                raise ValueError("wheel RECORD self-row is invalid")
            continue
        if not raw_size.isdecimal():
            raise ValueError("wheel RECORD size is invalid")
        payload = archive.read(relative)
        if int(raw_size) != len(payload) or _record_digest(encoded_hash) != _sha256(
            payload
        ):
            raise ValueError("wheel RECORD content binding is invalid")
    if seen != set(names):
        raise ValueError("wheel RECORD inventory is incomplete")

def verify_wheel_delta(base_wheel: Path, successor_wheel: Path) -> WheelDelta:
    """Require the successor wheel to change only TTL code and RECORD."""
    with (
        zipfile.ZipFile(base_wheel) as base,
        zipfile.ZipFile(successor_wheel) as successor,
    ):
        base_names = tuple(base.namelist())
        successor_names = tuple(successor.namelist())
        if base_names != successor_names:
            raise ValueError("wheel member order or inventory changed")
        _verify_record(base, base_names)
        _verify_record(successor, successor_names)
        changed = tuple(
            name
            for name in base_names
            if base.read(name) != successor.read(name)
        )
        if changed != _EXPECTED_CHANGED_MEMBERS:
            raise ValueError("wheel delta exceeds the TTL seam")
        before = base.read(_TTL_MEMBER)
        after = successor.read(_TTL_MEMBER)
        if (
            before.count(_OLD_TTL) != 1
            or before.count(_NEW_TTL) != 0
            or after.count(_OLD_TTL) != 0
            or after.count(_NEW_TTL) != 1
            or before.replace(_OLD_TTL, _NEW_TTL) != after
        ):
            raise ValueError("TTL source replacement is not exact")
        return WheelDelta(len(base_names), changed)

def _verify_binding(document: dict[str, object]) -> dict[str, object]:
    if set(document) != {
        "schema",
        "derivation_inputs",
        "candidate_digest",
        "binding_sha256",
    }:
        raise ValueError("product binding fields are invalid")
    raw_inputs = document["derivation_inputs"]
    if not isinstance(raw_inputs, dict) or any(
        not isinstance(key, str) for key in raw_inputs
    ):
        raise ValueError("product derivation inputs are invalid")
    inputs = dict(raw_inputs)
    candidate = _sha256(_canonical(inputs))
    unsigned = {key: value for key, value in document.items() if key != "binding_sha256"}
    if (
        document["schema"] != "task26-preexecution-product-binding-v1"
        or document["candidate_digest"] != candidate
        or document["binding_sha256"] != _sha256(_canonical(unsigned))
    ):
        raise ValueError("product binding digest is invalid")
    return inputs

def verify_product_binding_delta(
    base_binding: Path,
    successor_binding: Path,
) -> str:
    """Require canonical bindings to differ only by the Hermes wheel hash."""
    base = _read_object(base_binding)
    successor = _read_object(successor_binding)
    base_inputs = _verify_binding(base)
    successor_inputs = _verify_binding(successor)
    changed = {
        key
        for key in base_inputs.keys() | successor_inputs.keys()
        if base_inputs.get(key) != successor_inputs.get(key)
    }
    if changed != {"hermes_wheel_sha256"}:
        raise ValueError("product derivation delta exceeds the Hermes wheel")
    candidate = successor["candidate_digest"]
    if not isinstance(candidate, str) or len(candidate) != _DIGEST_LENGTH:
        raise ValueError("successor candidate digest is invalid")
    return candidate

def verify_release_inputs(
    base_root: Path,
    successor_root: Path,
) -> dict[str, object]:
    """Verify product identity and both fresh Golden Path qualifications."""
    base_binding_path = base_root / "preexecution-product-binding.json"
    successor_binding_path = successor_root / "preexecution-product-binding.json"
    candidate = verify_product_binding_delta(
        base_binding_path,
        successor_binding_path,
    )
    binding = _read_object(successor_binding_path)
    inputs = _verify_binding(binding)
    base_wheel = (
        base_root / "artifacts" / "hermes_agent-0.17.0-py3-none-any.whl"
    )
    successor_wheel = (
        successor_root / "artifacts" / "hermes_agent-0.17.0-py3-none-any.whl"
    )
    profile_wheel = (
        successor_root
        / "artifacts"
        / "physique_checkin_cli-0.1.0-py3-none-any.whl"
    )
    if (
        inputs.get("hermes_wheel_sha256")
        != _sha256(successor_wheel.read_bytes())
        or inputs.get("profile_wheel_sha256")
        != _sha256(profile_wheel.read_bytes())
    ):
        raise ValueError("successor wheel hash differs from product binding")
    delta = verify_wheel_delta(base_wheel, successor_wheel)
    statuses: dict[str, str] = {}
    for mode, expected in (
        ("source", "ACTUAL_SOURCE_GOLDEN_PATH_PASS"),
        ("installed", "ACTUAL_INSTALLED_GOLDEN_PATH_PASS"),
    ):
        result = _read_object(successor_root / "receipts" / f"{mode}-result.json")
        verification = _read_object(
            successor_root / "receipts" / f"{mode}-verify.json"
        )
        if (
            result.get("candidate_digest") != candidate
            or result.get("candidate_product_binding_sha256")
            != binding.get("binding_sha256")
            or result.get("hermes_wheel_sha256")
            != inputs.get("hermes_wheel_sha256")
            or result.get("profile_wheel_sha256")
            != inputs.get("profile_wheel_sha256")
            or verification.get("candidate_digest") != candidate
            or verification.get("status") != expected
        ):
            raise ValueError(f"{mode} Golden Path binding is invalid")
        statuses[mode] = expected
    return {
        "candidate_digest": candidate,
        "installed_golden_path": statuses["installed"],
        "source_golden_path": statuses["source"],
        "wheel_member_count": delta.member_count,
    }

def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("base", type=Path)
    parser.add_argument("successor", type=Path)
    args = parser.parse_args()
    result = {
        "status": "DUALCOACH_V11_CANDIDATE_INPUTS_PASS",
        **verify_release_inputs(args.base, args.successor),
    }
    print(json.dumps(result, sort_keys=True, separators=(",", ":")))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())