--
tests/gateway/test_telegram_physique_checkin.py-557-        assert reply.prompt.buttons == ()
tests/gateway/test_telegram_physique_checkin.py-558-
tests/gateway/test_telegram_physique_checkin.py-559-    def test_fresh_bridge_rehydrates_persisted_active_draft_and_consumes_typed_value(self, tmp_path):
tests/gateway/test_telegram_physique_checkin.py:560:        profile_source = "/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli"
tests/gateway/test_telegram_physique_checkin.py-561-        if profile_source not in sys.path:
tests/gateway/test_telegram_physique_checkin.py-562-            sys.path.insert(0, profile_source)
tests/gateway/test_telegram_physique_checkin.py-563-        from checkin_cli.wizard import WizardService
--
tests/gateway/test_telegram_physique_checkin.py-582-        self,
tests/gateway/test_telegram_physique_checkin.py-583-        tmp_path,
tests/gateway/test_telegram_physique_checkin.py-584-    ):
tests/gateway/test_telegram_physique_checkin.py:585:        profile_source = "/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli"
tests/gateway/test_telegram_physique_checkin.py-586-        if profile_source not in sys.path:
tests/gateway/test_telegram_physique_checkin.py-587-            sys.path.insert(0, profile_source)
tests/gateway/test_telegram_physique_checkin.py-588-        from checkin_cli.wizard import WizardService
--
tests/gateway/test_telegram_physique_checkin.py-594-            service=WizardService.for_standalone(state_home),
tests/gateway/test_telegram_physique_checkin.py-595-            binding_path=binding_path,
tests/gateway/test_telegram_physique_checkin.py-596-        )
tests/gateway/test_telegram_physique_checkin.py:597:        launch = first.open_launcher("nutrition_daily", message_id="44")
tests/gateway/test_telegram_physique_checkin.py-598-        assert launch.callback_data is not None
tests/gateway/test_telegram_physique_checkin.py-599-        started = first.handle_callback(
tests/gateway/test_telegram_physique_checkin.py-600-            launch.callback_data,
--
tests/gateway/test_telegram_physique_checkin.py-651-            binding_path=binding_path,
tests/gateway/test_telegram_physique_checkin.py-652-        )
tests/gateway/test_telegram_physique_checkin.py-653-        resume_launch = restarted.open_launcher(
tests/gateway/test_telegram_physique_checkin.py:654:            "nutrition_daily",
tests/gateway/test_telegram_physique_checkin.py-655-            message_id="44",
tests/gateway/test_telegram_physique_checkin.py-656-        )
tests/gateway/test_telegram_physique_checkin.py-657-        assert resume_launch.callback_data is not None
--
tests/gateway/test_telegram_physique_checkin.py-670-        self,
tests/gateway/test_telegram_physique_checkin.py-671-        tmp_path,
tests/gateway/test_telegram_physique_checkin.py-672-    ):
tests/gateway/test_telegram_physique_checkin.py:673:        profile_source = "/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli"
tests/gateway/test_telegram_physique_checkin.py-674-        if profile_source not in sys.path:
tests/gateway/test_telegram_physique_checkin.py-675-            sys.path.insert(0, profile_source)
tests/gateway/test_telegram_physique_checkin.py-676-        from checkin_cli.wizard import WizardService
--
tests/gateway/test_telegram_physique_checkin.py-680-            service=WizardService.for_standalone(tmp_path / "wizard-state"),
tests/gateway/test_telegram_physique_checkin.py-681-            binding_path=tmp_path / "bindings.json",
tests/gateway/test_telegram_physique_checkin.py-682-        )
tests/gateway/test_telegram_physique_checkin.py:683:        launch = bridge.open_launcher("nutrition_daily", message_id="44")
tests/gateway/test_telegram_physique_checkin.py-684-        assert launch.callback_data is not None
tests/gateway/test_telegram_physique_checkin.py-685-        reply = bridge.handle_callback(
tests/gateway/test_telegram_physique_checkin.py-686-            launch.callback_data,
--
tests/gateway/test_telegram_physique_checkin.py-710-        self,
tests/gateway/test_telegram_physique_checkin.py-711-        tmp_path,
tests/gateway/test_telegram_physique_checkin.py-712-    ):
tests/gateway/test_telegram_physique_checkin.py:713:        profile_source = "/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli"
tests/gateway/test_telegram_physique_checkin.py-714-        if profile_source not in sys.path:
tests/gateway/test_telegram_physique_checkin.py-715-            sys.path.insert(0, profile_source)
tests/gateway/test_telegram_physique_checkin.py-716-        from checkin_cli.wizard import WizardService
--
tests/gateway/test_telegram_physique_checkin.py-720-            service=WizardService.for_standalone(tmp_path / "wizard-state"),
tests/gateway/test_telegram_physique_checkin.py-721-            binding_path=tmp_path / "bindings.json",
tests/gateway/test_telegram_physique_checkin.py-722-        )
tests/gateway/test_telegram_physique_checkin.py:723:        launch = bridge.open_launcher("nutrition_daily", message_id="44")
tests/gateway/test_telegram_physique_checkin.py-724-        assert launch.callback_data is not None
tests/gateway/test_telegram_physique_checkin.py-725-        reply = bridge.handle_callback(
tests/gateway/test_telegram_physique_checkin.py-726-            launch.callback_data,
--
tests/gateway/test_telegram_physique_checkin.py-762-            "optional_note": ("a0", "a1"),
tests/gateway/test_telegram_physique_checkin.py-763-            "summary": ("a0", "a1", "a2"),
tests/gateway/test_telegram_physique_checkin.py-764-        }
tests/gateway/test_telegram_physique_checkin.py:765:        assert bridge._flow_steps("nutrition_daily") == tuple(expected_choice_actions)
tests/gateway/test_telegram_physique_checkin.py-766-        visited_steps = []
tests/gateway/test_telegram_physique_checkin.py-767-
tests/gateway/test_telegram_physique_checkin.py-768-        for _ in range(30):
--
tests/gateway/test_telegram_physique_checkin.py-858-
tests/gateway/test_telegram_physique_checkin.py-859-
tests/gateway/test_telegram_physique_checkin.py-860-    def test_registered_morning_bridge_round_trips_every_extended_step(self, tmp_path):
tests/gateway/test_telegram_physique_checkin.py:861:        profile_source = "/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli"
tests/gateway/test_telegram_physique_checkin.py-862-        if profile_source not in sys.path:
tests/gateway/test_telegram_physique_checkin.py-863-            sys.path.insert(0, profile_source)
tests/gateway/test_telegram_physique_checkin.py-864-        from checkin_cli.wizard import WizardService
--
tests/gateway/test_telegram_physique_checkin.py-952-        assert bridge._bindings[session_id].step == "optional_note"
tests/gateway/test_telegram_physique_checkin.py-953-
tests/gateway/test_telegram_physique_checkin.py-954-    def test_trainer_bridge_start_to_save_scopes_event_to_registered_customer(self, tmp_path):
tests/gateway/test_telegram_physique_checkin.py:955:        profile_source = "/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli"
tests/gateway/test_telegram_physique_checkin.py-956-        if profile_source not in sys.path:
tests/gateway/test_telegram_physique_checkin.py-957-            sys.path.insert(0, profile_source)
tests/gateway/test_telegram_physique_checkin.py-958-        from checkin_cli.wizard import WizardService
--
tests/gateway/test_telegram_physique_checkin.py-2387-        adapter.send_inline_card = AsyncMock()
tests/gateway/test_telegram_physique_checkin.py-2388-        adapter._enqueue_text_event = MagicMock()
tests/gateway/test_telegram_physique_checkin.py-2389-        message = SimpleNamespace(
tests/gateway/test_telegram_physique_checkin.py:2390:            text="체크인 시작", chat=SimpleNamespace(id="chat-1", type="supergroup", is_forum=True),
tests/gateway/test_telegram_physique_checkin.py-2391-            from_user=SimpleNamespace(id="owner-1"), message_thread_id="topic-1", is_topic_message=True,
tests/gateway/test_telegram_physique_checkin.py-2392-        )
tests/gateway/test_telegram_physique_checkin.py-2393-
--
tests/gateway/test_telegram_physique_checkin.py-2406-        adapter.send_inline_card = AsyncMock()
tests/gateway/test_telegram_physique_checkin.py-2407-        adapter._enqueue_text_event = MagicMock()
tests/gateway/test_telegram_physique_checkin.py-2408-        message = SimpleNamespace(
tests/gateway/test_telegram_physique_checkin.py:2409:            text="체크인 시작", chat=SimpleNamespace(id="chat-1", type="supergroup", is_forum=True),
tests/gateway/test_telegram_physique_checkin.py-2410-            from_user=SimpleNamespace(id="other"), message_thread_id="wrong-topic", is_topic_message=True,
tests/gateway/test_telegram_physique_checkin.py-2411-        )
tests/gateway/test_telegram_physique_checkin.py-2412-
--
tests/gateway/test_telegram_physique_checkin.py-2436-        adapter.send_inline_card = AsyncMock()
tests/gateway/test_telegram_physique_checkin.py-2437-        adapter._enqueue_text_event = MagicMock()
tests/gateway/test_telegram_physique_checkin.py-2438-        message = SimpleNamespace(
tests/gateway/test_telegram_physique_checkin.py:2439:            text="체크인 시작", chat=SimpleNamespace(id="chat-1", type="supergroup", is_forum=True),
tests/gateway/test_telegram_physique_checkin.py-2440-            from_user=SimpleNamespace(id="owner-1"), message_thread_id="topic-1", is_topic_message=True,
tests/gateway/test_telegram_physique_checkin.py-2441-        )
tests/gateway/test_telegram_physique_checkin.py-2442-
--
tests/gateway/test_telegram_physique_checkin.py-2480-    @pytest.mark.asyncio
tests/gateway/test_telegram_physique_checkin.py-2481-    async def test_save_callback_records_once_and_replaces_summary_with_confirmation(self, tmp_path):
tests/gateway/test_telegram_physique_checkin.py-2482-        """A real finalized wizard must visibly confirm instead of re-rendering Save."""
tests/gateway/test_telegram_physique_checkin.py:2483:        profile_source = "/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli"
tests/gateway/test_telegram_physique_checkin.py-2484-        if profile_source not in sys.path:
tests/gateway/test_telegram_physique_checkin.py-2485-            sys.path.insert(0, profile_source)
tests/gateway/test_telegram_physique_checkin.py-2486-        from checkin_cli.wizard import WizardService
--
tests/gateway/test_telegram_physique_checkin.py-2542-    @pytest.mark.asyncio
tests/gateway/test_telegram_physique_checkin.py-2543-    async def test_saved_checkin_adds_profile_gated_model_feedback_without_generic_ingress(self, tmp_path):
tests/gateway/test_telegram_physique_checkin.py-2544-        """Only an accepted finalized private check-in may ask the coach model."""
tests/gateway/test_telegram_physique_checkin.py:2545:        profile_source = "/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli"
tests/gateway/test_telegram_physique_checkin.py-2546-        if profile_source not in sys.path:
tests/gateway/test_telegram_physique_checkin.py-2547-            sys.path.insert(0, profile_source)
tests/gateway/test_telegram_physique_checkin.py-2548-        from checkin_cli.wizard import WizardService
--
tests/gateway/test_telegram_physique_checkin.py-2585-        adapter._humanize_korean_copy.assert_not_awaited()
tests/gateway/test_telegram_physique_checkin.py-2586-        rendered = query.edit_message_text.call_args.kwargs["text"]
tests/gateway/test_telegram_physique_checkin.py-2587-        assert "오늘 체크인 완료" in rendered
tests/gateway/test_telegram_physique_checkin.py:2588:        assert rendered == adapter._nutrition_daily_text(
tests/gateway/test_telegram_physique_checkin.py-2589-            bridge.finalized_coaching_snapshot(session_id)
tests/gateway/test_telegram_physique_checkin.py-2590-        )
tests/gateway/test_telegram_physique_checkin.py-2591-        assert query.edit_message_text.call_args.kwargs["reply_markup"] is None
--
tests/gateway/test_telegram_physique_checkin.py-2678-            "체중 증가 속도도 현재 린매스업 목표 범위 안에 있습니다."
tests/gateway/test_telegram_physique_checkin.py-2679-        )
tests/gateway/test_telegram_physique_checkin.py-2680-
tests/gateway/test_telegram_physique_checkin.py:2681:        assert TelegramAdapter._nutrition_daily_text(snapshot, feedback) == (
tests/gateway/test_telegram_physique_checkin.py-2682-            "오늘 체크인 완료\n\n"
tests/gateway/test_telegram_physique_checkin.py-2683-            "- 체중: 78.6kg\n"
tests/gateway/test_telegram_physique_checkin.py-2684-            "- 섭취: 2,675kcal\n"
--
tests/gateway/test_telegram_physique_checkin.py-2696-        )
tests/gateway/test_telegram_physique_checkin.py-2697-
tests/gateway/test_telegram_physique_checkin.py-2698-    def test_daily_copy_fails_closed_when_facts_or_model_are_missing(self):
tests/gateway/test_telegram_physique_checkin.py:2699:        assert TelegramAdapter._nutrition_daily_text({"answers": {}}) == (
tests/gateway/test_telegram_physique_checkin.py-2700-            "오늘 체크인 완료\n\n"
tests/gateway/test_telegram_physique_checkin.py-2701-            "- 체중: 기록 없음\n"
tests/gateway/test_telegram_physique_checkin.py-2702-            "- 섭취: 기록 없음\n"
--
tests/gateway/test_telegram_physique_checkin.py-2764-
tests/gateway/test_telegram_physique_checkin.py-2765-
tests/gateway/test_telegram_physique_checkin.py-2766-def _schedule_checkin_cli():
tests/gateway/test_telegram_physique_checkin.py:2767:    profile_source = "/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli"
tests/gateway/test_telegram_physique_checkin.py-2768-    if profile_source not in sys.path:
tests/gateway/test_telegram_physique_checkin.py-2769-        sys.path.insert(0, profile_source)
tests/gateway/test_telegram_physique_checkin.py-2770-    import checkin_cli
--
tests/gateway/test_telegram_physique_checkin.py-3344-        calls = provider.await_args_list
tests/gateway/test_telegram_physique_checkin.py-3345-        assert calls[0].kwargs["chat_id"] == "customer-chat"
tests/gateway/test_telegram_physique_checkin.py-3346-        assert calls[0].kwargs["message_thread_id"] == "customer-topic"
tests/gateway/test_telegram_physique_checkin.py:3347:        assert "아래 ‘오늘 체크인 시작’을 누르면 첫 질문이 열립니다." in calls[0].kwargs["text"]
tests/gateway/test_telegram_physique_checkin.py-3348-        assert calls[1].kwargs["chat_id"] == "owner-chat"
tests/gateway/test_telegram_physique_checkin.py-3349-        assert calls[1].kwargs["message_thread_id"] == "owner-topic"
tests/gateway/test_telegram_physique_checkin.py-3350-        assert calls[1].kwargs["text"].startswith("코치 검토용 피드백 초안")
--
tests/gateway/test_telegram_physique_checkin.py-3760-        bridge.finalized_coaching_snapshot.assert_called_once()
tests/gateway/test_telegram_physique_checkin.py-3761-        query.edit_message_text.assert_awaited_once()
tests/gateway/test_telegram_physique_checkin.py-3762-        assert query.edit_message_text.await_args.kwargs["text"] == (
tests/gateway/test_telegram_physique_checkin.py:3763:            adapter._nutrition_daily_text(snapshot)
tests/gateway/test_telegram_physique_checkin.py-3764-        )
tests/gateway/test_telegram_physique_checkin.py-3765-
tests/gateway/test_telegram_physique_checkin.py-3766-    @pytest.mark.asyncio
--
tests/gateway/test_telegram_physique_checkin.py-3805-        adapter._request_physique_coaching_stage.assert_not_called()
tests/gateway/test_telegram_physique_checkin.py-3806-        query.edit_message_text.assert_awaited_once()
tests/gateway/test_telegram_physique_checkin.py-3807-        rendered = query.edit_message_text.await_args.kwargs["text"]
tests/gateway/test_telegram_physique_checkin.py:3808:        assert rendered == adapter._nutrition_daily_text(snapshot)
tests/gateway/test_telegram_physique_checkin.py-3809-
tests/gateway/test_telegram_physique_checkin.py-3810-    def test_adaptive_publication_rechecks_revision_before_reservation(self):
tests/gateway/test_telegram_physique_checkin.py-3811-        adapter = object.__new__(TelegramAdapter)
--
gateway/platforms/nutrition_coaching.py-2328-        ):
gateway/platforms/nutrition_coaching.py-2329-            return CustomerOnboardingCard(
gateway/platforms/nutrition_coaching.py-2330-                "개인정보 처리 동의가 저장돼 있습니다.\n"
gateway/platforms/nutrition_coaching.py:2331:                "아직 체크인은 시작되지 않았습니다. 운영자 활성화가 완료되면 "
gateway/platforms/nutrition_coaching.py-2332-                "이 토픽에 ‘오늘 체크인’ 버튼이 표시됩니다."
gateway/platforms/nutrition_coaching.py-2333-            )
gateway/platforms/nutrition_coaching.py-2334-        customer_key = getattr(spec, "customer_key", None)
gateway/platforms/nutrition_coaching.py-2335-        return CustomerOnboardingCard(
gateway/platforms/nutrition_coaching.py-2336-            (
gateway/platforms/nutrition_coaching.py:2337:                "체크인을 시작하려면 개인정보 처리 동의가 필요합니다.\n\n"
gateway/platforms/nutrition_coaching.py-2338-                "이 코칭방에 입력한 식사·체중·활동 기록은 AI 코칭 초안과 "
gateway/platforms/nutrition_coaching.py-2339-                "운영자 검토에 사용됩니다.\n"
gateway/platforms/nutrition_coaching.py-2340-                "동의하지 않으면 AI 코칭은 시작되지 않습니다.\n"
--
gateway/platforms/nutrition_coaching.py-2421-    def _customer_consent_result(granted: bool) -> CustomerTransition:
gateway/platforms/nutrition_coaching.py-2422-        notice = (
gateway/platforms/nutrition_coaching.py-2423-            "동의가 저장됐습니다.\n"
gateway/platforms/nutrition_coaching.py:2424:            "아직 체크인은 시작되지 않았습니다. 운영자 활성화가 완료되면 "
gateway/platforms/nutrition_coaching.py-2425-            "이 토픽에 ‘오늘 체크인’ 버튼이 표시됩니다."
gateway/platforms/nutrition_coaching.py-2426-            if granted
gateway/platforms/nutrition_coaching.py-2427-            else "동의하지 않음이 저장됐습니다. "
--
gateway/platforms/nutrition_coaching.py-2791-        resolved = self._by_key.get(customer_key)
gateway/platforms/nutrition_coaching.py-2792-        if resolved is None:
gateway/platforms/nutrition_coaching.py-2793-            return WizardReply(True, False, "고객 경로를 사용할 수 없습니다.", None, None)
gateway/platforms/nutrition_coaching.py:2794:        opening = resolved.bridge.open_launcher("nutrition_daily")
gateway/platforms/nutrition_coaching.py-2795-        if not opening.accepted:
gateway/platforms/nutrition_coaching.py-2796-            return opening
gateway/platforms/nutrition_coaching.py-2797-        return self._terminal_launcher_reply(resolved, opening)
--
gateway/platforms/telegram.py-4475-                diagnostic = (
gateway/platforms/telegram.py-4476-                    "physique_checkin initialization failed: "
gateway/platforms/telegram.py-4477-                    f"{type(exc).__name__}: {exc}. "
gateway/platforms/telegram.py:4478:                    "Check the profile workspace/checkin_cli package and exact owner/chat/topic settings."
gateway/platforms/telegram.py-4479-                )
gateway/platforms/telegram.py-4480-                self._physique_checkin_error = diagnostic[:500]
gateway/platforms/telegram.py-4481-                logger.error("[%s] %s", getattr(self, "name", "telegram"), self._physique_checkin_error)
--
gateway/platforms/telegram.py-4827-            diagnostic = (
gateway/platforms/telegram.py-4828-                "nutrition_coaching initialization failed: "
gateway/platforms/telegram.py-4829-                f"{type(exc).__name__}: {exc}. "
gateway/platforms/telegram.py:4830:                f"Check registry_path={config.registry_path!s}, the profile workspace/checkin_cli package, "
gateway/platforms/telegram.py-4831-                "and enabled customer consent/address records."
gateway/platforms/telegram.py-4832-            )
gateway/platforms/telegram.py-4833-            self._nutrition_coaching_error = diagnostic[:500]
--
gateway/platforms/telegram.py-5937-        await self._send_nutrition_topic(
gateway/platforms/telegram.py-5938-            chat_id=chat_id,
gateway/platforms/telegram.py-5939-            topic_id=topic_id,
gateway/platforms/telegram.py:5940:            text="아래 ‘오늘 체크인’ 버튼으로 언제든 체크인을 시작하거나 이어갈 수 있습니다.",
gateway/platforms/telegram.py-5941-            reply_markup=self._customer_checkin_reply_markup(),
gateway/platforms/telegram.py-5942-        )
gateway/platforms/telegram.py-5943-        initialized.add(key)
--
gateway/platforms/telegram.py-5990-            else None
gateway/platforms/telegram.py-5991-        )
gateway/platforms/telegram.py-5992-        control_buttons = tuple(getattr(controls, "buttons", ()) or ())
gateway/platforms/telegram.py:5993:        button_rows = ((("오늘 체크인 시작", callback_data),),)
gateway/platforms/telegram.py-5994-        if control_buttons:
gateway/platforms/telegram.py-5995-            button_rows = (*button_rows, control_buttons)
gateway/platforms/telegram.py-5996-        return WizardPrompt(
gateway/platforms/telegram.py-5997-            f"{year}년 {month}월 {day}일{day_label}\n"
gateway/platforms/telegram.py-5998-            "이 고객 체크인 토픽의 오늘 기록입니다.\n"
gateway/platforms/telegram.py:5999:            "아래 ‘오늘 체크인 시작’을 누르면 첫 질문이 열립니다.\n"
gateway/platforms/telegram.py-6000-            "입력을 모두 마친 뒤 ‘체크인 저장’을 누르면 오늘 기록이 제출됩니다.",
gateway/platforms/telegram.py-6001-            button_rows=button_rows,
gateway/platforms/telegram.py-6002-        )
--
gateway/platforms/telegram.py-6215-        opening = coordinator.open_launcher(customer_key)
gateway/platforms/telegram.py-6216-        if not getattr(opening, "accepted", False):
gateway/platforms/telegram.py-6217-            await query.answer(
gateway/platforms/telegram.py:6218:                text=str(getattr(opening, "notice", "") or "오늘 체크인을 시작할 수 없습니다.")[:190]
gateway/platforms/telegram.py-6219-            )
gateway/platforms/telegram.py-6220-            return
gateway/platforms/telegram.py-6221-        message_id = str(getattr(message, "message_id", "") or "")
--
gateway/platforms/telegram.py-7649-    @staticmethod
gateway/platforms/telegram.py-7650-    def _is_physique_recovery_command(text: str) -> bool:
gateway/platforms/telegram.py-7651-        """Recognize only the value-free manual Start/Resume command."""
gateway/platforms/telegram.py:7652:        return text.strip() == "체크인 시작"
gateway/platforms/telegram.py-7653-    @staticmethod
gateway/platforms/telegram.py-7654-    def _is_nutrition_customer_start_command(text: str) -> bool:
gateway/platforms/telegram.py-7655-        """Recognize only the exact customer topic start aliases."""
gateway/platforms/telegram.py:7656:        return " ".join(str(text).split()) in {"체크인 시작", "오늘 체크인"}
gateway/platforms/telegram.py-7657-
gateway/platforms/telegram.py-7658-    @staticmethod
gateway/platforms/telegram.py-7659-    def _is_physique_numeric_answer(text: str) -> bool:
--
gateway/platforms/telegram.py-7680-            if snapshot is not None:
gateway/platforms/telegram.py-7681-                # Telegram callbacks render only the finalized authority snapshot.
gateway/platforms/telegram.py-7682-                # Provider-backed coaching belongs to the out-of-band worker seam.
gateway/platforms/telegram.py:7683:                text = self._nutrition_daily_text(snapshot)
gateway/platforms/telegram.py-7684-        try:
gateway/platforms/telegram.py-7685-            await query.edit_message_text(
gateway/platforms/telegram.py-7686-                text=text,
--
gateway/platforms/telegram.py-7691-            # failure is safe: the user can use the current Start/Resume card.
gateway/platforms/telegram.py-7692-            return
gateway/platforms/telegram.py-7693-    @staticmethod
gateway/platforms/telegram.py:7694:    def _nutrition_daily_text(
gateway/platforms/telegram.py-7695-        snapshot: object,
gateway/platforms/telegram.py-7696-        feedback: object | None = None,
gateway/platforms/telegram.py-7697-    ) -> str:
gateway/platforms/telegram.py-7698-        """Render the approved daily check-in copy from finalized facts."""
gateway/platforms/telegram.py-7699-        answers = TelegramAdapter._nutrition_snapshot_answers(snapshot)
gateway/platforms/telegram.py:7700:        facts = TelegramAdapter._nutrition_daily_facts(answers)
gateway/platforms/telegram.py-7701-        missing = any(value.endswith("기록 없음") for value in facts)
gateway/platforms/telegram.py:7702:        interpretation = TelegramAdapter._nutrition_daily_interpretation(feedback)
gateway/platforms/telegram.py:7703:        actions = TelegramAdapter._nutrition_daily_actions(answers, missing)
gateway/platforms/telegram.py-7704-        lines = [
gateway/platforms/telegram.py-7705-            "오늘 체크인 완료",
gateway/platforms/telegram.py-7706-            "",
--
gateway/platforms/telegram.py-7858-        return labels.get(value.casefold(), value) if value else "기록 없음"
gateway/platforms/telegram.py-7859-
gateway/platforms/telegram.py-7860-    @staticmethod
gateway/platforms/telegram.py:7861:    def _nutrition_daily_facts(answers: Mapping[str, object]) -> tuple[str, ...]:
gateway/platforms/telegram.py-7862-        bodyweight = TelegramAdapter._nutrition_snapshot_value(answers, "bodyweight")
gateway/platforms/telegram.py-7863-        calories = TelegramAdapter._nutrition_snapshot_value(answers, "calories")
gateway/platforms/telegram.py-7864-        macros = answers.get("macros")
--
gateway/platforms/telegram.py-7906-        )
gateway/platforms/telegram.py-7907-
gateway/platforms/telegram.py-7908-    @staticmethod
gateway/platforms/telegram.py:7909:    def _nutrition_daily_interpretation(feedback: object | None) -> str:
gateway/platforms/telegram.py-7910-        fallback = "일부 항목이 기록되지 않아 저장된 내용만 안내합니다."
gateway/platforms/telegram.py-7911-        lines = []
gateway/platforms/telegram.py-7912-        if feedback is not None:
--
gateway/platforms/telegram.py-7932-        return "\n".join(lines) if lines else fallback
gateway/platforms/telegram.py-7933-
gateway/platforms/telegram.py-7934-    @staticmethod
gateway/platforms/telegram.py:7935:    def _nutrition_daily_actions(
gateway/platforms/telegram.py-7936-        answers: Mapping[str, object],
gateway/platforms/telegram.py-7937-        missing: bool,
gateway/platforms/telegram.py-7938-    ) -> tuple[str, ...]:
--
gateway/platforms/telegram.py-8126-
gateway/platforms/telegram.py-8127-    async def _render_physique_feedback_replay(self, message: object, snapshot: dict[str, object]) -> None:
gateway/platforms/telegram.py-8128-        """Render only the canonical replay after a current-authority check."""
gateway/platforms/telegram.py:8129:        canonical = self._nutrition_daily_text(snapshot)
gateway/platforms/telegram.py-8130-        bridge = self._get_physique_checkin()
gateway/platforms/telegram.py-8131-        if bridge is None:
gateway/platforms/telegram.py-8132-            return
--
gateway/platforms/telegram.py-8145-        else:
gateway/platforms/telegram.py-8146-            supplier = lambda: self._daily_grounding_input(
gateway/platforms/telegram.py-8147-                bridge.latest_finalized_coaching_snapshot(),
gateway/platforms/telegram.py:8148:                canonical=self._nutrition_daily_text(
gateway/platforms/telegram.py-8149-                    bridge.latest_finalized_coaching_snapshot()
gateway/platforms/telegram.py-8150-                ),
gateway/platforms/telegram.py-8151-            )
--
gateway/platforms/telegram.py-9555-        return None
gateway/platforms/telegram.py-9556-
gateway/platforms/telegram.py-9557-    @staticmethod
gateway/platforms/telegram.py:9558:    def _nutrition_daily_task_is_current(
gateway/platforms/telegram.py-9559-        task: object,
gateway/platforms/telegram.py-9560-        customer: object,
gateway/platforms/telegram.py-9561-        local_now: datetime,
--
gateway/platforms/telegram.py-9596-            and local_now.time().replace(tzinfo=None) >= daily_time
gateway/platforms/telegram.py-9597-        )
gateway/platforms/telegram.py-9598-
gateway/platforms/telegram.py:9599:    async def _send_activation_completion_notices(
gateway/platforms/telegram.py-9600-        self,
gateway/platforms/telegram.py-9601-        profile_root: object,
gateway/platforms/telegram.py-9602-        coordinator: object | None = None,
--
gateway/platforms/telegram.py-9604-        kst_date: date | None = None,
gateway/platforms/telegram.py-9605-    ) -> tuple[str, ...]:
gateway/platforms/telegram.py-9606-        """Serialize local drains so an in-flight send is not restart-reconciled."""
gateway/platforms/telegram.py:9607:        lock = getattr(self, "_activation_notice_lock", None)
gateway/platforms/telegram.py-9608-        if lock is None:
gateway/platforms/telegram.py-9609-            lock = asyncio.Lock()
gateway/platforms/telegram.py:9610:            self._activation_notice_lock = lock
gateway/platforms/telegram.py-9611-        async with lock:
gateway/platforms/telegram.py:9612:            return await self._drain_activation_completion_notices(
gateway/platforms/telegram.py-9613-                profile_root,
gateway/platforms/telegram.py-9614-                coordinator,
gateway/platforms/telegram.py-9615-                kst_date=kst_date,
gateway/platforms/telegram.py-9616-            )
gateway/platforms/telegram.py-9617-
gateway/platforms/telegram.py:9618:    async def _drain_activation_completion_notices(
gateway/platforms/telegram.py-9619-        self,
gateway/platforms/telegram.py-9620-        profile_root: object,
gateway/platforms/telegram.py-9621-        coordinator: object | None = None,
gateway/platforms/telegram.py-9622-        *,
gateway/platforms/telegram.py-9623-        kst_date: date | None = None,
gateway/platforms/telegram.py-9624-    ) -> tuple[str, ...]:
gateway/platforms/telegram.py:9625:        """Drain reserved activation notices with provider-at-most-once semantics."""
gateway/platforms/telegram.py:9626:        from gateway.platforms.telegram_activation_notice import ActivationNoticeStore
gateway/platforms/telegram.py-9627-
gateway/platforms/telegram.py-9628-        store = ActivationNoticeStore(_Path(str(profile_root)))
gateway/platforms/telegram.py-9629-        failures: list[str] = []
--
gateway/platforms/telegram.py-9650-                if coordinator is not None:
gateway/platforms/telegram.py-9651-                    refresh = getattr(coordinator, "refresh_live_registry", None)
gateway/platforms/telegram.py-9652-                    if not callable(refresh) or not refresh():
gateway/platforms/telegram.py:9653:                        raise RuntimeError("activation notice registry unavailable")
gateway/platforms/telegram.py-9654-                    customer = coordinator.customer(receipt.customer_key)
gateway/platforms/telegram.py-9655-                    destination = getattr(getattr(customer, "spec", None), "telegram", None)
gateway/platforms/telegram.py-9656-                    if (
--
gateway/platforms/telegram.py-9663-                            kst_date=kst_date or date.fromisoformat(receipt.starts_on),
gateway/platforms/telegram.py-9664-                        )
gateway/platforms/telegram.py-9665-                    ):
gateway/platforms/telegram.py:9666:                        raise RuntimeError("activation notice authority changed")
gateway/platforms/telegram.py-9667-                sending = store.mark_sending(receipt)
gateway/platforms/telegram.py-9668-            except Exception:
gateway/platforms/telegram.py-9669-                try:
--
gateway/platforms/telegram.py-9771-            )
gateway/platforms/telegram.py-9772-
gateway/platforms/telegram.py-9773-        failures: list[str] = list(
gateway/platforms/telegram.py:9774:            await self._send_activation_completion_notices(
gateway/platforms/telegram.py-9775-                profile_root,
gateway/platforms/telegram.py-9776-                coordinator,
gateway/platforms/telegram.py-9777-                kst_date=local_now.date(),
--
gateway/platforms/telegram.py-9850-            if customer is None:
gateway/platforms/telegram.py-9851-                failures.append(task_key)
gateway/platforms/telegram.py-9852-                continue
gateway/platforms/telegram.py:9853:            if task.kind == "daily" and not self._nutrition_daily_task_is_current(
gateway/platforms/telegram.py-9854-                task,
gateway/platforms/telegram.py-9855-                customer,
gateway/platforms/telegram.py-9856-                local_now,
--
gateway/platforms/telegram.py-10036-                if (
gateway/platforms/telegram.py-10037-                    (
gateway/platforms/telegram.py-10038-                        task.kind == "daily"
gateway/platforms/telegram.py:10039:                        and not self._nutrition_daily_task_is_current(
gateway/platforms/telegram.py-10040-                            task,
gateway/platforms/telegram.py-10041-                            current_customer,
gateway/platforms/telegram.py-10042-                            local_now,
--
gateway/platforms/telegram.py-10666-            prompt = WizardPrompt(
gateway/platforms/telegram.py-10667-                f"{date_label}\n오늘 기록을 시작하거나 이어갈 수 있습니다.",
gateway/platforms/telegram.py-10668-                (
gateway/platforms/telegram.py:10669:                    ("아침 체크인 시작", morning.callback_data),
gateway/platforms/telegram.py-10670-                    ("운동 후 기록 시작", workout.callback_data),
gateway/platforms/telegram.py-10671-                ),
gateway/platforms/telegram.py-10672-            )

Dedicated Task23/test-surface candidates:scripts/install.ps1-1206-                # bare `git checkout` then aborts with "Your local changes would
scripts/install.ps1-1207-                # be overwritten by checkout", which is exactly the failure GUI
scripts/install.ps1:1208:                # users hit on update. Pin autocrlf=false so the dirt is never
scripts/install.ps1-1209-                # created in the first place.
scripts/install.ps1-1210-                git -c windows.appendAtomically=false config core.autocrlf false 2>$null
--
scripts/tests/test-install-ps1-stage-protocol.ps1-5-#   powershell -NoProfile -ExecutionPolicy Bypass -File scripts/tests/test-install-ps1-stage-protocol.ps1
scripts/tests/test-install-ps1-stage-protocol.ps1-6-#
scripts/tests/test-install-ps1-stage-protocol.ps1:7:# These tests only exercise the metadata surface (-ProtocolVersion, -Manifest,
scripts/tests/test-install-ps1-stage-protocol.ps1-8-# unknown -Stage handling).  They DO NOT actually run any install stages --
scripts/tests/test-install-ps1-stage-protocol.ps1-9-# those have heavy side effects (winget, git clone, pip install, PATH writes)
--
tests/gateway/test_korean_humanizer.py-1131-
tests/gateway/test_korean_humanizer.py-1132-
tests/gateway/test_korean_humanizer.py:1133:@pytest.mark.parametrize("mutation", ("surface", "revision"))
tests/gateway/test_korean_humanizer.py-1134-def test_mismatched_playbook_or_revision_is_zero_call_canonical(mutation):
tests/gateway/test_korean_humanizer.py-1135-    calls = []
--
tests/gateway/test_korean_humanizer.py-1317-    ),
tests/gateway/test_korean_humanizer.py-1318-)
tests/gateway/test_korean_humanizer.py:1319:def test_invalid_surface_or_canonical_is_canonical_with_zero_calls(surface, canonical):
tests/gateway/test_korean_humanizer.py-1320-    calls = []
tests/gateway/test_korean_humanizer.py-1321-    result, receipt = coach_and_polish(
--
tests/gateway/test_matrix.py-2442-        }
tests/gateway/test_matrix.py-2443-
tests/gateway/test_matrix.py:2444:    def test_matrix_capability_claims_match_adapter_surfaces(self):
tests/gateway/test_matrix.py-2445-        from gateway.platforms.matrix import MatrixAdapter, get_matrix_capabilities
tests/gateway/test_matrix.py-2446-
--
tests/gateway/test_telegram_group_gating.py-2129-
tests/gateway/test_telegram_group_gating.py-2130-    asyncio.run(_run())
tests/gateway/test_telegram_group_gating.py:2131:def test_review_surface_reserves_contact_edited_and_channel_ingress():
tests/gateway/test_telegram_group_gating.py-2132-    from gateway.platforms.nutrition_coaching_config import AdaptiveNutritionConfig, AdaptiveReviewOperator
tests/gateway/test_telegram_group_gating.py-2133-
--
tests/gateway/test_compress_command.py-186-
tests/gateway/test_compress_command.py-187-@pytest.mark.asyncio
tests/gateway/test_compress_command.py:188:async def test_compress_command_surfaces_aux_model_failure_even_when_recovered():
tests/gateway/test_compress_command.py-189-    """When the user's configured ``auxiliary.compression.model`` errors out
tests/gateway/test_compress_command.py-190-    but compression recovers by retrying on the main model, /compress must
--
tests/gateway/conftest.py-286-        tree = ast.parse(source)
tests/gateway/conftest.py-287-    except SyntaxError:
tests/gateway/conftest.py:288:        return []  # Let pytest surface the real syntax error.
tests/gateway/conftest.py-289-
tests/gateway/conftest.py-290-    offenses: list[str] = []
--
tests/gateway/test_telegram_nutrition_onboarding_direct_entry.py-144-
tests/gateway/test_telegram_nutrition_onboarding_direct_entry.py-145-@pytest.mark.asyncio
tests/gateway/test_telegram_nutrition_onboarding_direct_entry.py:146:async def test_staff_surface_cannot_submit_customer_answer() -> None:
tests/gateway/test_telegram_nutrition_onboarding_direct_entry.py-147-    runtime = object.__new__(TelegramNutritionOnboardingRuntime)
tests/gateway/test_telegram_nutrition_onboarding_direct_entry.py-148-    runtime._session_for_customer_message = Mock(return_value=None)
--
tests/gateway/relay/test_relay_follow_up.py-95-
tests/gateway/relay/test_relay_follow_up.py-96-@pytest.mark.asyncio
tests/gateway/relay/test_relay_follow_up.py:97:async def test_follow_up_failure_surfaces_when_capability_unresolvable(wired):
tests/gateway/relay/test_relay_follow_up.py-98-    """Connector couldn't resolve (absent/expired/tenant mismatch) -> success=False."""
tests/gateway/relay/test_relay_follow_up.py-99-    adapter, stub = wired
--
tests/gateway/test_telegram_approval_buttons.py-566-    @pytest.mark.asyncio
tests/gateway/test_telegram_approval_buttons.py-567-    async def test_update_prompt_callback_allows_authorized_user(self, tmp_path):
tests/gateway/test_telegram_approval_buttons.py:568:        """Allowed Telegram users can still answer update prompt buttons."""
tests/gateway/test_telegram_approval_buttons.py-569-        adapter = _make_adapter()
tests/gateway/test_telegram_approval_buttons.py-570-
--
tests/gateway/test_restart_redelivery_dedup.py-99-@pytest.mark.asyncio
tests/gateway/test_restart_redelivery_dedup.py-100-async def test_fresh_restart_with_higher_update_id_is_processed(tmp_path, monkeypatch):
tests/gateway/test_restart_redelivery_dedup.py:101:    """A NEW /restart from the user (higher update_id) bypasses the dedup guard."""
tests/gateway/test_restart_redelivery_dedup.py-102-    monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
tests/gateway/test_restart_redelivery_dedup.py-103-    monkeypatch.delenv("INVOCATION_ID", raising=False)
--
tests/gateway/test_feishu.py-4257-        self.assertEqual(normalized.text_content, "@Hermes /help")
tests/gateway/test_feishu.py-4258-
tests/gateway/test_feishu.py:4259:    def test_text_message_at_all_surfaces_ref(self):
tests/gateway/test_feishu.py-4260-        from gateway.platforms.feishu import normalize_feishu_message
tests/gateway/test_feishu.py-4261-
--
gateway/platforms/telegram.py-597-    # initial implementation name. The API limit is character-based, not bytes.
gateway/platforms/telegram.py-598-    RICH_MESSAGE_MAX_BYTES = RICH_MESSAGE_MAX_CHARS
gateway/platforms/telegram.py:599:    # Threshold for detecting Telegram client-side message splits.
gateway/platforms/telegram.py-600-    # When a chunk is near this limit, a continuation is almost certain.
gateway/platforms/telegram.py-601-    _SPLIT_THRESHOLD = 4000
--
gateway/platforms/telegram.py-728-        self._media_group_events: Dict[str, MessageEvent] = {}
gateway/platforms/telegram.py-729-        self._media_group_tasks: Dict[str, asyncio.Task] = {}
gateway/platforms/telegram.py:730:        # Buffer rapid text messages so Telegram client-side splits of long
gateway/platforms/telegram.py-731-        # messages are aggregated into a single MessageEvent.  Lower defaults
gateway/platforms/telegram.py-732-        # (0.3s / 1.0s instead of 0.6s / 2.0s) let short replies stream
--
gateway/platforms/telegram.py-2257-                    self._persist_dm_topic_thread_id(int(chat_id), topic_name, thread_id)
gateway/platforms/telegram.py-2258-
gateway/platforms/telegram.py:2259:                    # Send a seed message so the topic is visible in Telegram's client.
gateway/platforms/telegram.py-2260-                    # Empty topics are hidden by the client UI until they contain a message.
gateway/platforms/telegram.py-2261-                    try:
--
gateway/platforms/telegram.py-14059-        adapter's own bot username, this adapter should ignore the message.
gateway/platforms/telegram.py-14060-
gateway/platforms/telegram.py:14061:        MessageEntity values are preferred, but some Telegram clients expose
gateway/platforms/telegram.py-14062-        selected bot handles as plain text in group messages. The raw-text
gateway/platforms/telegram.py-14063-        fallback is intentionally limited to usernames ending in "bot", which
--
gateway/platforms/telegram.py-14552-        """Handle incoming text messages.
gateway/platforms/telegram.py-14553-
gateway/platforms/telegram.py:14554:        Telegram clients split long messages into multiple updates.  Buffer
gateway/platforms/telegram.py-14555-        rapid successive text messages from the same user/chat and aggregate
gateway/platforms/telegram.py-14556-        them into a single MessageEvent before dispatching.
--
gateway/platforms/telegram.py-14894-
gateway/platforms/telegram.py-14895-    # ------------------------------------------------------------------
gateway/platforms/telegram.py:14896:    # Text message aggregation (handles Telegram client-side splits)
gateway/platforms/telegram.py-14897-    # ------------------------------------------------------------------
gateway/platforms/telegram.py-14898-
--
gateway/platforms/telegram.py-14915-        """Buffer a text event and reset the flush timer.
gateway/platforms/telegram.py-14916-
gateway/platforms/telegram.py:14917:        When Telegram splits a long user message into multiple updates,
gateway/platforms/telegram.py-14918-        they arrive within a few hundred milliseconds.  This method
gateway/platforms/telegram.py-14919-        concatenates them and waits for a short quiet period before
--
tests/gateway/test_run_progress_topics.py-772-
tests/gateway/test_run_progress_topics.py-773-@pytest.mark.asyncio
tests/gateway/test_run_progress_topics.py:774:async def test_run_agent_surfaces_real_interim_commentary(monkeypatch, tmp_path):
tests/gateway/test_run_progress_topics.py-775-    adapter, result = await _run_with_agent(
tests/gateway/test_run_progress_topics.py-776-        monkeypatch,
--
tests/gateway/test_run_progress_topics.py-786-
tests/gateway/test_run_progress_topics.py-787-@pytest.mark.asyncio
tests/gateway/test_run_progress_topics.py:788:async def test_run_agent_surfaces_interim_commentary_by_default(monkeypatch, tmp_path):
tests/gateway/test_run_progress_topics.py-789-    adapter, result = await _run_with_agent(
tests/gateway/test_run_progress_topics.py-790-        monkeypatch,
--
tests/gateway/test_telegram_text_batching.py-1-"""Tests for Telegram text message aggregation.
tests/gateway/test_telegram_text_batching.py-2-
tests/gateway/test_telegram_text_batching.py:3:When a user sends a long message, Telegram clients split it into multiple
tests/gateway/test_telegram_text_batching.py-4-updates.  The TelegramAdapter should buffer rapid successive text messages
tests/gateway/test_telegram_text_batching.py-5-from the same session and aggregate them before dispatching.
--
tests/gateway/test_task22_supplemental_owner_review.py-919-        owner_user_id=12, owner_chat_id=-100, owner_topic_id=22
tests/gateway/test_task22_supplemental_owner_review.py-920-    )
tests/gateway/test_task22_supplemental_owner_review.py:921:    evidence = SimpleNamespace(actor_user_id=12, update_id=500, message_id=122)
tests/gateway/test_task22_supplemental_owner_review.py-922-    publication = SimpleNamespace(generation=27, message_id=122)
tests/gateway/test_task22_supplemental_owner_review.py-923-    token = SimpleNamespace(action="owner_ok", generation=27)
--
tests/gateway/test_title_command.py-314-
tests/gateway/test_title_command.py-315-    @pytest.mark.asyncio
tests/gateway/test_title_command.py:316:    async def test_reset_command_duplicate_title_surfaces_warning(self):
tests/gateway/test_title_command.py-317-        """/new <title> with an already-in-use title returns a warning in the reply."""
tests/gateway/test_title_command.py-318-        from datetime import datetime
--
tests/gateway/test_stream_consumer_draft.py-32-    The runtime subclass + cleared __abstractmethods__ pattern lets us
tests/gateway/test_stream_consumer_draft.py-33-    construct an adapter without hauling in any platform's heavy state
tests/gateway/test_stream_consumer_draft.py:34:    (Telegram bot, Discord client, etc.) while still satisfying the
tests/gateway/test_stream_consumer_draft.py-35-    consumer's isinstance(BasePlatformAdapter) gate.
tests/gateway/test_stream_consumer_draft.py-36-    """
--
tests/gateway/test_stt_config.py-34-
tests/gateway/test_stt_config.py-35-@pytest.mark.asyncio
tests/gateway/test_stt_config.py:36:async def test_enrich_message_with_transcription_surfaces_path_when_stt_disabled():
tests/gateway/test_stt_config.py-37-    from gateway.run import GatewayRunner
tests/gateway/test_stt_config.py-38-
--
tests/gateway/test_task22_trainer_authority_removal.py-1176-
tests/gateway/test_task22_trainer_authority_removal.py-1177-
tests/gateway/test_task22_trainer_authority_removal.py:1178:@pytest.mark.parametrize("surface", ("reset", "archive"))
tests/gateway/test_task22_trainer_authority_removal.py-1179-@pytest.mark.parametrize(
tests/gateway/test_task22_trainer_authority_removal.py-1180-    ("state", "valid"),
--
tests/gateway/test_platform_http_client_limits.py-74-def test_helper_is_importable_from_every_platform_that_uses_it():
tests/gateway/test_platform_http_client_limits.py-75-    """Every persistent-httpx-client platform adapter imports this helper.
tests/gateway/test_platform_http_client_limits.py:76:    If any of those modules fails to import, this test surfaces it before
tests/gateway/test_platform_http_client_limits.py-77-    the regression shows up as a runtime adapter-startup crash."""
tests/gateway/test_platform_http_client_limits.py-78-    # Just importing exercises the helper's import path for each adapter.
--
tests/gateway/test_session_api.py-53-
tests/gateway/test_session_api.py-54-@pytest.mark.asyncio
tests/gateway/test_session_api.py:55:async def test_capabilities_advertises_session_control_surface(adapter):
tests/gateway/test_session_api.py-56-    app = _create_session_app(adapter)
tests/gateway/test_session_api.py-57-    async with TestClient(TestServer(app)) as cli:
--
tests/gateway/test_nutrition_coaching.py-634-            "display_name": "고객 001",
tests/gateway/test_nutrition_coaching.py-635-            "enabled": True,
tests/gateway/test_nutrition_coaching.py:636:            "telegram": {"user_id": "client", "chat_id": "customer-chat", "topic_id": "customer-topic"},
tests/gateway/test_nutrition_coaching.py-637-            "trainer": {"user_id": "trainer", "chat_id": "trainer-chat", "topic_id": "trainer-topic"},
tests/gateway/test_nutrition_coaching.py-638-            "ai_processing_consent": {
--
tests/gateway/test_slack.py-1527-
tests/gateway/test_slack.py-1528-    @pytest.mark.asyncio
tests/gateway/test_slack.py:1529:    async def test_download_failure_is_surfaced_in_message_text(self, adapter):
tests/gateway/test_slack.py-1530-        """Attachment download failures (401/403/HTML-body/etc.) should be
tests/gateway/test_slack.py-1531-        translated into a user-facing `[Slack attachment notice]` block so
--
tests/gateway/test_whatsapp_connect.py-319-
tests/gateway/test_whatsapp_connect.py-320-    @pytest.mark.asyncio
tests/gateway/test_whatsapp_connect.py:321:    async def test_shutdown_still_surfaces_nonzero_crash(self):
tests/gateway/test_whatsapp_connect.py-322-        """Even during shutdown, a truly crashed bridge (e.g. returncode 9) is fatal.
tests/gateway/test_whatsapp_connect.py-323-
--
tests/gateway/test_telegram_physique_checkin.py-2659-        assert result.success is False
tests/gateway/test_telegram_physique_checkin.py-2660-
tests/gateway/test_telegram_physique_checkin.py:2661:class TestNutritionCopySurfaces:
tests/gateway/test_telegram_physique_checkin.py-2662-    def test_daily_copy_keeps_the_approved_sections_and_spacing(self):
tests/gateway/test_telegram_physique_checkin.py-2663-        snapshot = {
--
tests/gateway/test_session_list_allowed_sources.py-40-
tests/gateway/test_session_list_allowed_sources.py-41-
tests/gateway/test_session_list_allowed_sources.py:42:def test_session_list_surfaces_all_user_facing_sources(monkeypatch):
tests/gateway/test_session_list_allowed_sources.py-43-    """acp / webhook / custom sources should all appear; only ``tool`` is hidden."""
tests/gateway/test_session_list_allowed_sources.py-44-    rows = [
--
gateway/platforms/api_server.py-729-
gateway/platforms/api_server.py-730-# Defense-in-depth: mirror the agent-facing cronjob tool, which scans the
gateway/platforms/api_server.py:731:# user-supplied prompt for exfiltration/injection payloads at create/update
gateway/platforms/api_server.py-732-# time (tools/cronjob_tools.py).  The REST cron endpoints are authenticated
gateway/platforms/api_server.py-733-# (every handler runs _check_auth, and connect() refuses to start without
--
gateway/platforms/nutrition_coaching.py-11491-        **kwargs: object,
gateway/platforms/nutrition_coaching.py-11492-    ) -> "AdaptiveNutritionCoordinator":
gateway/platforms/nutrition_coaching.py:11493:        """Construct the caller-fed test surface through the sealed factory."""
gateway/platforms/nutrition_coaching.py-11494-        if (
gateway/platforms/nutrition_coaching.py-11495-            cls is not AdaptiveNutritionCoordinator

[Showing lines 154-803 of 803 (50.0KB limit). Full output: /tmp/pi-bash-4a9646256381f5b9.log]


**Inspecting Task22 canonical script and venv**

**Diagnosing missing bootstrap session blocker**

**Investigating Task22 supplemental activation code**

**Planning environment verification**

**Investigating virtual environment paths**

**Planning environment verification**

**Inspecting related Task22 code for session dependency**

**Searching getChatMember usage for activation flow**

**Inspecting file schemas for provider_observation**

**Planning session.json analysis and status verification**

**Evaluating manual Telegram interaction requirement**

**Specifying precise Telegram Web login step**

**Identifying missing bootstrap session blocking activation**

**Planning minimal fix for bootstrap session defect**

**Planning test-first fix for Task22 activation**

**Inspecting Task22 callback storage module**

**Reviewing Task22 migration and activation flow**

**Investigating missing bootstrap in Task22 migration**

bash

cd /home/cube/projects/richard/hermes-agent && before=$(sha256sum /home/cube/.hermes/profiles/dualcoachtest/data/onboarding/telegram-room-bootstrap-v1/ledger.json | cut -d' ' -f1); scripts/nutrition-room-bootstrap status --profile-root /home/cube/.hermes/profiles/dualcoachtest; after=$(sha256sum /home/cube/.hermes/profiles/dualcoachtest/data/onboarding/telegram-room-bootstrap-v1/ledger.json | cut -d' ' -f1); printf '{"ledger_unchanged":%s,"before":"%s","after":"%s"}\n' "$( [ "$before" = "$after" ] && echo true || echo false )" "$before" "$after"

python3 - <<'PY'
from __future__ import annotations
import hashlib,json
from pathlib import Path
root=Path('/home/cube/.hermes/profiles/dualcoachtest')
for rel in [
 'data/customers/task22_dm_rehearsal/nutrition-onboarding/session.json',
 'data/customers/task22_dm_rehearsal/nutrition-onboarding/ready.json',
 'data/customers/task22_dm_rehearsal/nutrition-onboarding/readiness-current.json',
 'data/customers/task22_dm_rehearsal/nutrition-onboarding/readiness-receipt-v1.json',
 'data/customers/task22_dm_rehearsal/nutrition-onboarding/migrations/task22-supplemental-owner-review-v1.json',
 'data/onboarding/telegram-room-bootstrap-v1/ledger.json',
]:
 path=root/rel; raw=path.read_bytes(); value=json.loads(raw)
 print('\n---',rel,'---')
 print('sha256=',hashlib.sha256(raw).hexdigest(),'keys=',sorted(value) if isinstance(value,dict) else type(value).__name__)
 def summarize(item: object, depth:int=0)-> object:
  if depth>1: return type(item).__name__
  if isinstance(item,dict):
   allowed=('schema','state','phase','session_id','customer_key','generation','review_state','activation','activation_enabled','delivery_enabled','ready','finalized','customer_enabled')
   return {key:(item[key] if isinstance(item[key],(str,int,float,bool,type(None))) else summarize(item[key],depth+1)) for key in allowed if key in item}
  return type(item).__name__
 print('summary=',json.dumps(summarize(value),sort_keys=True))
 for key in ('sessions','records','events','publications','callbacks','downstream','activation'):
  if isinstance(value,dict) and key in value:
   item=value[key]
   print(key,'type=',type(item).__name__,'count=',len(item) if isinstance(item,(list,dict)) else None)
PY

cd /home/cube/projects/richard/hermes-agent && printf '%s\n' 'Task22-to-activation integration references:'; rg -n -C 3 'require_task22_supplemental_for_activation|task22_supplemental|task22.*activation|activation.*task22|PUBLICATION_COMMITTED|owner_review' gateway/platforms tests/gateway scripts | head -1200; printf '\nRoomBootstrap creation/migration selectors:'; rg -n -C 3 'RoomBootstrapSession\(|\.prepare\(|prepare_rehearsal|AWAITING_ACTIVATION|_complete_committed_activation' gateway/platforms tests/gateway | head -1200

printf '%s\n' 'Existing isolated virtual environments for Task22 canonical verifier:'; find /home/cube -type f -path '*bin/python*' -printf '%p\n' 2>/dev/null | grep -E 'task22|dualcoach|hermes-agent/.venv|checkin_cli/.venv' | sort | head -300; printf '\nCanonical verifier requirements:'; rg -n -C 3 'isolated|virtual|venv|prefix|executable|sys\.' .omo/evidence/task22-supplemental-compatibility-canonical.py .omo/evidence/verify-task22-supplemental-candidate.py

cd /home/cube/projects/richard/hermes-agent && printf '%s\n' 'Membership receipt generation/consumption:'; rg -n -C 3 'telegram-membership-observation-v2|TelegramMembershipObservation|provider_membership|load_provider_membership|canonical_observation_digest|getChatMember' gateway/platforms scripts tests/gateway /home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/checkin_cli | head -1400


#!/usr/bin/env -S python3 -I -B
"""Canonical, code-only verifier for the Task22 compatibility implementation."""

from __future__ import annotations

import hashlib
import json
import subprocess
import sys
from pathlib import Path
from typing import Final, cast

EVIDENCE: Final = Path(__file__).resolve().parent
VENV: Final = Path("/home/cube/projects/richard/hermes-agent/.venv")
VERIFIER: Final = EVIDENCE / "verify-task22-supplemental-candidate.py"
EXPECTED_VERIFIER_SHA256: Final = "fd57070f322f4de6a8a416d2c306652deb4313d8ba2aa44dba0aaf9e43f08bd2"
EXPECTED: Final = {
    "candidate": "3ce5b95637211b0289529c35b452cf1771fe868a34777140388a8caa75e183cc",
    "changed_paths": [
        "gateway:gateway/platforms/telegram_nutrition_onboarding_supplemental_review.py",
        "gateway:scripts/task22-supplemental-owner-review",
        "gateway:tests/gateway/test_task22_supplemental_owner_review.py",
    ],
    "freeze": "5c98ffeb84961eb91ae33e184094f2382ceff14ad6110263ffdb6ca860f23b48",
    "manifest": "e456cfc6c93d97989319818879a38237cd70c0d516ae5d2e33543a59962658e8",
    "mode": "narrow-code-only-compatibility-verification",
    "paths": 177,
    "wheel": "f592abefa3deca172c9d0b8dcb8013e0e8a1fc97ff03db4054adb3f0b6f3abf2",
    "wheel_receipt": "188a2e0451ddc52ef583d232d53f6ae51ba6db7ae4d078e50c9fe0f9df4a8663",
}

def _snapshot(path: Path, expected: str) -> None:
    if hashlib.sha256(path.read_bytes()).hexdigest() != expected:
        raise RuntimeError(f"canonical pin mismatch: {path.name}")

def main() -> int:
    if sys.flags.isolated != 1 or Path(sys.prefix) != VENV:
        raise RuntimeError("canonical verifier requires the isolated Task22 virtual environment")
    _snapshot(VERIFIER, EXPECTED_VERIFIER_SHA256)
    result = subprocess.run(
        [sys.executable, "-I", "-B", str(VERIFIER)],
        cwd=EVIDENCE,
        env={
            "HOME": "/home/cube",
            "LANG": "C.UTF-8",
            "LC_ALL": "C.UTF-8",
            "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        },
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        check=False,
    )
    if result.returncode != 0 or result.stderr:
        raise RuntimeError(result.stderr.strip() or result.stdout.strip() or "direct verifier failed")
    value = cast(object, json.loads(result.stdout))
    if value != EXPECTED:
        raise RuntimeError("canonical verifier result does not match exact pins")
    print(json.dumps({
        "mode": "canonical-code-only-verification",
        "ready": True,
        "successor": EXPECTED,
    }, sort_keys=True))
    return 0

if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except (OSError, RuntimeError, ValueError, json.JSONDecodeError) as exc:
        print(json.dumps({"mode": "canonical-code-only-verification", "ready": False, "error": str(exc)}, sort_keys=True), file=sys.stderr)
        raise SystemExit(1)