# RALPLAN Revision 7 — guarded real Korean LLM postprocessor

## Status, provenance, and scope

This is the next full revision of run `019fa7fb-3be3-7000-b23f-03f6c05d81ff`. It supersedes conflicting wording in stage 05 and resolves every stage-06 Architect/Critic finding. The original planner is unavailable (`planner_resumable: true`, `planner_fallback_reason: resume_failed`, `planner_fallback_attempted_id: 328-HumanizerPlannerFallback`, `planner_fallback_stage_n: 1`); this revision is the documented fallback continuation.

**Objective.** Add one guarded, gateway-side postprocessor that lets a real LLM produce natural Korean in small, typed prose slots of final canonical daily, weekly, and adaptive-operator strings. Code remains the sole authority for all facts, values, calculations, decisions, safety, actions, approval/delivery state, identities, layouts, buttons, and canonical fallback.

**Boundaries.** This is default-disabled, presentation-only, one direct-call implementation. It does not activate customers, alter consent/Gate-D, change adaptive proposal/delivery schemas or state machines, add a generic provider abstraction, change `agent.auxiliary_client`, use Kimi/Moonshot/Codex/Anthropic, persist an encrypted presentation, retry a model request, or create a second Telegram delivery path. Execution of this planning task used the allowed OpenAI fallback path only; no Kimi agent or Kimi research/execution path was used.

**Inspected roots.** Product code/tests are in `/home/cube/projects/richard/hermes-agent`; operator guidance is `/home/cube/projects/richard/traning coach/듀얼코치_사용설명서.md`; profile packages and their independent adaptive tests are `/home/cube/.hermes/profiles/{physique-coach,dualcoachtest}/workspace/checkin_cli`. The active Hermes gateway test imports the physique-coach profile (`tests/gateway/test_adaptive_nutrition.py:1-45`). Current adaptive renderers differ materially: dualcoachtest has `현재 판단`/`검토 필요` spans (`dualcoachtest/.../adaptive_nutrition.py:4877-4916`), while physique-coach renders structured `결정`/`근거` lines and final status only (`physique-coach/.../adaptive_nutrition.py:4649-4688`).

## Source facts and root cause

- `TelegramAdapter._nutrition_daily_text(snapshot, feedback)` still accepts arbitrary generated feedback; `_saved_physique_coaching_feedback`, `_generate_physique_coaching_feedback`, `_request_physique_coaching_feedback`, and `_render_physique_feedback_replay` make/reinsert it (`gateway/platforms/telegram.py:5629-5948,6074-6113`). `PhysiqueCheckinConfig.coaching_feedback_enabled` is the old parser flag (`gateway/platforms/physique_checkin_config.py:8-50`).
- Weekly rendering occurs before `template_digest` and `reserve_customer_task_delivery` (`telegram.py:6359-6578`). The existing receipt intentionally exposes only body/destination digests (`customer_schedule.py:89-130`), although the prepared ledger row stores exact body and destination (`customer_schedule.py:1245-1279`). Thus a prepared restart cannot safely reconstruct a selected body from the public receipt.
- Fresh adaptive cards pass through `_handle_adaptive_review_callback` to `mark_publish_pending` before Telegram publication (`telegram.py:7225-7355`; `nutrition_coaching.py:3677-3757`). Recovery republishes that durable `card_payload`; it is the correct persistence boundary.
- `agent.auxiliary_client` deliberately resolves broad providers and normalizes endpoints; it is unsuitable. Hermes pins `openai==2.24.0` (`pyproject.toml:45`) and supports an explicit `max_retries=0` client.

**Root cause.** The daily path grants a free-form model semantic authority after deterministic domain rendering. The replacement is neither a whole-body rewriter nor a finite local variant selector: it is a model-authored bounded-slot composition protocol over code-owned final canonical strings.

## Final canonical surfaces and versioned adapters

Every call begins by rendering a final canonical string. The postprocessor replaces only adapter-declared slot spans; it reconstructs all locked fragments byte-for-byte. Unknown shape, invalid adapter input, unavailable authority, disabled surface, missing credential, request failure, invalid output, busy claim, or corrupt metadata returns canonical. The provider never receives the canonical body, raw customer/session/event/schedule/proposal IDs, values, labels, notes, or prompt history.

| Surface | Final canonical input | Eligible slot(s) | Durable final-body authority |
|---|---|---|---|
| daily | `_nutrition_daily_text(snapshot)` after removing feedback parameter | `daily.status`: the single complete or incomplete status sentence | none; selected text is used only for the successful initial Telegram edit |
| weekly | `_nutrition_report_text(...)` before digest/reservation | `weekly.interpretation` and `weekly.rationale` | existing scheduled-delivery `body`, written by `reserve_customer_task_delivery` |
| adaptive dualcoachtest | fresh `status == "card"` final payload after gateway envelope | whole `현재 판단` line and each existing `검토 필요` reason line | existing `card_payload`, written by `mark_publish_pending` |
| adaptive physique-coach | fresh `status == "card"` final payload after gateway envelope | one gateway-added, presentation-only `검토 안내` line | existing `card_payload`, written by `mark_publish_pending` |

### Daily baseline and placement

Refactor `_nutrition_daily_text` to accept only `snapshot`. It derives `missing` from the seven existing facts and emits exactly one canonical status:

- complete: `저장된 오늘 기록을 확인했습니다.`
- incomplete: `일부 항목이 기록되지 않아 저장된 내용만 안내합니다.`

Titles, seven fact bullets, headings, blank lines, actions, action order, and values remain locked. No complete path may imply quality, adherence, health, praise, or completeness beyond the issued status atom. Delete the five legacy free-form feedback paths and remove/reject `coaching_feedback_enabled` from the physique config parser. The save callback gets one atomic `(snapshot, finalized_event_id)` bridge result, renders canonical, may make one guarded attempt before its first edit, and uses the candidate only in that edit. `_render_physique_feedback_replay` renders canonical directly and never calls the coordinator/provider.

### Weekly placement and prepared recovery

For a **new** weekly task, render canonical report, then attempt/validate the postprocess, then calculate `template_digest` and call existing `reserve_customer_task_delivery` with selected-or-canonical body. Telegram sends only the body durably pinned by that reservation.

Add the smallest trusted profile API to **both** profile copies, exported by each `checkin_cli/__init__.py`:

`load_prepared_customer_task_delivery(profile_root, receipt) -> PinnedScheduledDelivery(body, destination)`.

It accepts only a `ScheduledDeliveryReceipt`, holds the existing schedule lock, validates the ledger/fence/hash chain, resolves the receipt’s current prepared row, verifies exact `body_digest`, `destination_digest`, registry/config pins and task identity, and returns the existing row’s exact body/destination only to the gateway. It is not a generic lookup and writes nothing.

`_send_nutrition_coaching_tick` handles an existing `prepared` weekly receipt before report rendering: load that pinned material, revalidate current authority/destination/pins, mark it `sending`, and send exactly that body. If any check fails, mark unknown and do not send. It makes **zero** postprocessor/provider calls. `sending`, `delivered`, `unknown`, `abandoned`, and `sent_audited` retain their existing no-retry/reconciliation semantics. This closes crash-after-reservation recovery without exposing body text through the bounded receipt or rebuilding a different selected body.

### Adaptive cards: exact supported shapes

`AdaptiveOperatorService.presentation_identity(callback_data, payload)` is a narrow validator for a **fresh** `status == "card"` only. It verifies the live session action, proposal digest, revision, envelope, payload provenance and current authority; internally returns the exact proposal/session/revision/card-kind identity needed for HMAC. It does not expose an identity to the model, telemetry, or user and does not change `NutritionProposal`, proposal body digest, button/session issue rules, terminal state, or recovery.

`AdaptiveSurfaceAdapter` in the gateway owns two frozen signatures. It consumes the complete gateway-enveloped canonical `payload["text"]`, returns a `CanonicalSurface` with locked fragments and slots, and reinserts validated text only before `mark_publish_pending`.

1. **`adaptive.dualcoachtest.card.v1`.** Require the exact envelope separator `\n\n`, title beginning `적응형 영양 검토`, the one `현재 판단: ` line before `\n\n권장안\n\n`, and an optional exact `\n\n검토 필요\n\n` block before either `\n\n상세 근거\n\n` or final `\n\n고객에게는 아직 전달되지 않았습니다.`. The whole judgment line is slot `adaptive.dual.judgment`; each nonempty review line, in source order, is a separate `adaptive.dual.review.N` slot. Goal/range/facts, recommendations, note, customer fields, approval/delivery sentence, envelope, blank lines, and buttons are locked.
2. **`adaptive.physique.card.v1`.** Require the exact envelope separator and the current body grammar: customer/day/D+ first line, `결정:`, `근거:`, optional structured fact/target/cycle/meal/note lines, and exact final `상태: 운영자 승인 전 · 고객에게 자동 전달하지 않음`. The adapter creates the final gateway canonical projection by inserting, immediately before that status line, `검토 안내\n\n제안의 구조화된 항목을 확인한 뒤 승인 여부를 결정하세요.\n\n`. Only the sentence is slot `adaptive.physique.review_instruction`; the heading and every original line stay locked. This is a code-owned presentation paragraph derived from no new proposal field and does not alter proposal or customer-delivery semantics.

Any mismatch—including a future profile rendering, a `view`/menu/terminal payload, malformed envelope, unexpected count/order, or unknown profile shape—uses the supplied canonical payload unchanged and does not open a claim or call OpenAI. Fresh known cards commit selected-or-canonical payload via `mark_publish_pending`; `_recover_pending_adaptive_cards`, `view`, menu, terminal delivery cards, and all recovered/published cards are strict pass-through with zero cosmetic calls.

## Frozen semantic anchors, grammar, request, and validator

### Internal manifest and provider document

Each adapter builds a private manifest, in slot order, from the already rendered canonical string:

- `slot_id`, `grammar_id`, exact byte prefix/suffix, and slot character cap;
- internal semantic/fact/action identifiers and exact NFC Korean expansion for each atom;
- opaque provider anchors `[[S:NN]]`, `[[F:NN]]`, `[[A:NN]]`, or `[[T:NN]]`, numbered only by slot-local ordinal; and
- the exact expected anchor sequence and role (`statement` or `decision`).

Internal semantic/fact/action IDs never leave the process. The model receives only schema, opaque artifact key, surface, slot id, grammar id, anchor strings, and role/order—not canonical text, raw IDs, values, customer data, or notes. This eliminates the prior contradiction over whether raw IDs were sent.

The response is UTF-8 JSON, at most 16 KiB, with exactly this shape and no duplicate keys:

```json
{
  "schema_version": "korean-expression-v3",
  "surface": "daily|weekly|adaptive_operator",
  "artifact_key": "opaque-hmac-key",
  "slots": [{
    "slot_id": "...",
    "grammar_id": "...",
    "anchors": ["[[S:01]]"],
    "generated_ko": "model-authored Korean anchor composition"
  }]
}
```

No `variant_id`, body field, offsets, tool calls, prompt echo, semantic/fact/action IDs, or alternative transport is accepted. A selected result must differ from the canonical slot after expansion; canonical-only anchor output is valid syntax but is finalized as canonical fallback, never misreported as a selected LLM result.

### Exact grammar v1

The validator requires NFC exactly (it does not normalize), printable Unicode only, no control characters, no newline/tab in a slot, no leading/trailing whitespace, and exact ASCII anchor lexemes matching `\[\[(?:S|F|A|T):[0-9]{2}\]\]`. It tokenizes left-to-right: anchors are first-class tokens; every remaining Hangul/space/punctuation byte must be consumed by the grammar below. Raw expansion text, digits, dates, units, Latin words, a customer label, a number sign, brackets other than valid anchors, negation/modality, medical/safety/delivery/approval/action vocabulary, and all unlisted Hangul are rejected.

```
LEAD       ::= "" | "정리하면, " | "확인 결과, " | "기록상, "
JOIN       ::= " 또한 " | " 그리고 "
STATEMENT  ::= <statement-anchor> "."
LIST       ::= LEAD STATEMENT (JOIN STATEMENT)*
DAILY      ::= LEAD <statement-anchor> "."
RATIONALE  ::= LEAD <statement-anchor> "."
JUDGMENT   ::= LEAD "현재 판단은 " <decision-anchor> "입니다."
REVIEW     ::= LEAD <statement-anchor> "."
INSTRUCTION::= LEAD <statement-anchor> "."
```

- `daily.status` uses `DAILY`, exactly one `S` statement anchor.
- `weekly.interpretation` uses `LIST`, one or two ordered `S` statement anchors taken from canonical interpretation sentences; `weekly.rationale` uses `RATIONALE`, exactly one `S` statement anchor.
- `adaptive.dual.judgment` uses `JUDGMENT`, exactly one `T` decision anchor. Each `adaptive.dual.review.N` uses `REVIEW`, exactly one `S` statement anchor.
- `adaptive.physique.review_instruction` uses `INSTRUCTION`, exactly one `A` statement anchor whose expansion is the code-owned canonical instruction.

The grammar deliberately permits the model to author the framing and connective composition as raw Korean rather than choose a local enum or finite `variant_id`; it permits no unanchored proposition. Semantic clauses/decision terms are inserted only from private code-issued anchors, so the model cannot add or change a fact, causality, target, conclusion, action, safety condition, approval/delivery status, date, quantity, or identity.

### Strict validation and reconstruction

Reject invalid UTF-8, UTF-8 BOM, control characters, oversize response, duplicate keys at any JSON depth, any missing/extra/reordered top-level/slot key, non-string/non-list values, boolean-as-int, wrong schema/surface/artifact key, wrong slot count/order/id/grammar, or mismatched returned anchor list. Parse `generated_ko` with the grammar for that exact slot; require issued anchors exactly once, in exact order, only in their assigned slot. Reject missing/duplicate/new/malformed/nested/cross-slot anchors and literal copies of any private expansion.

After expansion, splice slots into the final canonical body and assert every locked fragment—including title, headings, fact lines, values, sections, blank lines, decision/action order, envelope, status, and button payload—is byte-identical. Require Telegram UTF-16 limits after final reconstruction. Any validation failure discards the entire response and selects canonical; no partial slot acceptance, repair, extraction, or retry exists.

Representative valid vectors (the provider never sees the Korean expansions):

| Surface | Provider `generated_ko` | Local expansion/result |
|---|---|---|
| daily | `정리하면, [[S:01]].` | `정리하면, 저장된 오늘 기록을 확인했습니다.` |
| weekly interpretation | `확인 결과, [[S:01]]. 또한 [[S:02]].` | the same two code-issued canonical trend clauses, only reframed/connected |
| dualcoachtest judgment | `기록상, 현재 판단은 [[T:01]]입니다.` | the exact code-issued decision term in the current-judgment line |
| physique-coach instruction | `확인 결과, [[A:01]].` | `확인 결과, 제안의 구조화된 항목을 확인한 뒤 승인 여부를 결정하세요.` |

These vectors prove model-authored Korean differs from canonical without granting semantic authority.

## One positive OpenAI-compatible identity and config parser

`KoreanExpressionConfig.from_extra` in the new gateway module is called by `TelegramAdapter.__init__`; it is the only parser for `config.extra["korean_expression_postprocessor"]`. Absent or invalid mapping is disabled. A present mapping has exactly these keys and literal values:

```json
{
  "schema_version": "korean-expression-v3",
  "mode": "disabled|shadow|enabled",
  "surfaces": {"daily": true, "weekly": true, "adaptive_operator": true},
  "timeout_seconds": 3,
  "provider": {
    "provider_id": "openai-direct",
    "transport": "chat_completions",
    "endpoint": "https://api.openai.com/v1",
    "model": "gpt-4.1-mini"
  }
}
```

All keys are exact; bools must be real bools; `timeout_seconds` must be integer `3`, not bool; strings are ASCII and require exact byte equality (no trim/normalization). The compiled allow-list is the single literal `(openai-direct, chat_completions, https://api.openai.com/v1, gpt-4.1-mini)`. `auto`, a custom/proxy/aggregator endpoint, alternate model, Responses API, OAuth/Codex, Anthropic, Kimi/Moonshot, and all provider pools are invalid configuration, not alternate outcomes. Invalid/disabled/per-surface-off configuration creates no client, claim, provider request, or outcome row; existing downstream durable bodies replay exactly, and otherwise the caller renders canonical.

The sole credential source is process environment `OPENAI_API_KEY`: a nonempty, whitespace-free value read only at the moment the direct client is constructed. The feature never reads config-embedded keys, `OPENAI_BASE_URL`, `OPENAI_API_BASE`, provider pools, OAuth/session credentials, model defaults, or auxiliary credentials; those environment values cannot override the literal endpoint. Missing/invalid `OPENAI_API_KEY` finalizes the authorized metadata attempt as `credential_unavailable` with no HTTP request and returns canonical.

The direct factory imports `openai.OpenAI` directly from the pinned SDK and constructs exactly `OpenAI(api_key=OPENAI_API_KEY, base_url="https://api.openai.com/v1", max_retries=0, timeout=3.0)`. It makes exactly one non-streaming `chat.completions.create` call with `model="gpt-4.1-mini"`, `n=1`, `temperature=0.7`, `max_tokens=384`, `response_format={"type":"json_object"}`, `store=False`, no tools, no `user`, and fixed static instructions plus the redacted manifest. An outer owner deadline is four seconds. There is no SDK retry, application retry, fallback, or secondary client; a late thread result is discarded.

## No presentation persistence; bounded outcome metadata and claim rules

There are **no** sealed/encrypted artifacts, ciphertexts, content digests, prompt/response files, or selected-slot replay store. A selected result is retained only by an already-existing downstream durable final body: weekly reservation `body` or adaptive `card_payload`. Daily has no such body and never gains one. The only new persistence is protected no-content outcome metadata at `data/korean-expression-v1/`.

The root is 0700; `identity.key`, lock, and `outcomes.jsonl` are 0600, owned by the current UID, opened without following symlinks, and directory/file creation is fsynced. `identity.key` is a random 32-byte HMAC key, used solely to hide identity in metadata—not to encrypt any presentation. The artifact key is `HMAC-SHA256(identity.key, b"korean-expression-artifact-v1\\0" || length-prefixed UTF-8 NFC fields)`. Fields are daily `(surface, finalized_event_id)`, weekly `(surface, existing CustomerScheduleTask schedule identity)`, and adaptive `(surface, session, proposal digest, revision, card-kind)`. Raw fields are never stored, logged, telemetered, or sent to OpenAI.

Each canonical JSONL row has exactly `schema_version`, `sequence`, `artifact_key`, `surface`, `mode`, `attempt_id`, `state`, `outcome`, `started_at_utc`, `deadline_at_utc`, `finalized_at_utc`, `previous_row_digest`, and `row_digest`. It contains no body, anchor, prompt, response, customer/address/value, provider/model/endpoint, exception, or content-derived digest. Canonical JSON is NFC/sorted/no-whitespace UTF-8 plus one LF; `row_digest` is SHA-256 of the row without itself, chained by `previous_row_digest`. Any inaccessible, malformed, torn, replaced, permission-invalid, sequence-invalid, or chain-invalid metadata state is canonical-only with zero provider calls.

Allowed states/outcomes are exact:

- started: `state="started", outcome="attempt_started"`.
- final: `selected`, `shadow_selected`, `credential_unavailable`, `provider_error`, `timeout`, `invalid_response`, `validation_rejected`, `commit_failed`, `publication_failed`, or `claim_expired_canonical`.

There is no `provider_disallowed`; disallowed configuration is disabled before authorization. There is no encrypted/`sealed-v1` presentation state.

### One-attempt transaction and crash behavior

1. Before one wire attempt, an eligible enabled/shadow owner acquires the metadata lock, appends/fsyncs `attempt_started` with random `attempt_id` and four-second deadline, then releases the lock. A terminal outcome for that artifact means zero future calls.
2. A concurrent renderer that finds an unexpired started row makes no call, does not terminalize the healthy owner, and returns `in_flight` with `publication_permitted=false`. The duplicate callback/tick must not edit, reserve, or send a competing canonical body.
3. The owner alone accepts a response only if its `attempt_id` is still started and deadline has not elapsed. It validates the candidate in memory; no candidate is persisted by this feature. It applies a validated candidate to the existing downstream durable writer before finalizing `selected` (weekly `reserve_customer_task_delivery`; adaptive `mark_publish_pending`). Daily finalizes `selected` only after its first Telegram edit succeeds. Writer/edit failure finalizes `commit_failed`/`publication_failed`, never publishes an unpinned selected body.
4. If the process crashes at any point before a durable body is written, the started row survives but no output survives. A restart before expiry returns in-flight/no publication; at expiry it appends `claim_expired_canonical`, makes no call, and may complete the initial operation with canonical only. If weekly/adaptive durable body was written before crash, that body wins and recovery uses it exactly even if the outcome row never reached `selected`.
5. An owner request timeout finalizes `timeout` and may use canonical for its initial operation. A late worker conditionally observes its own nonterminal row; after terminal/deadline it discards the late result and cannot write a selected body. Shadow validates once and finalizes `shadow_selected` but always applies canonical.

Thus every artifact receives at most one authorized direct model attempt. Every replay/recovery lacking an already durable final body is canonical-only and makes zero provider calls; any existing weekly/adaptive durable final body is sent/recovered exactly as stored.

Telemetry is an optional nonblocking sink containing only `event`, `contract_version`, `surface`, `mode`, `outcome`, and coarse `latency_bucket`. It never includes identity key/artifact key, body, prompt, response, anchor/semantic IDs, ciphertext/digest, customer/address/value, model identity, exception, or length. Sink failure cannot alter rendering.

## Implementation sequence and bounded file set

1. Add `/home/cube/projects/richard/hermes-agent/gateway/platforms/korean_expression.py`: exact config parser/allow-list, final-string adapters, private manifest, grammar tokenizer/validator, direct OpenAI one-wire factory, no-content outcome claim journal, and coordinator. It imports neither profile-local renderers nor `agent.auxiliary_client`.
2. Modify Hermes `gateway/platforms/physique_checkin_config.py` and `physique_checkin.py`: remove/reject `coaching_feedback_enabled`; add atomic finalized coaching artifact accessors returning snapshot plus finalized event identity. Modify `gateway/platforms/telegram.py`: construct config/coordinator, replace old daily feedback, apply weekly before reserve, implement existing-prepared weekly reader path, and adapt only fresh validated adaptive cards before `mark_publish_pending`.
3. Modify Hermes `gateway/platforms/nutrition_coaching.py` only to add `presentation_identity` validation for fresh card payloads. Do not modify proposal schemas, adaptive canonical event data, delivery authority, buttons, or recovery state machine.
4. Modify **both** `/home/cube/.hermes/profiles/{physique-coach,dualcoachtest}/workspace/checkin_cli/checkin_cli/customer_schedule.py` and `__init__.py` solely for `PinnedScheduledDelivery` and `load_prepared_customer_task_delivery`. No new profile store/schema is introduced.
5. Update only `/home/cube/projects/richard/traning coach/듀얼코치_사용설명서.md`; leave pinned `PILOT_RUNBOOK.md` unchanged. Document disabled/shadow/enabled, one direct identity/no retry, no content persistence, recovery, and which fresh cards are eligible.

## Verification and release gates

### Focused unit/integration coverage

Add `/home/cube/projects/richard/hermes-agent/tests/gateway/test_korean_expression.py` with:

- daily complete/incomplete, weekly, and final adaptive canonical goldens; accepted model strings and exact reconstructed selected text; all locked prefix/suffix/heading/blank/bullet/value/button/envelope invariants;
- explicit dualcoachtest-v1 and physique-coach-v1 adapter goldens, selected cases, canonical fallback cases, unknown-shape/no-call cases, and the physique gateway-only `검토 안내` projection;
- parser/grammar negatives: non-JSON/fences/prefix/suffix, duplicate keys at every level, invalid UTF-8/BOM/controls/NFC, oversize, bool/type confusion, extra/missing/reordered fields/slots/anchors, raw expansion, raw number/date/unit/name, malformed/nested/cross-slot/missing/duplicate/reordered anchors, a `variant_id`, canonical-only selection, unknown Hangul/Latin, negation/modality, action/safety/delivery/approval vocabulary, and locked layout changes;
- metadata path/permission/symlink/torn/chain/key failures; assert no provider call and no content persistence; fixed HMAC identity vectors proving raw authoritative IDs are absent;
- in-flight claim traces: two concurrent owners (exactly one request; loser does not terminalize/publish), live owner completion, crash-before-response then restart-before-deadline, crash-after-deadline canonical recovery, timeout/late response discard, valid candidate crash before weekly/adaptive durable write, crash after durable write before metadata finalization, and terminal replay. Assert zero extra calls and exact durable body reuse;
- direct fake HTTP tests for one request on network/429/5xx/timeout, `max_retries=0`, exact literal endpoint/model/request parameters, sole credential behavior, absent key, all invalid provider/model/endpoint inputs, and no auxiliary resolver/log/telemetry content.

Update `tests/gateway/test_telegram_physique_checkin.py` for the daily cutover, obsolete key rejection, atomic bridge identity, selected/canonical/disabled/replay behavior, and unchanged active-turn paths. Update Hermes `tests/gateway/test_adaptive_nutrition.py` and `test_telegram_group_gating.py` for fresh-card selected/canonical persistence, callback races, terminal/recovery zero-call behavior. Update each profile suite at `/home/cube/.hermes/profiles/{physique-coach,dualcoachtest}/workspace/checkin_cli/tests/test_adaptive_nutrition.py` with its source-renderer golden, preserving the independently versioned shape captured by the gateway adapter tests. Add the narrow schedule API tests in both profile suites and a Hermes weekly crash-after-prepared test that asserts exact stored selected bytes, no LLM call, and one Telegram send authority.

Keep `tests/agent/test_auxiliary_client.py` and `tests/agent/test_moonshot_schema.py` unchanged unless a direct non-call assertion is needed; the feature must not change their production code. Add a non-default `live_korean_expression` pytest marker in Hermes `pyproject.toml`; it is excluded from default test selection.

Run from `/home/cube/projects/richard/hermes-agent`:

```text
pytest -q tests/gateway/test_korean_expression.py tests/gateway/test_telegram_physique_checkin.py tests/gateway/test_adaptive_nutrition.py tests/gateway/test_telegram_group_gating.py tests/gateway/test_nutrition_coaching.py
pytest -q /home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli/tests/test_adaptive_nutrition.py
pytest -q /home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli/tests/test_adaptive_nutrition.py
```

### Controlled real-model shadow proof

Add `tests/gateway/test_korean_expression_live_shadow.py`, marked `live_korean_expression` and skipped unless both `RUN_KOREAN_EXPRESSION_LIVE=1` and `OPENAI_API_KEY` are present. It invokes the coordinator directly—not `TelegramAdapter`—with three synthetic daily/weekly/adaptive manifests and synthetic local expansions. It sets a temporary profile root, has no Telegram token/client, monkeypatches every Telegram transport constructor/send function to raise, and permits only the direct OpenAI test request. It requires one `shadow_selected` result for each surface, captures stdout/stderr/logging/telemetry, and asserts they contain neither model output nor any manifest expansion; it reports only pass/fail surface names. It never calls `mark_publish_pending`, `reserve_customer_task_delivery`, or Telegram.

Run only intentionally:

```text
RUN_KOREAN_EXPRESSION_LIVE=1 pytest -q -m live_korean_expression tests/gateway/test_korean_expression_live_shadow.py
```

Promotion is manual: default is absent/disabled; require all normal tests plus the controlled three-surface proof before `shadow`; retain shadow until each enabled surface has at least one bounded `shadow_selected` outcome and no validation/provider/timeout outcome in the release observation window; enable one surface at a time. Rollback is `mode: disabled`: it makes no new calls, leaves already durable selected weekly/adaptive bytes untouched, and uses canonical where no final body exists.

## Deterministic attribution

The guide receives one non-executable attribution paragraph with immutable sources:

- Kiro reference: `https://github.com/Gaeduck-0908/im-not-ai-kiro/tree/901e378ae7f77035d17a491067343ac3f83d0214`
- original reference: `https://github.com/epoko77-ai/im-not-ai/tree/53e24e8f92cf344efcb812103f7c2b203e7efffc`

It states only that those MIT-licensed projects were consulted for the high-level goal of preserving meaning while improving presentation; this implementation independently defines its gateway adapters, grammar, validation, persistence, provider, and rollout contracts. It explicitly says no upstream source code, prompts, rules, workflow, or artifacts are copied, bundled, or claimed verbatim. Consequently the guide does **not** reproduce either full MIT notice, copyright lines, or `SOFTWARE` text. The documentation test asserts both immutable links and that exact non-copy statement; it does not assert any omitted license text.

## Acceptance criteria

- Default-disabled output is byte-identical canonical on all surfaces.
- A real model can produce a validated, non-canonical Korean composition in bounded slots on daily, weekly, dualcoachtest adaptive, and physique-coach adaptive cards; no finite local selector/variant ID exists.
- Both current adaptive schemas have versioned golden/selected/fallback coverage; any unknown shape is canonical/no-call.
- Facts, numbers, dates, names, decisions, actions, safety, approval/delivery state, layout, envelope, and buttons remain code-owned and byte-locked.
- Exactly one direct OpenAI request maximum per artifact identity; zero retries/fallbacks/Kimi/auxiliary routes; no request on invalid/disabled configuration, replay, recovery, in-flight loser, or unknown adaptive shape.
- There is no encrypted or other feature-owned presentation store. Only existing weekly/adaptive durable final bodies replay; all other recovery is canonical with zero provider calls.
- Prepared weekly recovery sends the exact pinned body through existing authority, never recomputes/reprocesses it.
- The controlled real-model shadow proof sends no Telegram message and emits no content-bearing logs or telemetry.
