I read this as: implement a strict-rerun legacy callback fix — stop dispatching `non1:` callbacks in the Telegram production gateway while keeping `non2:` and keeping `non1:` rejection fixtures under tests, TDD with a RED regression test proving `non1:` fails closed.

Let me start by exploring the scope.


Let me find the `non1:`/`non2:` dispatch code in the source and the related tests.


Let me look at the exact dispatch code at line 11313 in `telegram.py`.


Let me examine how the runtime handles an expired/historical callback, to keep the fail-closed behavior consistent.


I now understand the flow fully. Let me look at the test file's imports and the existing test helpers to write the RED test consistently.


Now I'll write the RED regression test, placed right after the existing (misnamed but non2-verifying) routing test.


I'll add the RED regression test right after the existing routing test.


Now let me run the NEW test to confirm it's RED (fails against current code).


The project needs its test harness. Let me check how tests are run (checkin_cli import path).


There's a `.venv` (python 3.12) that the repo uses. Let me verify.


The repo's `.venv` has everything wired correctly. Let me run the new test to confirm RED.


RED confirmed — a `non1:` callback currently reaches the onboarding handler. Now let me apply the production fix.


Now let me verify GREEN on the new test, plus the existing non2 routing test and the historical `non1:` rejection fixtures.


GREEN. Now let me run the full closest Telegram callback tests: the entire `test_telegram_nutrition_onboarding.py` and `test_trainer_free_v1_contract.py`, plus the preview tests that reference `non1:`.


All 75 pass. Now let me run Ruff on both changed files and check diagnostics.


The reason_code is valid. Let me apply Ruff's formatting to the test file.


`telegram.py` already had formatting drift (it's in the pre-existing modified set). Let me confirm my changed region is correctly formatted and the drift is pre-existing/unrelated.


My edited regions are Ruff-clean (the format drift in `telegram.py` is pre-existing and unrelated). Let me capture the exact diff of my changes and run the broader related test set to be safe.


Both files confirmed. `telegram.py` is tracked (pre-existing modifications + mine); the test file is a new untracked file on this feature branch. Let me show my exact production change.


Production change is clean. Let me run the adjacent `_handle_callback_query` dispatch test files to confirm nothing else regressed.


Let me confirm the observable stop: no `non1:` dispatch path remains in production source.


The observable stop is met. Here's the summary.

## Intent
Fix the strict-rerun legacy callback handling: stop dispatching `non1:` callbacks to the nutrition onboarding handler while keeping `non2:` behavior and keeping the existing `non1:` rejection fixtures. Done test-first.

## What I did

**RED** — added `test_non1_callback_is_rejected_as_stale_unknown_and_never_reaches_onboarding` to `tests/gateway/test_telegram_nutrition_onboarding.py`. Before the fix it failed with `AssertionError: Expected mock to not have been awaited. Awaited 1 times`, proving a `non1:` callback currently reaches the onboarding handler.

**GREEN** — in `gateway/platforms/telegram.py`, split the combined dispatch. `non2:` still routes to `_handle_nutrition_onboarding_callback`; `non1:` is now an explicit fail-closed rejection (stale/unknown answer `"만료되거나 잘못된 온보딩 버튼입니다."` + `callback_invalid` ingress log) that returns before the handler, so it never reaches the handler or mutates state.

## Exact diff

Production (`gateway/platforms/telegram.py`, line ~11313):
```python
-        if str(data).startswith(("non1:", "non2:")):
+        if str(data).startswith("non2:"):
             await self._handle_nutrition_onboarding_callback(
                 query,
                 str(data),
                 query_message,
                 update_id=getattr(update, "update_id", None),
             )
             return
+        # Historical non1 callbacks are expired; reject them fail-closed as
+        # stale/unknown so they never reach the onboarding handler or mutate
+        # state. Only the current non2 namespace is a dispatch path.
+        if str(data).startswith("non1:"):
+            log_ingress_stage(
+                "validation",
+                getattr(update, "update_id", None),
+                reason_code="callback_invalid",
+                logger=logger,
+            )
+            await query.answer(text="만료되거나 잘못된 온보딩 버튼입니다.")
+            return
```

Test (`tests/gateway/test_telegram_nutrition_onboarding.py`, after the existing `non2` routing test):
```python
@pytest.mark.asyncio
async def test_non1_callback_is_rejected_as_stale_unknown_and_never_reaches_onboarding() -> None:
    """Historical non1 callbacks must fail closed, never reaching the onboarding handler."""
    module = _load()
    adapter = object.__new__(TelegramAdapter)
    adapter._handle_nutrition_onboarding_callback = AsyncMock()
    adapter._handle_room_bootstrap_callback = AsyncMock(
        side_effect=AssertionError("room bootstrap callback branch was reached")
    )
    adapter._get_room_bootstrap_transport = lambda: (_ for _ in ()).throw(
        AssertionError("bootstrap reservation was reached")
    )
    historical = module.encode_callback(
        action="next",
        generation=1,
        session_id="session-1",
    ).replace("non2:", "non1:", 1)
    query = SimpleNamespace(
        data=historical,
        message=SimpleNamespace(chat_id=-100),
        answer=AsyncMock(),
    )

    await adapter._handle_callback_query(
        SimpleNamespace(callback_query=query),
        SimpleNamespace(),
    )

    adapter._handle_nutrition_onboarding_callback.assert_not_awaited()
    query.answer.assert_awaited_once_with(text="만료되거나 잘못된 온보딩 버튼입니다.")
```

## Compatibility concern
One existing test, `test_non1_callback_routes_before_bootstrap_and_generic_branches`, is misleadingly named — despite the `non1` in its name it builds its data with `encode_callback` (which produces `non2:`), so it verifies `non2` routing. It still passes unchanged and documents that `non2:` behavior is preserved. No other concerns: the historical `non1:` rejection fixtures (`consume_unknown_callback("non1:unknown")`, `decode_callback` non1 rejection in `test_trainer_free_v1_contract.py`, the `non1:` preview test, the historical trainer-callback rejection) were left untouched and still pass.

## Verification
- RED: new test failed (`Awaited 1 times`) before the fix.
- GREEN: new test passes after the fix.
- Related tests: `test_telegram_nutrition_onboarding.py`, `test_trainer_free_v1_contract.py`, `test_telegram_nutrition_onboarding_preview.py` (75 passed); `test_telegram_approval_buttons.py`, `test_telegram_clarify_buttons.py`, `test_telegram_group_gating.py` (127 passed).
- Ruff `check` on both files: all checks passed. Ruff `format` on the test file applied (a two-line assert join); `telegram.py` has pre-existing formatting drift unrelated to this change (my edited block is format-clean).
- Observable stop confirmed: `grep non1 gateway/platforms/telegram.py` shows only the fail-closed rejection block — no dispatch path. No plan/evidence/config/service/live-state/Git touched.