--- a/gateway/platforms/telegram_nutrition_onboarding_copy.py
+++ b/gateway/platforms/telegram_nutrition_onboarding_copy.py
@@ -10,6 +10,13 @@
     "weight_kg": "kg",
     "target_weight_kg": "kg",
 }
+_ACTIVITY_CATEGORIES = {
+    "좌식": "sedentary",
+    "가벼움": "light",
+    "보통": "moderate",
+    "매우 활동적": "very_active",
+    "최고 활동": "extra_active",
+}
 _KOREAN_MEAL_COUNTS = {
     "한": 1,
     "두": 2,
@@ -96,6 +103,20 @@
     return "승인이 완료됐습니다. 안전 자료를 최종 생성하고 있습니다.", None, "owner"
 
 
+def parse_legacy_activity_answer(
+    value: object,
+) -> tuple[str, str] | None:
+    if not isinstance(value, str):
+        return None
+    for label, category in _ACTIVITY_CATEGORIES.items():
+        marker = f"활동 수준: {label}. "
+        if value.startswith(marker):
+            rationale = value.removeprefix(marker).strip()
+            if rationale:
+                return category, rationale
+    return None
+
+
 def parse_answer(field: str, text: str) -> object:
     value = text.strip()
     if field in {
@@ -113,19 +134,18 @@
             "status": "provided",
             "items": [item.strip() for item in value.split(",")],
         }
+    if field == "activity_category":
+        exact = _ACTIVITY_CATEGORIES.get(value)
+        if exact is not None:
+            return exact
+        legacy = parse_legacy_activity_answer(value)
+        return legacy[0] if legacy is not None else value
     mappings = {
         "equation_sex_basis": {
             "남성": "male",
             "여성": "female",
             "선택 안 함": "decline",
         },
-        "activity_category": {
-            "좌식": "sedentary",
-            "가벼움": "light",
-            "보통": "moderate",
-            "매우 활동적": "very_active",
-            "최고 활동": "extra_active",
-        },
         "goal_type": {"감량": "loss", "유지": "maintain", "증량": "gain"},
     }
     if field in mappings:
--- a/gateway/platforms/telegram_nutrition_onboarding_runtime.py
+++ b/gateway/platforms/telegram_nutrition_onboarding_runtime.py
@@ -30,6 +30,9 @@
     TelegramNutritionOnboardingRuntimeCollectionMixin,
     is_consent_withdrawal,
 )
+from gateway.platforms.telegram_nutrition_onboarding_copy import (
+    parse_legacy_activity_answer,
+)
 from gateway.platforms.telegram_nutrition_onboarding_runtime_errors import (
     NutritionOnboardingRuntimeError,
 )
@@ -446,6 +449,15 @@
                     "invalid onboarding answers"
                 )
             answers[key] = value
+        target_field = fields[target_cursor]
+        if target_field == "schedule_constraints":
+            recovered_activity = parse_legacy_activity_answer(
+                answers.get("activity_category")
+            )
+            if recovered_activity is not None:
+                category, rationale = recovered_activity
+                answers["activity_category"] = category
+                answers["activity_rationale"] = rationale
         for field in fields[target_cursor:]:
             if not isinstance(field, str):
                 raise NutritionOnboardingRuntimeError(
--- a/tests/gateway/test_telegram_nutrition_onboarding_copy.py
+++ b/tests/gateway/test_telegram_nutrition_onboarding_copy.py
@@ -27,6 +27,22 @@
     assert parse_answer(field, text) == expected
 
 
+def test_parse_answer_normalizes_exact_activity_clarification_prefix() -> None:
+    clarification = (
+        "활동 수준: 보통. 주 3회, 회당 60분 정도의 중간 강도 근력운동을 "
+        "희망합니다. 평소 걷기·이동은 하루 약 6,000~8,000보 수준입니다."
+    )
+
+    assert parse_answer("activity_category", clarification) == "moderate"
+    assert parse_answer("activity_category", "보통") == "moderate"
+    assert (
+        parse_answer("activity_category", "보통 운동을 자주 합니다")
+        == "보통 운동을 자주 합니다"
+    )
+    unrecognized = "활동 수준: 꽤 활동적. 주 3회 운동합니다."
+    assert parse_answer("activity_category", unrecognized) == unrecognized
+
+
 def test_parse_answer_rejects_ambiguous_meal_count() -> None:
     with pytest.raises(ValueError):
         parse_answer("meal_count", "대충 네다섯 끼")
--- a/tests/gateway/test_telegram_nutrition_onboarding.py
+++ b/tests/gateway/test_telegram_nutrition_onboarding.py
@@ -16,6 +16,7 @@
 from gateway.platforms.telegram import TelegramAdapter
 from gateway.platforms.nutrition_onboarding_reconciliation import (
     NutritionOnboardingReconciliation,
+    render_authoritative_customer_summary,
 )
 from gateway.platforms.nutrition_coaching_proposal import NutritionTargets
 from gateway.platforms.telegram_nutrition_onboarding_copy import card
@@ -541,6 +542,35 @@
     ) == 24
 
 
+def test_changed_recovered_card_body_advances_publication_generation() -> None:
+    attestation = SimpleNamespace(
+        answer_count=22,
+        state=SimpleNamespace(value="customer_attestation"),
+    )
+    current = SimpleNamespace(
+        generation=24,
+        payload={
+            "body_digest": "stale-card",
+            "state": "customer_attestation",
+        },
+    )
+    corrected = {
+        "body_digest": "corrected-card",
+        "state": "customer_attestation",
+    }
+
+    assert publication_generation(
+        attestation,
+        current=current,
+        payload=corrected,
+    ) == 25
+    assert publication_generation(
+        attestation,
+        current=SimpleNamespace(generation=24, payload=corrected),
+        payload=corrected,
+    ) == 24
+
+
 def test_ready_card_announces_activation_readiness_to_owner() -> None:
     text, action, role = card(
         SimpleNamespace(
@@ -2006,6 +2036,229 @@
     )
 
 
+def test_schedule_rewind_recovers_exact_legacy_activity_shape() -> None:
+    runtime = object.__new__(TelegramNutritionOnboardingRuntime)
+    legacy = (
+        "활동 수준: 보통. 주 3회, 회당 60분 정도의 중간 강도 근력운동을 "
+        "희망합니다. 평소 걷기·이동은 하루 약 6,000~8,000보 수준입니다."
+    )
+    rationale = (
+        "주 3회, 회당 60분 정도의 중간 강도 근력운동을 희망합니다. "
+        "평소 걷기·이동은 하루 약 6,000~8,000보 수준입니다."
+    )
+    original = {
+        field: onboarding_domain.example_answer(field)
+        for field in onboarding_domain.QUESTION_FIELDS
+    }
+    original["activity_category"] = legacy
+    document = {
+        "state": "customer_attestation",
+        "cursor": 22,
+        "answers": dict(original),
+        "reconciliation": {
+            "answers_digest": "stale",
+            "state": "resolved",
+        },
+    }
+    setattr(runtime, "domain", SimpleNamespace(
+        QUESTION_FIELDS=onboarding_domain.QUESTION_FIELDS,
+        load_mutable_session=Mock(return_value=document),
+        save_session=Mock(),
+        build_status=Mock(
+            return_value=SimpleNamespace(
+                state=SimpleNamespace(value="collecting"),
+                answer_count=21,
+                next_field="schedule_constraints",
+            )
+        ),
+        OnboardingState=SimpleNamespace(
+            COLLECTING=SimpleNamespace(value="collecting")
+        ),
+    ))
+    service = SimpleNamespace(
+        session_path=Path("/private/workflow.json"),
+        customer_key="client_001",
+    )
+
+    status = getattr(runtime, "_rewind_collection")(
+        service=service,
+        authority="customer-authority",
+        evidence="customer-evidence",
+        target_cursor=21,
+    )
+
+    expected = dict(original)
+    expected["activity_category"] = "moderate"
+    expected["activity_rationale"] = rationale
+    expected.pop("schedule_constraints")
+    assert document["answers"] == expected
+    assert document["state"] == "collecting"
+    assert document["cursor"] == 21
+    assert "reconciliation" not in document
+    assert status.state.value == "collecting"
+    assert status.answer_count == 21
+    assert status.next_field == "schedule_constraints"
+
+
+def test_schedule_rewind_leaves_unrecognized_activity_prose_fail_closed() -> None:
+    runtime = object.__new__(TelegramNutritionOnboardingRuntime)
+    prose = "활동은 보통이고 운동을 자주 합니다."
+    document = {
+        "state": "customer_attestation",
+        "cursor": 22,
+        "answers": {
+            "activity_category": prose,
+            "activity_rationale": "기존 활동 근거",
+            "meal_count": 3,
+            "schedule_constraints": "없음",
+        },
+        "reconciliation": {"state": "resolved"},
+    }
+    setattr(runtime, "domain", SimpleNamespace(
+        QUESTION_FIELDS=onboarding_domain.QUESTION_FIELDS,
+        load_mutable_session=Mock(return_value=document),
+        save_session=Mock(),
+        build_status=Mock(return_value="collecting"),
+        OnboardingState=SimpleNamespace(
+            COLLECTING=SimpleNamespace(value="collecting")
+        ),
+    ))
+    service = SimpleNamespace(
+        session_path=Path("/private/workflow.json"),
+        customer_key="client_001",
+    )
+
+    getattr(runtime, "_rewind_collection")(
+        service=service,
+        authority="customer-authority",
+        evidence="customer-evidence",
+        target_cursor=21,
+    )
+
+    assert document["answers"] == {
+        "activity_category": prose,
+        "activity_rationale": "기존 활동 근거",
+        "meal_count": 3,
+    }
+
+
+def test_recovered_answers_resubmit_to_valid_baseline_and_summary(
+    tmp_path: Path,
+) -> None:
+    authority = onboarding_domain.OnboardingAuthority(
+        customer_key="client_001",
+        customer_user_id=10,
+        customer_chat_id=-100,
+        customer_topic_id=20,
+        owner_user_id=12,
+        owner_chat_id=-100,
+        owner_topic_id=22,
+        consent_notice_version="privacy-v1",
+        consent_granted=True,
+        customer_enabled=False,
+    )
+    service = onboarding_domain.NutritionOnboardingService(
+        profile_root=tmp_path,
+        customer_key="client_001",
+        enforce_current_authority=False,
+        enforce_reconciliation=True,
+    )
+    evidence = onboarding_domain.MessageEvidence(
+        actor_user_id=10,
+        chat_id=-100,
+        topic_id=20,
+        message_id=1,
+        update_id=101,
+    )
+    service.start_or_resume(authority=authority, evidence=evidence)
+    status = None
+    for index, field in enumerate(onboarding_domain.QUESTION_FIELDS):
+        value = onboarding_domain.example_answer(field)
+        if field == "activity_category":
+            value = (
+                "활동 수준: 보통. 주 3회, 회당 60분 정도의 중간 강도 "
+                "근력운동을 희망합니다. 평소 걷기·이동은 하루 약 "
+                "6,000~8,000보 수준입니다."
+            )
+        status = service.submit_answer(
+            field=field,
+            value=value,
+            authority=authority,
+            evidence=onboarding_domain.MessageEvidence(
+                actor_user_id=10,
+                chat_id=-100,
+                topic_id=20,
+                message_id=index + 2,
+                update_id=index + 102,
+            ),
+        )
+    assert status is not None
+    service.record_reconciliation(
+        answers_digest=canonical_digest(
+            service.reconciliation_answers(authority=authority)
+        ),
+        advisory={"summary_ko": "수정 전 요약"},
+        clarifications=[],
+        authority=authority,
+    )
+    runtime = object.__new__(TelegramNutritionOnboardingRuntime)
+    setattr(runtime, "domain", onboarding_domain)
+
+    rewound = getattr(runtime, "_rewind_collection")(
+        service=service,
+        authority=authority,
+        evidence=onboarding_domain.MessageEvidence(
+            actor_user_id=10,
+            chat_id=-100,
+            topic_id=20,
+            message_id=30,
+            update_id=130,
+        ),
+        target_cursor=21,
+    )
+    assert rewound.next_field == "schedule_constraints"
+    completed = service.submit_answer(
+        field="schedule_constraints",
+        value="없음",
+        authority=authority,
+        evidence=onboarding_domain.MessageEvidence(
+            actor_user_id=10,
+            chat_id=-100,
+            topic_id=20,
+            message_id=31,
+            update_id=131,
+        ),
+    )
+    assert completed.state.value == "customer_attestation"
+    answers = service.reconciliation_answers(authority=authority)
+    summary = render_authoritative_customer_summary(answers)
+    service.record_reconciliation(
+        answers_digest=canonical_digest(answers),
+        advisory={"summary_ko": summary},
+        clarifications=[],
+        authority=authority,
+    )
+
+    attested = service.attest_baseline(
+        authority=authority,
+        evidence=onboarding_domain.MessageEvidence(
+            actor_user_id=10,
+            chat_id=-100,
+            topic_id=20,
+            message_id=32,
+            update_id=132,
+        ),
+    )
+    baseline = json.loads(
+        service.baseline_path.read_text(encoding="utf-8")
+    )["baseline"]
+    assert attested.state.value == "owner_review"
+    assert baseline["activity_category"] == "moderate"
+    assert baseline["activity_rationale"].startswith("주 3회, 회당 60분")
+    assert "- 활동: 보통 · 주 3회, 회당 60분" in summary
+    assert "활동 수준:" not in summary
+
+
 @pytest.mark.asyncio
 async def test_continue_later_and_resume_keep_same_domain_session() -> None:
     runtime = object.__new__(TelegramNutritionOnboardingRuntime)
