# Revised RALPLAN — bounded Korean prose postprocessor v3

## Objective, non-goals, and approval boundary

Implement a real post-processing LLM for natural Korean on all three requested surfaces: finalized **daily** check-in prose, scheduled **weekly** report prose, and new **adaptive operator** proposal-card prose. This supersedes the prior finite local `variant_id` selector: a successful model response must contain generated Korean slot text/anchor composition, never a local alternative ID. Code remains the sole authority for facts, calculations, decisions, actions, follow-up, safety, approval/delivery state, destination, buttons, sections, and canonical fallback.

The feature is bounded, default-disabled, and presentation-only. It does not activate customers, change consent, delivery/Gate-D, feature epochs, adaptive proposal schemas, generic ingress, or unrelated active-turn/conversation model calls. It never retries. A failure, invalid output, untrusted persistence, unavailable approved provider, or deadline returns the already-rendered canonical body.

**Execution-provider clarification.** The user’s “OpenAI fallback agents only; no Kimi” instruction governs this planning execution: this revision used no Kimi agent or Kimi research path. Production is independently narrower, not broader: it accepts exactly one explicitly configured, positive allow-listed **direct OpenAI Chat Completions** identity and otherwise uses canonical fallback. There is no provider chain, auto resolution, custom endpoint, Codex, Anthropic, OpenRouter, proxy, Kimi/Moonshot, or model fallback implementation.

## Inspected current seams and root cause

- `gateway/platforms/telegram.py:_nutrition_daily_text(snapshot, feedback)` currently injects `feedback` through `_nutrition_daily_interpretation`; `_saved_physique_coaching_feedback`, `_generate_physique_coaching_feedback`, `_request_physique_coaching_feedback`, and `_render_physique_feedback_replay` can generate/reinsert arbitrary prose. The five-token filter in `_nutrition_daily_interpretation` is not a semantic boundary.
- The current daily canonical fixture in `tests/gateway/test_telegram_physique_checkin.py:TestNutritionCopySurfaces` relies on generated two-line feedback. Its missing-data line is already deterministic. A complete-data deterministic baseline must be introduced before enhancement so fallback is defined.
- `_send_nutrition_coaching_tick` renders the weekly `body`, calculates `template_digest`, calls `reserve_customer_task_delivery`, then gets the sole Telegram authority. Any enhancement must finish before the digest/reservation; retries must use the existing delivery row and make no prose call.
- `checkin_cli.adaptive_nutrition._make_proposal` persists `NutritionProposal.operator_body` and `operator_body_digest`; `AdaptiveOperatorService.mark_publish_pending` persists the exact `card_payload`; `TelegramAdapter._recover_pending_adaptive_cards` publishes that persisted payload. Enhancement must therefore happen only on a fresh `status == "card"` payload immediately before `mark_publish_pending`; recovery must be pass-through and never call the LLM.
- `PhysiqueCheckinConfig.coaching_feedback_enabled` is actually declared and parsed in `gateway/platforms/physique_checkin_config.py`, not in `telegram.py`; the obsolete field must be removed there and its old key rejected.
- `agent.auxiliary_client.resolve_provider_client` intentionally has auto/fallback/wrapper behavior, `openai==2.24.0` currently has default retries unless overridden, and the Anthropic adapter may stream then create. Reusing it would violate this feature’s one-attempt/direct-OpenAI boundary; this plan deliberately leaves it, `agent/moonshot_schema.py`, and Anthropic/Codex paths unchanged.

**Root cause:** the current daily free-form response receives semantic authority after domain rendering. The prior revision removed that authority by reducing the LLM to a local variant selector, but that fails the explicit product intent for an actual natural-Korean postprocessor across daily, weekly, and adaptive prose. The correct boundary is generated prose constrained by a typed, code-issued semantic manifest—not a whole-body rewriter and not an enum selector.

## Canonical baselines and surface contract

Render canonical content before constructing any request. Canonical is the only fallback and is independently golden-tested.

| Surface | Canonical artifact | Eligible generated prose slots | Immutable material kept out of slots |
|---|---|---|---|
| daily | `TelegramAdapter._nutrition_daily_text(snapshot)` | one interpretation/status paragraph between fact bullets and `오늘 할 일` | title, seven fact lines, headings, blank lines, action bullets/order, data values |
| weekly | `TelegramAdapter._nutrition_report_text(...)` | `weekly.interpretation` and `weekly.rationale` paragraphs | title, fact bullets, check-in rate/goal lines, judgment line, action bullets/order, boundaries |
| adaptive_operator | fresh `status == "card"` text returned by `AdaptiveOperatorService.handle_callback`, before `mark_publish_pending` | existing explanatory/current-judgment and review-reason prose spans only | envelope (`테스트 전용`, customer label, KST day, revision, state), title, facts, decision, recommendation bullets, delivery/approval wording, buttons/callbacks/layout |

### Daily cutover

Refactor `_nutrition_daily_text` to take only `snapshot`; calculate `missing` from the existing seven rendered facts and call `_nutrition_daily_interpretation(missing)`. Freeze these local literals:

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

The complete canonical output is the existing title, seven fact bullets, the complete literal, `오늘 할 일`, and the existing deterministic actions with the same `\n\n` boundaries. The incomplete fixture stays byte-identical. This is the baseline, not a “warm” assertion: no accepted text may say `빠짐없이`, `잘`, `충분`, `성공`, `좋`, or otherwise claim completeness, quality, adherence, effort, or health unless that exact assertion is a code-issued semantic atom (none is in v1).

Delete `_saved_physique_coaching_feedback`, `_generate_physique_coaching_feedback`, and `_request_physique_coaching_feedback`; do not route the new feature through `_request_physique_coach_completion`. `_render_physique_callback_prompt` uses `PhysiqueCheckinBridge.finalized_coaching_artifact(session_id)` to obtain one validated `(snapshot, finalized_event_id)` pair, renders canonical daily text, then invokes the bounded postprocessor. `_render_physique_feedback_replay` becomes replay-only: it obtains the same artifact identity and returns its stored accepted presentation or canonical fallback, never another provider call. Existing active-turn and conversation helpers retain their current 30-second behavior and are not called by this feature. Already-sent messages are not rewritten.

### Weekly and adaptive placement

For weekly tasks, derive the canonical report first, postprocess it once, and only then calculate `template_digest` and reserve it. Existing `prepared`/`sending`/`delivered`/`unknown` rows continue to short-circuit before rendering or provider activity. The scheduled-delivery ledger persists the final selected-or-canonical `body` as it already does; the new postprocessor never sends Telegram.

For adaptive cards, add a narrow `AdaptiveOperatorService.presentation_identity(callback_data, payload)` in `gateway/platforms/nutrition_coaching.py`. It validates the authoritative session, proposal digest/revision, and fresh `status == "card"` payload, and returns a private identity derived from that existing card/session state; it exposes neither customer data nor text to users or telemetry. `TelegramAdapter._handle_adaptive_review_callback` applies the postprocessor only after this identity validates and before `mark_publish_pending`; it preserves `envelope`, `buttons`, and every non-slot byte. The selected/canonical text is then what `mark_publish_pending` durably pins. `_recover_pending_adaptive_cards`, terminal delivery cards, `view`/menu cards, `adaptive_delivery_result_text`, and published-card recovery never invoke it: they reuse their already persisted or terminal exact bytes. This gives the requested adaptive operator prose real postprocessing without altering approval/delivery semantics or introducing a second delivery path.

## Typed generated-prose protocol and deterministic semantic validator

Add `gateway/platforms/korean_expression.py` as shared Hermes code (not either profile-local `checkin_cli` package). It provides frozen/slotted data types: `KoreanExpressionConfig`, `CanonicalSurface`, `ProseSlot`, `SemanticAtom`, `ImmutableToken`, `PostprocessDocument`, `ValidatedPresentation`, `KoreanExpressionStore`, and `KoreanExpressionCoordinator`.

### Private manifest and redacted model document

Each code-built `ProseSlot` contains, in canonical slot order:

- `slot_id`, `surface`, `section_index`, and an exact allowed slot boundary;
- ordered code-issued `semantic_ids` for each canonical assertion, decision, action, follow-up, safety, approval, or delivery statement that its prose could refer to;
- ordered `fact_ids` and `action_ids` in scope;
- a private `immutable_tokens` manifest: `{token_id, kind, exact_nfc_text, source_semantic_id}` for every number, date, measurement/unit, proper/customer label, decision, action, follow-up, safety, approval/delivery phrase, heading, and canonical proposition that must not change;
- the canonical slot prose and a no-new-claim grammar class.

The provider receives a bounded JSON document with the same IDs and opaque anchors (`[[S:id]]`, `[[F:id]]`, `[[A:id]]`, `[[T:id]]`) but **not** the private exact token strings, body, customer label, facts, prompt history, IDs, or canonical full text. The locally held manifest is what “carries exact immutable tokens”; render substitutes anchors only after validation. This removes customer values from the provider request while still forcing exact rendered tokens.

The response is one UTF-8 JSON object, maximum 16 KiB, with exactly:

```json
{
  "schema_version":"korean-prose-postprocess-v2",
  "surface":"daily|weekly|adaptive_operator",
  "artifact_key":"opaque code-issued key",
  "slots":[{
    "slot_id":"...",
    "section_index":0,
    "semantic_ids":["code-issued, ordered"],
    "fact_ids":["code-issued, ordered"],
    "action_ids":["code-issued, ordered"],
    "anchor_ids":["code-issued, ordered"],
    "generated_ko":"Korean prose containing only anchor references for content"
  }]
}
```

There is no `variant_id`, no free body replacement, no offsets from the model, no tools, no text echo, and no alternate transport. At least one eligible slot must have non-canonical generated composition to count as `selected`; a `variant_id` field, canonical-only selector response, or empty slot list is rejected rather than silently treated as an LLM result.

### Validator rules

Use a duplicate-key rejecting `json.loads(..., object_pairs_hook=...)`, reject invalid UTF-8/control characters/oversize input before parse, and require exact top-level and slot key sets, non-bool types, exact version/surface/artifact key, exact slot count/order/IDs/section indices, and byte-for-byte ordered equality of all returned semantic/fact/action/anchor IDs. No normalization, repair, partial acceptance, or best-effort extraction is allowed.

For each slot, parse anchors without rendering them. Require every issued anchor exactly once in its assigned slot; reject any missing, new, duplicate, cross-slot, reordered, malformed, nested, or literal-copy anchor. Substitute only the private NFC token tied to that anchor; reject if it would produce a different token or if any raw numeric/date/percent/unit/proper-token text appears outside an anchor. Rebuild the full body from locked non-slot fragments plus rendered slots and compare titles, section order, headings, blank lines, bullets, fact/action order, buttons, envelope, and all locked text byte-for-byte.

A closed deterministic Korean **surface grammar** governs non-anchor text. It permits only punctuation/whitespace, honorific endings, particles, neutral conjunctions and presentation verbs already mapped to the slot’s declared semantic IDs; it contains no evaluative, causal, medical, target, recommendation, temporal, delivery, approval, follow-up, safety, or action vocabulary. The grammar also defines which semantic atom may introduce each syntactic role. Therefore an assertion cannot be created by unanchored Korean: every content-bearing proposition is an issued `semantic_id`, and every value/status/action is an exact local anchor. Any unclassified Hangul/Latin lexical token, negation/modality change, new quantity/date, new decision/action/follow-up/safety/approval/delivery phrase, unsupported claim, or format change rejects the whole document to canonical.

This is deliberately more expressive than a finite local selector—the LLM must compose generated Korean across dynamic typed slots—but it is not a free-form semantic author. Expanding the neutral grammar or semantic atom inventory requires code review and golden tests, not prompt changes.

## Configuration and one allowed production identity

`KoreanExpressionConfig.from_extra` in the new `gateway/platforms/korean_expression.py` is the actual parser invoked by `TelegramAdapter.__init__` into `_korean_expression_config`. It alone parses `config.extra["korean_expression_postprocessor"]`; absent configuration is exactly disabled.

A present mapping must have exactly these keys and valid values:

```json
{
  "schema_version":"korean-prose-postprocess-v2",
  "mode":"disabled|shadow|enabled",
  "surfaces":{"daily":true,"weekly":true,"adaptive_operator":true},
  "timeout_seconds":3,
  "provider":{"provider_id":"openai","base_url":"https://api.openai.com/v1","transport":"chat_completions","model":"an explicit release-approved model"}
}
```

`surfaces` must have exactly those three actual booleans; timeout is an actual integer (not bool) 1–5; all strings have bounded ASCII/NFC length; unknown/missing keys and every invalid value fail closed to disabled. The model name is a literal deployment configuration value, not defaulted or normalized through the main model or auxiliary resolver. The compiled positive identity policy permits only `provider_id == "openai"`, canonical HTTPS `https://api.openai.com/v1` (no query, fragment, alias, port, or override), and `transport == "chat_completions"`; the configured model is the one allowed model for that deployment. Any other provider/model route—including `auto`, custom, proxy, aggregator, Codex Responses, Anthropic Messages, Kimi/Moonshot aliases/hosts/models—returns `provider_disallowed` before constructing a client. This is a single configured identity, not multi-provider infrastructure.

- **disabled:** no new decision, client resolution, request, or content persistence; a previously finalized presentation is replayed immutably for the same artifact.
- **shadow:** one validated request may occur for a new artifact, but canonical bytes are delivered; `shadow_selected` is terminal and can never later apply.
- **enabled:** one validated request may produce and persist a presentation; any failed result remains canonical.

Remove `coaching_feedback_enabled` from `PhysiqueCheckinConfig` and reject a supplied obsolete key in `PhysiqueCheckinConfig.from_extra`, requiring configuration cleanup rather than leaving a hidden legacy route. Add focused regression coverage in `tests/gateway/test_telegram_physique_checkin.py` for both removal and exact parser behavior.

## Direct one-wire client, privacy, and durable artifact semantics

Do not modify or call `agent.auxiliary_client`, `agent.moonshot_schema`, Codex, or Anthropic adapters. The new module has a small direct OpenAI factory that reads only the validated identity and approved credential source, constructs `openai.OpenAI(api_key=..., base_url="https://api.openai.com/v1", max_retries=0)`, and performs exactly one `chat.completions.create` with a bounded timeout, no tools, no provider fallback, and no application retry. The static instructions and redacted anchor document request the one JSON object above. `asyncio.to_thread` has one owner deadline; the inner request timeout is `timeout_seconds`, the outer deadline is `timeout_seconds + 0.25`, and a timeout best-effort closes the client.

Use one private per-profile store at `data/korean-expression-v1/`: a random 32-byte 0600 HMAC key, `decisions.jsonl`, a lock, and sealed presentation artifacts. The decision ledger is metadata only, never body/prompt/response/customer data. `artifact_key` is `HMAC-SHA256(profile_key, canonical artifact identity)`; raw daily event ID, schedule key/customer key, adaptive session/proposal ID, text, hash-of-text, address, numeric value, exception text, and provider identity are never persisted in it or telemetry.

Artifact identities are proportional to the existing authority: daily `finalized_event_id`; weekly existing `CustomerScheduleTask` schedule identity; adaptive the validated existing session/proposal/revision/card-kind identity. Each is HMACed before any persistence. Corrections receive a new finalized event identity. A weekly retry with an existing delivery row and adaptive recovery use their pinned output and create no new claim/call.

### Frozen decision-ledger v1

Every JSONL row is canonical JSON (NFC, sorted keys, `,`/`:` separators, UTF-8, exactly one LF) with exactly:

`schema_version`, `sequence`, `artifact_key`, `surface`, `mode`, `state`, `outcome`, `presentation_state`, `occurred_at_utc`, `previous_row_digest`, `row_digest`.

- `schema_version` is exactly `korean-prose-decision-v1`.
- `sequence` is contiguous positive integer; `artifact_key`, `previous_row_digest` (empty only for row 1), and `row_digest` are lower-case 64-hex.
- `surface` is `daily|weekly|adaptive_operator`; `mode` is `shadow|enabled`; `state` is `attempt_started|finalized`.
- `outcome` is exactly `attempt_started|selected|shadow_selected|provider_disallowed|provider_error|timeout|invalid_response|validation_rejected|claim_incomplete_canonical|presentation_unavailable` and is compatible with state.
- `presentation_state` is `none|sealed-v1`; only finalized `selected` uses `sealed-v1`.
- `occurred_at_utc` is UTC RFC3339 `YYYY-MM-DDTHH:MM:SS.ffffffZ`.
- `row_digest = SHA256(UTF-8 canonical JSON of the row with `row_digest` omitted)`. `previous_row_digest` is the prior full row digest.

Validate all path components with no symlinks, owner/current UID, root directories 0700, files/lock/key 0600, contiguous sequence, digest chain, exact field/type/enum/UTC format, and one terminal state per artifact. Lock with `flock`, append one row, `fsync` file and parent directory. Corrupt, torn, replaced, unavailable, or untrusted state makes no request and returns canonical.

The accepted presentation itself is not a log: it is an encrypted, 0600, one-artifact delivery/replay object needed to reproduce the customer/operator-visible result. Its sealed plaintext is only the validated slot composition plus schema/surface/artifact key; it contains no prompt/raw response, and it is revalidated after decryption before render. The decision ledger and telemetry never store it. Write and fsync the sealed artifact before appending `selected`; missing/corrupt sealed output replays canonical without retry. Shadow never writes a presentation artifact.

State transition: lock winner writes/fsyncs `attempt_started` before the sole wire request. The owner terminalizes its own deadline as **`timeout`**. A concurrent renderer or restart observing an unfinalized start terminalizes it as **`claim_incomplete_canonical`** without waiting or sending. A late worker conditionally observes the terminal row, discards its result, and cannot overwrite it. This resolves the earlier contradiction: `claim_incomplete_canonical` is only abandoned/in-flight observation; owner deadline is always `timeout`.

Telemetry is an injectable nonblocking sink with exactly `event`, `contract_version`, `surface`, `mode`, `outcome`, and `latency_bucket`. It contains no body, prompt, response, token/semantic ID, identity, artifact key, text/ciphertext digest, customer/address, value, exception string, or raw length. Sink failure is ignored after preserving rendering semantics.

## File-level implementation sequence

1. **Add `gateway/platforms/korean_expression.py`.** Implement the frozen config parser, manifest/anchor grammar, strict response parser, locked renderer, direct one-wire OpenAI client, HMAC-keyed decision/presentation store, mode/replay coordinator, and enum-only telemetry. It imports no profile-local package and no broad auxiliary provider router.
2. **Modify `gateway/platforms/physique_checkin_config.py`.** Remove the dataclass field and parser use of `coaching_feedback_enabled`; reject its presence. **Modify `gateway/platforms/physique_checkin.py`.** Add validated `finalized_coaching_artifact(session_id)` plus active/latest counterparts returning snapshot and final event identity together, without exposing that identity to user text or the provider.
3. **Modify `gateway/platforms/telegram.py`.** Parse `_korean_expression_config` in `__init__`; remove legacy daily feedback methods/calls; create canonical daily then postprocess/replay by final event; postprocess weekly before digest/reservation; postprocess only fresh validated adaptive `card` payload before `mark_publish_pending`; retain exact recovery/terminal behavior and all active-turn/conversation paths. Do not add this setting to adaptive `_FEATURE_FLAG_NAMES` or mutate customer delivery authority.
4. **Modify `gateway/platforms/nutrition_coaching.py`.** Add only `AdaptiveOperatorService.presentation_identity` validation needed for a fresh card’s existing session/proposal/revision identity. Do not alter `NutritionProposal`, `operator_body_digest`, adaptive event schema, buttons, terminal delivery state machine, or recovery transport.
5. **Update `듀얼코치_사용설명서.md` only as non-executable operator guidance.** Replace the daily generated-feedback example with the canonical baseline; document disabled/shadow/enabled, direct-OpenAI-only/no-retry, immutable/replay boundaries, privacy telemetry, and that actual adaptive proposal cards—not terminal delivery cards—are eligible. Keep `PILOT_RUNBOOK.md` unchanged because its pinned executable contract is not modified.
6. Add an attribution subsection in that guide naming `Gaeduck-0908/im-not-ai-kiro` and upstream `epoko77-ai/im-not-ai`, stating that this work was independently designed using high-level preservation principles and **does not claim verbatim copying of rules, code, workflow, or artifacts**. If retaining the upstream MIT notice, reproduce the Kiro `master/LICENSE` notice byte-for-byte, including both copyright lines and its final `SOFTWARE` (no invented terminal period); cite source URL/commit. Do not import its filesystem persistence, multi-agent/retry workflow, or raw input/output logging.

## Concrete acceptance outputs

All examples retain the current headings, blank lines, bullets, numbers, decisions, actions, and status tokens exactly; only designated prose slots differ from the canonical baseline after a validated `enabled` call.

**Daily (enabled selected):** facts and actions remain the existing fixture values; canonical `저장된 오늘 기록을 확인했습니다.` may render as `오늘 기록을 확인했습니다.` under the issued `daily.record_confirmed` semantic atom. It must not add praise/completeness wording, change `2,675kcal`, `2.8L`, or add an action.

**Weekly (enabled selected):** the fixture retains `+0.16%`, `+0.10~+0.25%`, `이번 주 판단: 유지`, all four action bullets, and rationale meaning. A valid generated prose result is `이번 주 체중 증가는 목표 범위 안에서 안정적으로 이어졌습니다. 현재 속도에서는 불필요한 체지방 증가 위험이 높지 않습니다.` followed by `급격한 증량이나 정체가 없으므로, 이번 주에는 칼로리를 변경하지 않습니다.`; the changed clauses must be bound to the existing issued semantic atoms and no new action/claim is accepted.

**Adaptive operator (enabled selected fresh card):** `테스트 전용`, customer label, `2026-07-27`, revision/state envelope, `+0.16%`, goal range, `현재 판단`, every recommendation bullet, `검토 필요`, and `고객에게는 아직 전달되지 않았습니다.` remain exact. A valid slot can render the existing current-judgment/review-reason propositions in smoother Korean, but cannot change a recommendation, approval/delivery state, or button. The exact selected text is persisted by `mark_publish_pending` and recovery republishes those bytes with zero LLM calls.

For disabled, shadow, malformed, timeout, rejected provider, corrupted store, and incomplete claim, the outputs above are the exact canonical daily/weekly/adaptive bodies; shadow never displays selected text.

## Verification and release gates

### New focused tests: `tests/gateway/test_korean_expression.py`

- Golden canonical daily complete/missing, weekly, and adaptive-card bodies; accepted outputs for all three surfaces; exact prefix/suffix/section/bullet/blank-line/button/envelope invariants.
- Parser attacks: non-JSON/fences/prefix/suffix, duplicate keys at every depth, invalid UTF-8/control bytes, oversize, type confusion/bools, missing/extra/reordered slot/IDs, raw token text, malformed/nested/cross-slot anchors, duplicate/missing/new anchors, selector-only `variant_id`, and canonical-only selector output. Each returns canonical and preserves no untrusted text.
- Semantic attacks: changed number/date/unit, negation/modality, decision/action/follow-up, safety/medical language, approval/delivery state, customer label, unsupported praise/completeness, unclassified Korean word, new causal assertion, heading/section/bullet/format change. Assert deterministic rejection.
- Store hash-chain fixed vectors; field/enum/key/timestamp failures; torn/corrupt/permission/symlink/replaced inode; sealed artifact missing/corrupt; no provider call for all. Assert HMAC keys reveal neither input identity nor text.
- Concurrent owner/renderers, restart after claim, crash after request-before-finalization, timeout plus late response, selected replay, shadow replay, disabled replay, daily correction new identity, weekly existing-delivery replay, and adaptive persisted-card recovery. Assert one wire attempt per existing artifact identity and the singular `timeout` versus `claim_incomplete_canonical` outcomes.
- Fake OpenAI HTTP boundary asserts exact one request for 429/5xx/network failure/timeout, `max_retries=0`, direct allowed base URL only, no client construction for `auto`, custom, proxy, Codex, Anthropic, OpenRouter, Kimi/Moonshot labels/hosts/models, and no request/body data in logger or telemetry capture.

### Existing seam tests

- Extend `tests/gateway/test_telegram_physique_checkin.py`: remove feedback fixture/helper expectations; assert canonical daily cutover, bridge artifact ID access, obsolete parser-key rejection, valid selected daily prose, canonical failure paths, and unchanged active-turn/conversation calls. For weekly, assert enhancement precedes template digest/reservation, the stored delivery body equals selected/canonical body, one Telegram provider call across duplicate/restart/timeout, and no prose retry.
- Extend `tests/gateway/test_adaptive_nutrition.py` and `tests/gateway/test_telegram_group_gating.py`: fresh card selected/canonical payload passes `mark_publish_pending`, all envelope/buttons/terminal strings survive byte-for-byte, one claim under callback races, and `recover_pending_cards[_async]` invokes the cosmetic provider zero times.
- Extend `tests/agent/test_auxiliary_client.py` only with a non-regression proving the postprocessor does not call the auxiliary resolver; do not alter provider router behavior. Keep `tests/agent/test_moonshot_schema.py` unchanged except optional negative integration assertion through the new direct factory.
- Run focused tests; `tests/gateway/test_telegram_physique_checkin.py`, `tests/gateway/test_adaptive_nutrition.py`, `tests/gateway/test_telegram_group_gating.py`, and `tests/gateway/test_nutrition_coaching.py`; both profile `test_adaptive_nutrition.py` suites; scheduled-delivery/P2–P6 regression coverage; and a subprocess import test proving `gateway.platforms.korean_expression` comes from Hermes, not either profile package.
- Documentation assertion/manual review: the guide contains canonical examples, no claim that rules were copied verbatim, both upstream URLs/copyright lines, and the Kiro notice ends exactly in `SOFTWARE` without a period.

Release requires: default-disabled baseline byte equality; at least one valid generated selected output on every requested surface; zero changes to protected semantic/layout tokens; zero legacy free-form daily calls; exactly one OpenAI wire attempt per artifact identity; zero calls for all disallowed identities and all adaptive recovery/terminal statuses; canonical output on every non-success; no duplicate Telegram delivery; and zero content-bearing telemetry/log fields.

## Pre-mortem

| Failure | Earliest signal | Prevention and containment |
|---|---|---|
| Model adds praise, a causal claim, medication/safety advice, or changes a value while sounding fluent | semantic/anchor grammar test or `validation_rejected` counter | semantic IDs plus exact private anchors and closed neutral grammar reject it; render canonical with no retry |
| Timeout/crash/race creates a second model request or applies a late result | decision-chain/concurrency test or `claim_incomplete_canonical`/`timeout` aggregate | fsync pre-call claim, zero SDK retries, conditional finalization, close/discard late worker; canonical replay only |
| Weekly output changes after delivery reservation or sends twice | template-digest mismatch or scheduled ledger test | process before `reserve_customer_task_delivery`; existing ledger remains sole send authority; unknown remains no-retry |
| Adaptive card diverges on recovery or terminal delivery language is humanized | persisted payload mismatch/recovery mock call | postprocess only fresh `card` before persistence; recovery and terminal paths are strict pass-through |
| Provider policy or logs leak scope/data | direct-client/config/telemetry tests | one positive direct OpenAI tuple, default disabled, no auxiliary resolver, redacted anchors, metadata-only decision ledger, sealed official presentation artifacts only |

## Result

This revision implements the requested actual Korean post-processing LLM across daily, weekly, and adaptive operator prose while preserving deterministic semantic authority. It resolves the stage-04 issues: explicit canonical baselines; warm wording without unproved completeness; one-attempt semantics tied to each existing artifact/delivery identity; unambiguous disabled/shadow/enabled; a frozen ledger schema and singular timeout rule; the real config parser file and obsolete-field removal; no broad provider infrastructure; and accurate non-verbatim MIT attribution.
