#!/usr/bin/env python3
"""Owner CLI for transport-free Telegram nutrition-room bootstrap authority."""

from __future__ import annotations

import argparse
import asyncio
import json
import sys
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
from typing import TypeGuard, cast

import yaml

REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

from gateway.platforms.telegram_activation_readiness import (  # noqa: E402
    ActivationReadinessAuthority,
    load_provider_membership_observation,
)
from gateway.platforms.telegram_room_bootstrap_cutover import (  # noqa: E402
    TelegramRoomBootstrapCutover,
)
from gateway.platforms.telegram_pending_marker_intake import (  # noqa: E402
    resolve_pending_marker_identities,
)
from gateway.platforms.telegram_room_bootstrap import (  # noqa: E402
    BootstrapError,
    CustomerDraft,
    RecoveryAttempt,
    RecoverySlot,
    RoomBootstrapSession,
    RoomBootstrapStore,
    load_customer_draft,
    room_bootstrap_state_dir,
)
from gateway.platforms.telegram_room_bootstrap_registration import (  # noqa: E402
    TelegramRoomBootstrapRegistration,
)

def _profile_root(args: argparse.Namespace) -> Path:
    profile = getattr(args, "profile_root", None)
    if profile is None:
        raise ValueError("--profile-root is required")
    root = Path(profile).expanduser()
    if root.is_symlink():
        raise ValueError("profile root symlinks are not allowed")
    resolved = root.resolve(strict=True)
    if not resolved.is_dir():
        raise ValueError("profile root must be a directory")
    return resolved

def _state_dir(args: argparse.Namespace) -> Path:
    explicit = getattr(args, "state_dir", None)
    if explicit is not None:
        return Path(explicit).expanduser()
    return room_bootstrap_state_dir(_profile_root(args))

def _add_authority_location(parser: argparse.ArgumentParser) -> None:
    location = parser.add_mutually_exclusive_group(required=True)
    location.add_argument("--state-dir")
    location.add_argument("--profile-root")

def _is_string_object_dict(value: object) -> TypeGuard[dict[str, object]]:
    if not isinstance(value, dict):
        return False
    mapping = cast(dict[object, object], value)
    return all(isinstance(key, str) for key in mapping)

def _string_object_dict(value: object) -> dict[str, object]:
    if not _is_string_object_dict(value):
        raise ValueError
    return value

def _is_object_list(value: object) -> TypeGuard[list[object]]:
    return isinstance(value, list)

def _profile_telegram_token(profile_root: Path) -> str:
    """Read only the selected profile's Telegram credential into local memory."""
    from agent.secret_scope import build_profile_secret_scope

    token = build_profile_secret_scope(profile_root).get("TELEGRAM_BOT_TOKEN", "").strip()
    if not token:
        raise ValueError("dedicated Telegram credential is unavailable")
    return token

def _rehearsal_marker_cutoff(profile_root: Path) -> datetime | None:
    """Use the latest supported reset receipt as the stale-update boundary."""
    if profile_root.name != "dualcoachtest":
        raise ValueError("pending-marker preparation is restricted to dualcoachtest")
    archives = profile_root / "data" / "rehearsal-reset-archives"
    if not archives.is_dir():
        return None
    receipts = tuple(archives.glob("*/receipt.json"))
    if not receipts:
        return None
    latest = max(receipts, key=lambda path: path.stat().st_mtime_ns)
    return datetime.fromtimestamp(latest.stat().st_mtime, tz=timezone.utc)

def _rehearsal_staff_authority(
    profile_root: Path,
    bot_username: str,
) -> tuple[str, str]:
    try:
        config = _string_object_dict(
            yaml.safe_load((profile_root / "config.yaml").read_text(encoding="utf-8"))
        )
        platforms = _string_object_dict(config["platforms"])
        telegram = _string_object_dict(platforms["telegram"])
        extra = _string_object_dict(telegram["extra"])
        adaptive = _string_object_dict(extra["adaptive_nutrition"])
        operator = _string_object_dict(adaptive["review_operator"])
        dedicated = _string_object_dict(adaptive["separate_bot"])
        owner_id = operator["user_id"]
        chat_id = operator["chat_id"]
        configured_bot = dedicated["bot_username"]
    except (KeyError, OSError, TypeError, ValueError, yaml.YAMLError) as exc:
        raise ValueError("dedicated rehearsal staff authority is unavailable") from exc
    if (
        not isinstance(owner_id, str)
        or not isinstance(chat_id, str)
        or not chat_id.startswith("-")
        or dedicated.get("dedicated") is not True
        or configured_bot != bot_username.removeprefix("@")
    ):
        raise ValueError("dedicated rehearsal staff authority is unavailable")
    return owner_id, chat_id

def _profile_owner_id(profile_root: Path) -> str:
    try:
        payload = _string_object_dict(
            json.loads(
                (profile_root / "customers" / "registry.json").read_text(
                    encoding="utf-8"
                )
            )
        )
        owner = _string_object_dict(payload["owner"])
        user_id = owner["user_id"]
    except (KeyError, OSError, TypeError, ValueError, json.JSONDecodeError) as exc:
        raise ValueError("canonical rehearsal owner is unavailable") from exc
    if not isinstance(user_id, str) or not user_id:
        raise ValueError("canonical rehearsal owner is unavailable")
    return user_id

def _activation_readiness_authority(
    args: argparse.Namespace,
) -> ActivationReadinessAuthority:
    """Bind a provider receipt to canonical identity and consent authority."""
    receipt_path = getattr(args, "provider_membership_receipt", None)
    if not receipt_path:
        raise ValueError("provider membership receipt is required")
    observation = load_provider_membership_observation(receipt_path)
    registry_path = Path(args.profile_root) / "customers" / "registry.json"
    try:
        raw: object = json.loads(registry_path.read_text(encoding="utf-8"))
        payload = _string_object_dict(raw)
        owner = _string_object_dict(payload["owner"])
        customers = payload["customers"]
        if not _is_object_list(customers):
            raise ValueError
        rows = [_string_object_dict(row) for row in customers]
        matches = [
            row
            for row in rows
            if row.get("customer_key") == args.customer_key
        ]
        if len(matches) != 1:
            raise ValueError
        consent = _string_object_dict(matches[0]["ai_processing_consent"])
        recorded_on = consent["recorded_on"]
        notice_version = consent["notice_version"]
        owner_user_id = owner["user_id"]
        owner_chat_id = owner["chat_id"]
        owner_topic_id = owner["topic_id"]
        if not all(
            isinstance(value, str)
            for value in (
                recorded_on,
                notice_version,
                owner_user_id,
                owner_chat_id,
                owner_topic_id,
            )
        ):
            raise ValueError
        consent_recorded_on = date.fromisoformat(str(recorded_on))
        consent_version = str(notice_version)
        owner_user_id = str(owner_user_id)
        owner_chat_id = str(owner_chat_id)
        owner_topic_id = str(owner_topic_id)
    except (KeyError, OSError, TypeError, ValueError, json.JSONDecodeError) as exc:
        raise ValueError(
            "canonical customer consent is unavailable for identity authority"
        ) from exc
    return ActivationReadinessAuthority(
        customer_key=args.customer_key,
        bootstrap_generation=args.generation,
        customer_user_id=args.customer_user_id,
        owner_user_id=owner_user_id,
        owner_chat_id=owner_chat_id,
        owner_topic_id=owner_topic_id,
        consent_notice_version=consent_version,
        consent_recorded_on=consent_recorded_on,
        consent_bootstrap_generation=args.generation,
        provider_observation=observation,
    )

def _session_output(session: RoomBootstrapSession) -> dict[str, object]:
    return {
        "session_id": session.session_id,
        "customer_key": session.customer_key,
        "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(),
        "chat_id": session.chat_id,
        "anchor_message_id": session.anchor_message_id,
        "owner_id": session.owner_id,
        "bot_id": session.bot_id,
        "topics": {slot.value: topic for slot, topic in session.topics},
        "claim_cards": [
            {"role": card.role.value, "message_id": card.message_id}
            for card in session.claim_cards
        ],
        "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
        ],
        "recovery_attempt_generation": session.recovery_attempt_generation,
        "recovery_attempts": [
            _attempt_output(attempt) for attempt in session.recovery_attempts
        ],
        "failure_code": session.failure_code,
    }

def _attempt_output(attempt: RecoveryAttempt) -> dict[str, object]:
    return {
        "session_id": attempt.session_id,
        "slot": attempt.slot.value,
        "chat_id": attempt.chat_id,
        "attempt_generation": attempt.generation,
        "created_at": attempt.created_at.isoformat(),
    }

def _add_recovery_authority(parser: argparse.ArgumentParser) -> None:
    _add_authority_location(parser)
    parser.add_argument("--session-id", required=True)
    parser.add_argument("--slot", required=True, choices=[slot.value for slot in RecoverySlot])
    parser.add_argument("--chat-id", required=True)
    parser.add_argument("--attempt-generation", required=True, type=int)

def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Prepare and inspect durable Telegram nutrition-room bootstrap sessions."
    )
    commands = parser.add_subparsers(dest="command", required=True)

    prepare = commands.add_parser("prepare", help="create an unbound 30-minute bootstrap session")
    _add_authority_location(prepare)
    prepare.add_argument("--draft", required=True)
    prepare.add_argument("--bot-username", required=True)

    pending_prepare = commands.add_parser(
        "prepare-from-pending-markers",
        help="resolve the dedicated pending rehearsal markers and prepare one private-role session",
    )
    pending_prepare.add_argument("--profile-root", required=True)
    pending_prepare.add_argument("--draft", required=True)
    pending_prepare.add_argument("--bot-username", required=True)

    status = commands.add_parser("status", help="show one session or the complete ledger")
    _add_authority_location(status)
    status.add_argument("--session-id")

    cancel = commands.add_parser("cancel", help="cancel a nonterminal session using generation CAS")
    _add_authority_location(cancel)
    cancel.add_argument("--session-id", required=True)
    cancel.add_argument("--generation", required=True, type=int)

    attempts = commands.add_parser(
        "recovery-status", help="list unresolved provider attempts"
    )
    _add_authority_location(attempts)
    attempts.add_argument("--session-id")

    bind = commands.add_parser(
        "recovery-bind", help="bind an existing Telegram topic/message receipt"
    )
    _add_recovery_authority(bind)
    bind.add_argument("--receipt-id", required=True)

    retry = commands.add_parser(
        "recovery-retry",
        help="acknowledge positive no-side-effect evidence and allow retry",
    )
    _add_recovery_authority(retry)

    cutover = commands.add_parser(
        "activate-cutover",
        help="preflight or execute one explicit canonical customer cutover",
    )
    cutover.add_argument("--profile-root", required=True)
    cutover.add_argument("--package-root")
    cutover.add_argument("--session-id", required=True)
    cutover.add_argument("--generation", required=True, type=int)
    cutover.add_argument("--customer-key", required=True)
    cutover.add_argument("--data-root", required=True)
    cutover.add_argument("--g1-g5-checklist", required=True)
    cutover.add_argument("--kst-date", required=True, type=date.fromisoformat)
    cutover.add_argument("--expected-current-customer", required=True)
    cutover.add_argument("--confirm-disable-current-customer", required=True)
    cutover.add_argument("--customer-user-id", required=True)
    cutover.add_argument("--provider-membership-receipt", required=True)
    cutover.add_argument(
        "--execute",
        action="store_true",
        help="commit the cutover; without this flag the command is read-only",
    )
    return parser

def _require_store(store: RoomBootstrapStore | None) -> RoomBootstrapStore:
    if store is None:
        raise RuntimeError("bootstrap store is unavailable")
    return store

def _run(args: argparse.Namespace) -> int:
    store: RoomBootstrapStore | None = None
    output: dict[str, object] = {}
    draft = (
        load_customer_draft(Path(args.draft))
        if args.command in {"prepare", "prepare-from-pending-markers"}
        else None
    )
    if args.command == "prepare":
        if draft is None:  # pragma: no cover - command branch narrows this
            raise RuntimeError("customer draft is unavailable")
        store = RoomBootstrapStore(_state_dir(args))
        prepared = store.prepare(
            draft,
            bot_username=args.bot_username,
        )
        output = {
            **_session_output(prepared.session),
            # The raw SID exists only in this one owner-facing prepare result.
            "sid": prepared.sid,
            "startgroup_link": prepared.startgroup_link,
        }
    elif args.command == "prepare-from-pending-markers":
        if draft is None:  # pragma: no cover - command branch narrows this
            raise RuntimeError("customer draft is unavailable")
        profile_root = _profile_root(args)
        expected_owner_id, expected_staff_chat_id = _rehearsal_staff_authority(
            profile_root,
            args.bot_username,
        )
        resolution = asyncio.run(
            resolve_pending_marker_identities(
                _profile_telegram_token(profile_root),
                bot_username=args.bot_username,
                not_before=_rehearsal_marker_cutoff(profile_root),
                expected_owner_id=expected_owner_id,
                expected_staff_chat_id=expected_staff_chat_id,
            )
        )
        if resolution.owner.user_id != _profile_owner_id(profile_root):
            raise ValueError("pending owner marker is not canonical")
        prepared = RoomBootstrapStore(_state_dir(args)).prepare_rehearsal_customer_invite(
            draft,
            bot_username=args.bot_username,
            owner_id=resolution.owner.user_id,
        )
        output = {
            "session_id": prepared.session.session_id,
            "state": prepared.session.state.value,
            "generation": prepared.session.generation,
            "customer_link": prepared.customer_link,
            "expires_at": prepared.expires_at.isoformat(),
            "pending_marker_intake": "resolved",
        }
    else:
        store = RoomBootstrapStore(_state_dir(args))
    if args.command in {"prepare", "prepare-from-pending-markers"}:
        pass
    elif args.command == "status":
        active_store = _require_store(store)
        active_store.expire_unbound()
        if args.session_id:
            output = _session_output(active_store.get(args.session_id))
        else:
            output = {"sessions": [_session_output(item) for item in active_store.list_sessions()]}
    elif args.command == "cancel":
        active_store = _require_store(store)
        output = _session_output(
            active_store.cancel(args.session_id, expected_generation=args.generation)
        )
    elif args.command == "recovery-status":
        active_store = _require_store(store)
        output = {
            "attempts": [
                _attempt_output(attempt)
                for attempt in active_store.unresolved_attempts(args.session_id)
            ]
        }
    elif args.command == "recovery-bind":
        active_store = _require_store(store)
        output = _session_output(active_store.bind_recovery_receipt(
            args.session_id,
            slot=RecoverySlot(args.slot),
            chat_id=args.chat_id,
            attempt_generation=args.attempt_generation,
            receipt_id=args.receipt_id,
        ))
    elif args.command == "recovery-retry":
        active_store = _require_store(store)
        output = _session_output(active_store.acknowledge_recovery_no_side_effect(
            args.session_id,
            slot=RecoverySlot(args.slot),
            chat_id=args.chat_id,
            attempt_generation=args.attempt_generation,
        ))
    elif args.command == "activate-cutover":
        active_store = _require_store(store)
        cutover = TelegramRoomBootstrapCutover(
            Path(args.profile_root),
            active_store,
            package_root=Path(args.package_root) if args.package_root else None,
        )
        operation = cutover.execute if args.execute else cutover.preflight
        receipt = operation(
            args.session_id,
            expected_generation=args.generation,
            customer_key=args.customer_key,
            data_root=Path(args.data_root),
            checklist_path=Path(args.g1_g5_checklist),
            kst_date=args.kst_date,
            expected_current_customer=args.expected_current_customer,
            confirm_disable_current_customer=args.confirm_disable_current_customer,
            readiness_authority=_activation_readiness_authority(args),
        )
        output = receipt.redacted_dict()
        output["mode"] = "execute" if args.execute else "preflight"
    else:  # pragma: no cover - argparse owns command choices
        raise RuntimeError("unsupported room-bootstrap command")
    print(json.dumps(output, ensure_ascii=False, sort_keys=True, separators=(",", ":")))
    return 0

def main(argv: list[str] | None = None) -> int:
    parser = _parser()
    values = list(sys.argv[1:] if argv is None else argv)
    args = parser.parse_args(values)
    try:
        return _run(args)
    except (BootstrapError, OSError, ValueError) as exc:
        parser.error(str(exc))

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


"""Durable at-most-once customer notice after canonical activation."""

from __future__ import annotations

import fcntl
import hashlib
import json
import os
from contextlib import contextmanager
from dataclasses import asdict, dataclass, replace
from pathlib import Path
from typing import Iterator, Mapping

from gateway.platforms.telegram_nutrition_addresses import (
    AddressConfigurationError,
    parse_customer_private_delivery_address,
)

ACTIVATION_COMPLETION_TEMPLATE = (
    "고객 등록이 완료되었습니다.\n"
    "코칭 시작일: {starts_on}\n"
    "아침 체크인: 평일 {daily_time}\n"
    "시작일의 해당 시간 이후 첫 체크인 안내를 보내드릴게요."
)
_TERMINAL_STATES = frozenset({"sent_audited", "unknown"})

class ActivationNoticeError(ValueError):
    """Activation notice state is malformed, stale, or unsafe."""

@dataclass(frozen=True, slots=True)
class ActivationNoticeReceipt:
    reservation_id: str
    customer_key: str
    starts_on: str
    daily_time: str
    body: str
    destination: dict[str, str]
    authority_digest: str
    state: str
    provider_receipt: str | None = None
    reason: str | None = None
    append_sequence: int = 0
    provider_authority: bool = False

class ActivationNoticeStore:
    """Append-only reservation ledger for one completion notice per authority."""

    def __init__(self, profile_root: Path | str) -> None:
        root = Path(profile_root)
        if root.is_symlink():
            raise ActivationNoticeError("profile root symlinks are not allowed")
        root.mkdir(parents=True, exist_ok=True, mode=0o700)
        self.path = root / "data" / "activation-completion-notices.jsonl"
        self.lock_path = root / "data" / "activation-completion-notices.lock"

    @contextmanager
    def _locked(self) -> Iterator[None]:
        self.path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
        self.path.parent.chmod(0o700)
        if self.path.is_symlink() or self.lock_path.is_symlink():
            raise ActivationNoticeError("activation notice paths are unsafe")
        fd = os.open(self.lock_path, os.O_CREAT | os.O_RDWR, 0o600)
        try:
            os.chmod(self.lock_path, 0o600)
            with os.fdopen(fd, "a+", encoding="utf-8") as lock:
                fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
                yield
                fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
        except Exception:
            raise

    @staticmethod
    def _validate_destination(value: Mapping[str, object]) -> dict[str, str]:
        try:
            destination = parse_customer_private_delivery_address(value)
        except AddressConfigurationError as exc:
            raise ActivationNoticeError(str(exc)) from exc
        return {
            "user_id": str(destination.user_id),
            "chat_id": str(destination.chat_id),
            "topic_id": str(destination.topic_id),
        }

    def reserve(
        self,
        *,
        customer_key: str,
        starts_on: str,
        daily_time: str,
        destination: Mapping[str, object],
        authority_digest: str,
    ) -> ActivationNoticeReceipt:
        destination_pin = self._validate_destination(destination)
        if not customer_key or len(customer_key) > 128:
            raise ActivationNoticeError("activation notice customer is invalid")
        try:
            from datetime import date, time

            date.fromisoformat(starts_on)
            time.fromisoformat(daily_time)
        except (TypeError, ValueError) as exc:
            raise ActivationNoticeError("activation notice schedule is invalid") from exc
        if len(authority_digest) != 64 or any(c not in "0123456789abcdef" for c in authority_digest):
            raise ActivationNoticeError("activation notice authority digest is invalid")
        body = ACTIVATION_COMPLETION_TEMPLATE.format(
            starts_on=starts_on,
            daily_time=daily_time[:5],
        )
        material = json.dumps(
            {
                "customer_key": customer_key,
                "starts_on": starts_on,
                "daily_time": daily_time[:5],
                "destination": destination_pin,
                "authority_digest": authority_digest,
            },
            sort_keys=True,
            separators=(",", ":"),
        )
        reservation_id = hashlib.sha256(material.encode()).hexdigest()
        with self._locked():
            latest = self._latest_unlocked().get(reservation_id)
            if latest is not None:
                return latest
            receipt = ActivationNoticeReceipt(
                reservation_id=reservation_id,
                customer_key=customer_key,
                starts_on=starts_on,
                daily_time=daily_time[:5],
                body=body,
                destination=destination_pin,
                authority_digest=authority_digest,
                state="prepared",
                append_sequence=len(self._rows_unlocked()) + 1,
            )
            self._append_unlocked(receipt)
            return receipt

    def mark_sending(self, receipt: ActivationNoticeReceipt) -> ActivationNoticeReceipt:
        return self._transition(receipt, "prepared", "sending", provider_authority=True)

    def mark_delivered(
        self, receipt: ActivationNoticeReceipt, *, provider_receipt: str
    ) -> ActivationNoticeReceipt:
        if not provider_receipt or len(provider_receipt) > 128:
            raise ActivationNoticeError("activation notice provider receipt is invalid")
        return self._transition(
            receipt,
            "sending",
            "delivered",
            provider_receipt=provider_receipt,
        )

    def mark_audited(self, receipt: ActivationNoticeReceipt) -> ActivationNoticeReceipt:
        return self._transition(receipt, "delivered", "sent_audited")

    def mark_unknown(
        self, receipt: ActivationNoticeReceipt, *, reason: str
    ) -> ActivationNoticeReceipt:
        if receipt.state not in {"prepared", "sending"}:
            raise ActivationNoticeError("activation notice is not pending")
        return self._transition(receipt, receipt.state, "unknown", reason=reason[:128])

    def _transition(
        self,
        receipt: ActivationNoticeReceipt,
        expected: str,
        target: str,
        **changes: object,
    ) -> ActivationNoticeReceipt:
        with self._locked():
            current = self._latest_unlocked().get(receipt.reservation_id)
            if current is None or current.state != expected or current != replace(receipt, provider_authority=False):
                raise ActivationNoticeError(f"activation notice is not {expected}")
            updated = replace(
                current,
                state=target,
                append_sequence=len(self._rows_unlocked()) + 1,
                provider_authority=bool(changes.pop("provider_authority", False)),
                **changes,
            )
            self._append_unlocked(updated)
            return updated

    def receipts(self) -> tuple[ActivationNoticeReceipt, ...]:
        with self._locked():
            return tuple(self._rows_unlocked())

    def latest(self) -> tuple[ActivationNoticeReceipt, ...]:
        with self._locked():
            return tuple(self._latest_unlocked().values())

    def _rows_unlocked(self) -> list[ActivationNoticeReceipt]:
        if not self.path.exists():
            return []
        if self.path.is_symlink() or not self.path.is_file():
            raise ActivationNoticeError("activation notice ledger is unsafe")
        rows: list[ActivationNoticeReceipt] = []
        try:
            for line in self.path.read_text(encoding="utf-8").splitlines():
                raw = json.loads(line)
                if not isinstance(raw, dict):
                    raise ValueError
                rows.append(ActivationNoticeReceipt(**raw))
        except (OSError, TypeError, ValueError, json.JSONDecodeError) as exc:
            raise ActivationNoticeError("activation notice ledger is corrupt") from exc
        return rows

    def _latest_unlocked(self) -> dict[str, ActivationNoticeReceipt]:
        latest: dict[str, ActivationNoticeReceipt] = {}
        for row in self._rows_unlocked():
            latest[row.reservation_id] = row
        return latest

    def _append_unlocked(self, receipt: ActivationNoticeReceipt) -> None:
        payload = asdict(replace(receipt, provider_authority=False))
        with self.path.open("a", encoding="utf-8") as ledger:
            ledger.write(json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n")
            ledger.flush()
            os.fsync(ledger.fileno())
        self.path.chmod(0o600)

__all__ = [
    "ACTIVATION_COMPLETION_TEMPLATE",
    "ActivationNoticeError",
    "ActivationNoticeReceipt",
    "ActivationNoticeStore",
]


[project]
name = "physique-checkin-cli"
version = "0.1.0"
description = "Append-only, local check-in event store for the physique coach."
requires-python = ">=3.12"
dependencies = ["jsonschema>=4.23", "pydantic>=2", "typer>=0.12"]

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[tool.setuptools.package-data]
"checkin_cli.policies" = ["*.json"]

[project.optional-dependencies]
test = ["pytest>=8"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = ["-ra", "--strict-config", "--strict-markers"]


/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py-179-    *,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py-180-    customer_key: str,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py:181:    activation_receipt_digest: str,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py-182-    registry_projection_digest: str,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py-183-    owner_digest: str,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py-184-) -> bool:
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py:185:    expected = build_legacy_activation_authority(
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py-186-        customer_key=customer_key,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py:187:        activation_receipt_digest=activation_receipt_digest,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py-188-        registry_projection_digest=registry_projection_digest,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_projection.py-189-        owner_digest=owner_digest,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py-12-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py-13-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py:14:MODULE = "checkin_cli.nutrition_onboarding_models"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py-15-assert importlib.util.find_spec(MODULE) is not None, (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py-16-    "nutrition onboarding contract missing: "
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py:17:    "checkin_cli.nutrition_onboarding_models"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py-18-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py-19-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py:20:from checkin_cli.nutrition_onboarding_models import (  # noqa: E402
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py-21-    ActivityCategory,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_nutrition_onboarding_models.py-22-    EquationSexBasis,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py:1:"""Local, append-only check-in event store."""
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py:2:from checkin_cli.customer_coaching import CustomerRegistry, load_customer_registry
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py:3:from checkin_cli.customer_grounding import build_customer_grounded_context
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py:4:from checkin_cli.customer_reporting import CustomerPeriodReport, build_customer_period_report
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py:5:from checkin_cli.customer_schedule import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-6-    CustomerScheduleTask,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-7-    build_due_customer_tasks,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-19-    schedule_delivery_ledger,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-20-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py:21:from checkin_cli.customer_admin import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-22-    GateDPreflightReceipt,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-23-    audit_gate_d_preflight,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-25-    validate_adaptive_registration_reapproval,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-26-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py:27:from checkin_cli.diagnostic_isolation import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-28-    DiagnosticAuditPendingNoSend,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-29-    DiagnosticDeliveryAuthority,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-37-    VerifiedDiagnosticReservation,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-38-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py:39:from checkin_cli.diagnostic_evidence import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-40-    DiagnosticEvidenceSource,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/__init__.py-41-    DiagnosticSourceDescriptor,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-13-import pytest
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-14-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:15:import checkin_cli.customer_admin as customer_admin_module
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:16:from checkin_cli import adaptive_nutrition as adaptive_module
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:17:from checkin_cli import customer_schedule as schedule_module
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:18:from checkin_cli import diagnostic_evidence as evidence_module
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:19:from checkin_cli.adaptive_nutrition import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-20-    AdaptiveEventStore,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-21-    AdaptiveOverlay,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-23-    OverlayJournal,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-24-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:25:from checkin_cli.customer_admin import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-26-    CustomerDraft,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-27-    register_customer,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-28-    set_customer_ai_consent,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-29-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:30:from checkin_cli.customer_coaching import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-31-    AiProcessingConsent,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-32-    CONSENT_VERSION,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-34-    load_customer_registry,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-35-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:36:from checkin_cli.customer_schedule import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-37-    CustomerScheduleTask,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-38-    initialize_schedule_delivery_fence,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-39-    reserve_customer_task_delivery,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-40-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:41:from checkin_cli.diagnostic_evidence import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-42-    DiagnosticEvidenceError,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-43-    DiagnosticEvidenceSource,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-49-    synthetic_replay_fixture,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-50-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:51:from checkin_cli.store import CanonicalEventTransaction
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:52:from checkin_cli.wizard_models import WizardFlow, WizardSession
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:53:from checkin_cli.wizard_storage import WizardStorage
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-54-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-55-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-170-    return {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-171-        "event_id": f"event-{index:04d}",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:172:        "event_type": "morning_checkin",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-173-        "occurred_at_kst": f"2026-08-{index + 2:02d}T09:00:00+09:00",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-174-        "recorded_at_kst": f"2026-08-{index + 2:02d}T09:00:00+09:00",
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-180-        "status": "accepted",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-181-        "dedupe_key": f"diagnostic-event-{index}",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:182:        "check_in": {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-183-            "body_weight_kg": 80,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-184-            "calories_kcal": 2300,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-185-            "sleep_hours": 8,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:186:            "sleep_quality_1to5": 4,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-187-            "readiness_1to5": 4,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-188-            "pain_summary": "없음",
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-628-    profile = tmp_path / "profile"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-629-    hermes = tmp_path / "hermes"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:630:    (profile / "checkin_cli").mkdir(parents=True)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-631-    (profile / "tests").mkdir()
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-632-    (hermes / "gateway" / "platforms").mkdir(parents=True)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-633-    (hermes / "tests" / "gateway").mkdir(parents=True)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-634-    (profile / "pyproject.toml").write_text("[project]\n")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:635:    (profile / "checkin_cli" / "__init__.py").write_text("")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-636-    (hermes / "gateway" / "__init__.py").write_text("")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-637-    (hermes / "gateway" / "platforms" / "nutrition_coaching.py").write_text("")
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1048-def test_promotion_manifest_rejects_unlisted_field(tmp_path):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1049-    profile, hermes = _promotion_roots(tmp_path)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:1050:    candidate = profile / "checkin_cli" / "diagnostic.py"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1051-    candidate.write_text("SCHEMA = 'diagnostic'\n")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:1052:    entry = _entry(profile, "profile", "checkin_cli/diagnostic.py")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1053-    entry["runtime_data"] = False
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1054-    with pytest.raises(DiagnosticEvidenceError, match="closed"):
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1069-        build_diagnostic_promotion_manifest([traversal], approved_roots={"profile": profile, "hermes": hermes})
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1070-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:1071:    link = profile / "checkin_cli" / "linked.py"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1072-    link.symlink_to(outside)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1073-    with pytest.raises(DiagnosticEvidenceError):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1074-        build_diagnostic_promotion_manifest(
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:1075:            [_entry(profile, "profile", "checkin_cli/linked.py")],
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1076-            approved_roots={"profile": profile, "hermes": hermes},
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1077-        )
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1080-def test_promotion_manifest_rejects_bad_digest_and_suffix(tmp_path):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1081-    profile, hermes = _promotion_roots(tmp_path)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:1082:    candidate = profile / "checkin_cli" / "diagnostic.py"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1083-    candidate.write_text("SCHEMA = 'diagnostic'\n")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:1084:    bad = _entry(profile, "profile", "checkin_cli/diagnostic.py")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1085-    bad["sha256"] = "0" * 64
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1086-    with pytest.raises(DiagnosticEvidenceError, match="digest"):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1087-        build_diagnostic_promotion_manifest([bad], approved_roots={"profile": profile, "hermes": hermes})
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1088-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:1089:    config = profile / "checkin_cli" / "runtime.json"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1090-    config.write_text("{}\n")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1091-    with pytest.raises(DiagnosticEvidenceError, match="suffix"):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1092-        build_diagnostic_promotion_manifest(
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py:1093:            [_entry(profile, "profile", "checkin_cli/runtime.json")],
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1094-            approved_roots={"profile": profile, "hermes": hermes},
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_diagnostic_evidence.py-1095-        )
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_io.py-10-from typing import cast
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_io.py-11-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_io.py:12:from checkin_cli.nutrition_readiness_contract import DIGEST_PATTERN
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_io.py-13-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_io.py-14-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-34-    RAW_PAYLOAD_ARCHIVED = "raw_payload_archived"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-35-    HISTORY_IMPORTED = "history_imported"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:36:    CHECK_IN_RECEIVED = "check_in_received"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:37:    CHECK_IN_VALIDATED = "check_in_validated"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-38-    CORRECTION = "correction"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-39-    SAFETY_FLAG = "safety_flag"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-40-    COACHING_RESPONSE = "coaching_response"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-41-    DECISION = "decision"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:42:    MORNING_CHECKIN = "morning_checkin"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:43:    NUTRITION_CHECKIN = "nutrition_checkin"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-44-    WORKOUT_RECORD = "workout_record"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-45-    SAFETY_AUDIT = "safety_audit"
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-99-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-100-class SafetySourceFlow(StrEnum):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:101:    CUSTOMER_CHECKIN = "customer_checkin"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-102-    TRAINER_SESSION = "trainer_session"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-103-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-121-    BODY_WEIGHT_KG = "body_weight_kg"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-122-    SLEEP_HOURS = "sleep_hours"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:123:    SLEEP_QUALITY_1TO5 = "sleep_quality_1to5"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-124-    READINESS_1TO5 = "readiness_1to5"
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-125-    TRAINING_PLAN = "training_plan"
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-184-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-185-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:186:class CheckinValues(BaseModel):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-187-    """Validated values used by the local current-state view."""
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-188-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-264-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-265-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:266:class ContractCheckin(BaseModel):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-267-    """Schema-named representation of live or partial historical measurements."""
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-268-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-275-    fat_g: int | None = Field(default=None, ge=0, le=1_000)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-276-    sleep_hours: float | None = Field(default=None, ge=0, le=24)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:277:    sleep_quality_1to5: int | None = Field(default=None, ge=1, le=5)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-278-    readiness_1to5: int | None = Field(default=None, ge=1, le=5)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-279-    training_summary: str | None = Field(default=None, max_length=4000)
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-876-    supersedes: str | None = Field(default=None, pattern=r"^[a-z0-9][a-z0-9_-]{7,127}$")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-877-    dedupe_key: str = Field(min_length=16, max_length=256)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:878:    check_in: ContractCheckin | None = None
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-879-    safety: Safety | None = None
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-880-    trainer_session: TrainerSessionPayload | None = None
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1068-    except ValidationError as exc:
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1069-        raise ValueError("event does not conform to canonical schema: " + str(exc)) from exc
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:1070:    schema_path = Path("/home/cube/.hermes/profiles/physique-coach/contracts/checkin-event.schema.json")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1071-    schema = json.loads(schema_path.read_text(encoding="utf-8"))
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1072-    validator_type, format_checker = _canonical_validator()
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1079-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1080-class CurrentView(BaseModel):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:1081:    """Rebuildable current state containing only eligible check-ins."""
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1082-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1083-    model_config = _STRICT_FROZEN
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1084-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:1085:    eligible_checkins: tuple[CheckinValues, ...]
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1086-    safety_flags: tuple[str, ...]
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1087-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1198-    if isinstance(target_map.get("target"), Mapping):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1199-        target_map = target_map["target"]
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:1200:    elif isinstance(target_map.get("check_in_target"), Mapping):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:1201:        target_map = target_map["check_in_target"]
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1202-    if isinstance(actual_map.get("actual"), Mapping):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1203-        actual_map = actual_map["actual"]
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:1204:    elif isinstance(actual_map.get("check_in"), Mapping):
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py:1205:        actual_map = actual_map["check_in"]
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1206-    aliases = {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/models.py-1207-        "calories": ("calories_kcal", "calories", "target_calories_kcal", "actual_calories_kcal"),
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_reconciliation.py-8-from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_reconciliation.py-9-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_reconciliation.py:10:from checkin_cli.nutrition_onboarding_contract import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_reconciliation.py-11-    QUESTION_FIELDS,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_reconciliation.py-12-    canonical_digest,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py-5-from pathlib import Path
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py-6-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py:7:from checkin_cli.nutrition_onboarding_authority import validate_authority, validate_route
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py:8:from checkin_cli.nutrition_onboarding_contract import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py-9-    QUESTION_FIELDS,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py-10-    MessageEvidence,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py-13-    canonical_digest,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py-14-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py:15:from checkin_cli.nutrition_onboarding_fs import atomic_write_private_json, read_private_json
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py:16:from checkin_cli.nutrition_onboarding_models import OnboardingState
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py-17-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_session.py-18-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-11-from typing import Final, Literal
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-12-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:13:from checkin_cli.history_baseline import HistoryBaselineParser, SourceInventory
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:14:from checkin_cli.history_models import Baseline, BaselineEntry
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:15:from checkin_cli.models import ContractCheckin, ContractStatus, Event, EventType, ImportManifest, Provenance
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-16-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-17-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-62-        {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-63-            "date": event.occurred_at_kst[:10],
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:64:            "weight_kg": event.check_in.body_weight_kg,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:65:            "sleep_hours": event.check_in.sleep_hours,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:66:            "calories_kcal": event.check_in.calories_kcal,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-67-        }
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-68-        for event in events
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:69:        if event.event_type is EventType.HISTORY_IMPORTED and event.check_in is not None
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-70-    )
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-71-    return json.dumps(
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-93-    schema_version: Literal["1.0", "2.0"] = "1.0",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-94-) -> Event:
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:95:    check_in = ContractCheckin(
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-96-        body_weight_kg=entry.weight_kg,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-97-        sleep_hours=entry.sleep_hours,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-101-        pain_summary=",".join(entry.pain_tokens) or None,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-102-    )
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:103:    payload = check_in.model_dump(exclude_none=True)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-104-    event_identity = entry.source_sha256 + entry.section_anchor + json.dumps(payload, sort_keys=True)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-105-    if schema_version == "2.0":
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-114-        provenance=Provenance(source_type="historical_markdown", source_ref=entry.source_path, content_sha256=entry.source_sha256, section_anchor=entry.section_anchor),
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-115-        status=ContractStatus.ARCHIVED,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py:116:        check_in=check_in,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-117-        import_manifest=(
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/history_imports.py-118-            ImportManifest(observation_kst_day=entry.recorded_date)
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-8-from typing import cast
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-9-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py:10:from checkin_cli.nutrition_readiness_contract import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-11-    BASELINE_FIELDS,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-12-    BASELINE_FIELDS_V2,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-18-    RECONCILIATION_FIELDS,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-19-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py:20:from checkin_cli.nutrition_readiness_io import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-21-    number_in_range,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-22-    positive_number,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-260-        if document.get("delivery_enabled") is not False:
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-261-            reasons.add("delivery_enabled")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py:262:        if document.get("activation_enabled") is not False:
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-263-            reasons.add("feature_flags_enabled")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_readiness_validators.py-264-        try:
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py-5-from pathlib import Path
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py-6-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py:7:from checkin_cli.customer_coaching import load_customer_registry
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py:8:from checkin_cli.nutrition_onboarding_contract import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py-9-    MessageEvidence,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py-10-    OnboardingAuthority,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py-11-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py:12:from checkin_cli.nutrition_onboarding_models import NutritionOnboardingBaseline
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py-13-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/nutrition_onboarding_authority.py-14-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-17-from typing import Final
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-18-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py:19:from .knowledge_retrieval import retrieve_for_checkin
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-20-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-21-_DOCTRINE_LINE: Final[re.Pattern[str]] = re.compile(r"^\d+\.\s+\*\*(.+?)\*\*\s*(.*)$")
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-44-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-45-_PLAYBOOKS: Final[dict[str, tuple[str, str]]] = {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py:46:    "daily_checkin_v1": ("daily", "1"),
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-47-    "weekly_report_v1": ("weekly", "1"),
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-48-    "adaptive_nutrition_v1": ("adaptive_operator", "1"),
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-69-}
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-70-_SURFACE_CLUSTERS: Final[dict[str, tuple[str, ...]]] = {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py:71:    "daily": ("daily-checkin",),
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-72-    "weekly": ("weekly-summary",),
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-73-    "adaptive_operator": ("adaptive-proposal",),
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-156-    if selected_playbook is None:
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-157-        selected_playbook = {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py:158:            "daily": "daily_checkin_v1",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-159-            "weekly": "weekly_report_v1",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-160-            "adaptive_operator": "adaptive_nutrition_v1",
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-173-    available = _available_principles(doctrine_root)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-174-    preferred = {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py:175:        "daily_checkin_v1": ("nutrition_01", "nutrition_03", "choi_06", "nutrition_10"),
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-176-        "weekly_report_v1": ("nutrition_01", "nutrition_03", "choi_06", "nutrition_10"),
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-177-        "adaptive_nutrition_v1": ("nutrition_01", "nutrition_03", "choi_06", "choi_07"),
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-223-    source_facts = current.get("answers", {}) if isinstance(current.get("answers"), dict) else {}
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-224-    query = {str(key): str(value) for key, value in source_facts.items() if isinstance(value, str)}
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py:225:    retrieved = _compat_retrieved(retrieve_for_checkin(profile_root, query))
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-226-    try:
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-227-        grounding = export_coaching_grounding(
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-258-    question_text = question[:2_000] if isinstance(question, str) else ""
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-259-    retrieved = _compat_retrieved(
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py:260:        retrieve_for_checkin(profile_root, {"question": question_text})
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-261-    )
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-262-    content = _user_content(doctrine, historical, current) + _retrieved_content(retrieved)
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-492-    answers = snapshot.get("answers")
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-493-    allowed = {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py:494:        "bodyweight", "sleep_duration", "sleep_quality", "condition", "pain", "digestion",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-495-        "calories", "training_plan", "completion", "training_summary", "workout_quality",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/coaching_grounding.py-496-        "macros", "performance", "intensity",
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard_storage.py-13-from dataclasses import dataclass
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard_storage.py-14-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard_storage.py:15:from checkin_cli.wizard_models import WizardFlow, WizardSession
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard_storage.py-16-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard_storage.py-17-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:1:"""Owner/topic-bound personal and customer check-in wizard domain."""
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-2-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-3-from __future__ import annotations
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-12-from zoneinfo import ZoneInfo
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-13-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:14:from checkin_cli.customer_coaching import CustomerRuntime, RegisteredCustomerBinding
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:15:from checkin_cli.models import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:16:    ContractCheckin,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-17-    ContractStatus,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-18-    Event,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-24-    build_trainer_session_event,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-25-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:26:from checkin_cli.store import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-27-    CanonicalEventTransaction,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-28-    EventStore,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-29-    _reject_registered_persistence_root,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-30-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:31:from checkin_cli.wizard_models import (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-32-    ADAPTIVE_FOLLOW_UP_IDS,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-33-    WizardBranch,
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-38-    WizardStatus,
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-39-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:40:from checkin_cli.wizard_storage import WizardStorage
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-41-
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-42-
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-94-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-95-MORNING_STEPS: Final[tuple[str, ...]] = (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:96:    "bodyweight", "sleep_duration", "sleep_quality", "condition", "pain",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-97-    "calories", "training_plan", "optional_note", "summary",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-98-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-99-REGISTERED_MORNING_STEPS: Final[tuple[str, ...]] = (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:100:    "bodyweight", "sleep_duration", "sleep_quality", "condition", "pain",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-101-    "calories", "training_plan", "weight_change_percent", "fatigue",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-102-    "exercise_feasibility", "meal_deviation", "optional_note", "summary",
--
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-173-}
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-174-NUTRITION_STEPS: Final[tuple[str, ...]] = (
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:175:    "bodyweight", "calories", "macros", "meals", "water", "sleep_duration", "sleep_quality",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-176-    "digestion", "condition", "appetite_stress", "training_summary", "optional_note", "summary",
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-177-)
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-178-ADAPTIVE_MAXIMUM: Final[int] = 2000
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py-179-ADAPTIVE_TRIGGER_STEPS: Final[dict[WizardFlow, frozenset[str]]] = {
/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli/wizard.py:180:    WizardFlow.MORNING: frozenset({"sleep_quality", "condition"}),

[Showing lines 638-1058 of 1058 (50.0KB limit). Full output: /tmp/pi-bash-ec9d7d9fe9874a3f.log]