## Verdict
**ITERATE**

## Claim Checks

- **Artifact identity verified.** The ralplan index names `stage-03-revision.md` with SHA-256 `ba255a37b8487026c614fff06de79e84460e0b19d548913568aa273e88a4f0d1`, matching the assignment. The artifact is a human-readable Markdown plan, not a YAML-only plan.
- **Binding provider direction is consistent.** The plan selects only direct OpenAI Chat Completions at `https://api.openai.com/v1/chat/completions`, explicitly excludes Kimi/Moonshot and generic/auxiliary resolvers, disables redirects/retries/stream/tools/repair, and pins `gpt-4.1-mini`. The repository already pins `openai==2.24.0`, `httpx==0.28.1`, and `certifi==2026.5.20` in `pyproject.toml`, so the named client stack exists.
- **The static feature block is substantially exact, but the runtime exactness claim is not yet true.** The plan defines exact key sets, exact scalar types, three modes, four surfaces, and byte-exact provider constants. Both real configs currently contain `coaching_feedback_enabled: true` under `platforms.telegram.extra.physique_checkin` (`physique-coach/config.yaml:633`; `dualcoachtest/config.yaml:634`), so the staged legacy-key migration is grounded. However, the gateway currently parses `config.yaml` with `yaml.safe_load` (`gateway/config.py:805-812`). `TelegramAdapter.__init__` receives only the resulting dict, so it cannot detect duplicate nested keys, anchors, or last-key-wins input. The proposed strict audit script is manual and is not a runtime authority. Therefore duplicate-key rejection is not enforced on every restart as claimed.
- **Provider egress isolation is directionally strong, but the privacy proof is incomplete.** `trust_env=False`, explicit certifi TLS, origin/path pinning, redirects off, and zero SDK/transport retries form a good isolation boundary. The plan also rejects ambient OpenAI org/project/base/proxy variables. But the installed OpenAI SDK honors `OPENAI_LOG` and logs full request options at DEBUG (`openai/_utils/_logs.py`; `openai/_base_client.py:485-490`), which includes message content and aliases. The plan neither rejects `OPENAI_LOG` nor neutralizes inherited global DEBUG logging. This contradicts “no prompt/response/alias logs.” The exact `chat.completions.create` messages, JSON-mode argument, temperature/sampling fields, and canonical byte-counting envelope are also absent, so an executor must invent the actual free-prose request.
- **The private wire removes raw facts and IDs, but key/linkability details are missing.** Per-attempt nonce-derived aliases and local-only expansion prevent sending canonical bodies, values, notes, destinations, and raw identities. Strict duplicate JSON, alias-order, Unicode, size, usage, and local frozen-byte checks are well specified at a policy level. However, “a 0600 key” has no exact path, create/open/no-follow/owner/link-count checks, atomic provisioning, backup/rotation rule, failure behavior, or test fixture. `artifact_key` is described as an internal-identity HMAC without explicitly including the attempt nonce, so cross-attempt linkability is unresolved. It is also unclear whether placeholder masking occurs before every allowlist/lexical check and whether `generated_ko` echoes only placeholder aliases or all alias arrays.
- **Daily durability fits the owning store in principle, but its schema cannot be implemented from the current plan.** Current profile `Event` validation is both Pydantic-strict and checked against `/home/cube/.hermes/profiles/physique-coach/contracts/checkin-event.schema.json` (`checkin_cli/models.py:763-784, 927-940`), whose top-level schema has `additionalProperties: false`. The plan adds `DailyPresentationAttemptV1` to `EventType.COACHING_RESPONSE` but omits that contract JSON from the file list and does not define the enclosing Event’s event ID, dedupe key, provenance, status, field name, phase-specific required/null fields, or claim-to-final predecessor rule. Existing `PhysiqueCheckinBridge` can recover the finalized Event and session (`physique_checkin.py:382-443`), so the integration point is real, but `DailyPresentationV1(branch=complete|partial)` does not match the existing session branch vocabulary `normal|anomaly|change|safety_hold` (`physique_checkin.py:444-495`) and no complete/partial predicate is given.
- **Weekly durability correctly reuses the schedule lock and durable body, but mixed-version migration is under-specified.** The current ledger is schema version 1 only, has an exact row-key set, accepts only `prepared` as a normal first state, and requires body/destination/registry/config/template pins immutable across transitions (`customer_schedule.py:19-40, 711-866`). The proposed `presentation_claimed -> prepared` state and v2 fields therefore require a version-aware row reader, state machine, fence policy, and mixed v1/v2 chain rules. “Existing v1 prepared rows are immutable canonical rows” is the right invariant but is not enough to decide whether the global schema/fence remains v1, how v1 and v2 rows coexist, or how `PinnedScheduledDelivery` represents a v1 canonical outcome. The prepared-before-render branch is correctly placed: current `_send_nutrition_coaching_tick` otherwise refreshes and calls both weekly report builders before re-reserving (`telegram.py:6360-6525`).
- **Both adaptive producers and both grammars were located, but the origin/result contract is missing.** Fresh callback cards are produced in `AdaptiveOperatorService.handle_callback`; typed `edit_note` cards are produced in `handle_text` after consuming `awaiting_input` (`nutrition_coaching.py:4399-4783, 4775-4852`). Telegram routes them through two different publication functions (`telegram.py:4551-4689, 7225-7354`). The plan correctly covers both and correctly reuses authority/publication locking and persisted pending-card recovery. Yet it does not define whether `CardPresentationOrigin` is a wrapper, tuple, or transient result member, how Telegram removes it before canonical JSON/persistence, or its exact equality/validation rules. This matters because the existing persisted card validator canonical-JSON serializes the result. The new `presentation_claimed` row’s exact key set, outcome enum, phase-specific fields, predecessor rules, and compatibility with existing schema `1.0` rows are also absent.
- **Restart/replay intent is safe but recovery triggers are incomplete.** Daily explicit replay becoming canonical, weekly sending only a durable `prepared` body, adaptive pending-card recovery, late-worker ownership checks, and no second provider call after an expired claim are correct. Current Telegram startup calls `_recover_pending_adaptive_cards` (`telegram.py:2279-2280, 4404-4446`), but that recovery currently scans only `publish_pending`/expired `publish_claimed`; the plan does not explicitly extend it to expire `presentation_claimed` before publication. No concrete startup or on-access scanner is named for daily expired presentation claims. Recovery must also run when the postprocessor is disabled, otherwise rollback/restart cannot make the promised canonical terminal transitions without a coordinator client.
- **Migration and rollback are conservative but one deletion instruction is ambiguous.** Removing only the obsolete legacy feedback key, defaulting the replacement off, auditing before deleting the old field/path, and never restoring the old path on rollback are sound. However, “delete … generic resolver imports” is ambiguous in the current call graph: `_request_physique_coach_completion` is shared by unrelated active-turn and generic-conversation behavior (`telegram.py:5946-6103`). Those paths are explicitly outside this feature and must not be broken. The revision must say to remove only the legacy finalized-feedback call edge while preserving unrelated generic conversation/active-turn behavior.
- **Verification breadth is good but does not yet prove runtime config strictness or real free prose.** The plan covers hostile environment, Unicode/JSON/YAML attacks, frozen slots, crash boundaries, late workers, pin mismatch, both adaptive producers/grammars, logging, wrappers, full suite, live marker collection, staged shadow, and rollback. `scripts/run_tests.sh` currently runs under `env -i` and has no live switch, so the proposed explicit opt-in forwarding is grounded. But no test is named for the real gateway `safe_load` duplicate-key path, SDK/global DEBUG request-option leakage, HMAC-key provisioning, mixed v1/v2 ledgers, or `presentation_claimed` startup expiry. The live proof says “four coordinator paths,” while the P1 profile matrix contains daily once, weekly in both profiles, and two adaptive grammars—five profile/path combinations if both weekly implementations are evidence. It also checks successful/accepted calls but has no explicit assertion that accepted `generated_ko` came from the provider as novel open prose rather than a source literal or finite catalog.
- **Referenced paths are partly verified.** Existing gateway/profile/config/test/wrapper files were found. `gateway/platforms/korean_expression.py`, the strict audit script, their tests, and ADR-021 are intentionally new. The plan does not give an exact ADR path. `듀얼코치_사용설명서.md` exists at `/home/cube/projects/richard/traning coach/듀얼코치_사용설명서.md`, not under `/home/cube/projects/richard/hermes-agent`; the multi-root documentation path must be explicit. The external strict event contract file is material but absent from the plan’s file inventory.

## Missing Evidence

Definitely missing:

1. A runtime raw-YAML authority that rejects duplicates/anchors/merge keys on every gateway startup, not only during a manual cutover audit.
2. The exact OpenAI system/user messages and complete `chat.completions.create` argument set, including JSON mode and sampling behavior, plus a byte-budget definition for the actual serialized HTTP body.
3. Protection against `OPENAI_LOG` and inherited DEBUG logging of SDK request options; client/transport close and bounded worker-shutdown behavior are also unspecified.
4. Exact HMAC-key storage/provisioning/rotation/linkability rules.
5. Exact daily Event, weekly v2 row, adaptive presentation row, and transient origin schemas, including phase-dependent required fields and predecessor/identity rules.
6. Mixed v1/v2 schedule-ledger and fence migration rules with immutable real-v1 fixtures.
7. Concrete restart/on-access recovery entry points for every `claimed` state, including disabled mode.
8. Exact allowed enum/code mappings or derivation rules for `complete|partial`, weekly interpretation/rationale, dual decisions/reasons, and physique review decisions.
9. A direct acceptance criterion proving provider-produced, open Korean prose without a finite phrase catalog.

Possibly unclear:

- Whether an adaptive pin race may durably publish the old canonical payload or must hard-stop with no edit. “Finalizes canonical if possible” and “pin-race zero selected pending/edit” admit different readings.
- Whether P1 live evidence covers four generic surface schemas or all five profile-specific paths in the rollout matrix.
- Whether “generic resolver imports” means only the removed legacy-feedback edge or the shared completion helper used by excluded behavior.

## Approval Boundary

Execution is **not approved** from this revision because exact configuration enforcement, privacy logging, persistence schemas, migration compatibility, and recovery triggers still require executor guesses. The following design choices may be retained in the next revision: default-off presentation-only behavior; direct OpenAI only; no Kimi/generic fallback; opaque provider wire with local expansion; whole-response deterministic validation; canonical fallback; no cross-store journal; daily/weekly/adaptive authority remaining in their owning stores; prepared weekly delivery; callback plus typed-note adaptive coverage; staged shadow-to-enabled rollout.

No product-source implementation, config migration, or live provider run is approved until the required changes below are incorporated.

## Summary

- **Clarity:** Strong policy narrative; weak at exact runtime call, transient origin, and phase-schema boundaries.
- **Verifiability:** Broad test list, but key security claims are not reachable through the named runtime paths/tests.
- **Completeness:** Missing external event contract, mixed ledger migration, key lifecycle, recovery scanners, and exact new paths.
- **Big Picture:** Surface-owned durability and canonical fallback are the correct architecture; runtime configuration and logging currently bypass parts of that boundary.
- **Principle/Option Consistency:** OpenAI-only/default-off/local expansion are consistent. Opaque semantics versus meaningful free prose needs an exact prompt/output contract rather than executor invention.
- **Alternatives Depth:** Finite catalogs and whole-body rewriting are reasonably rejected; raw-httpx versus OpenAI SDK logging/isolation and runtime strict-loader alternatives are not assessed.
- **Risk/Verification Rigor:** Crash-state coverage is ambitious, but privacy logging, schema migration, and disabled-mode recovery need first-class gates.

## Required Changes

1. **Make exact config a runtime property.** Name the production raw-config entry point and enforce the same bounded no-follow, one-document, no-anchor/alias/merge, duplicate-key, non-string-key rules before `PlatformConfig.extra` is trusted on every startup. Cover managed overlays and prove that no env/legacy layer can synthesize this key. Add `gateway/config.py` (or the chosen equivalent) and its focused tests to the file/test inventory. Keep malformed feature config canonical-only without taking down unrelated Telegram behavior.
2. **Seal the actual OpenAI call, not only the client.** Provide the literal system message, literal user-envelope serialization, `response_format`, all sampling fields, and the exact byte-count boundary. State which aliases may occur in `generated_ko` and the order of masking/normalization/lexical validation. Reject `OPENAI_LOG` (both cases) and/or use a transport/client path proven not to log bodies under inherited DEBUG; add caplog/handler tests for SDK and httpx loggers. Specify `close()` ownership and how shutdown remains bounded when a worker returns after its lease.
3. **Specify key privacy completely.** Give the exact key path, creation/open flags, owner/mode/link/symlink checks, fsync/atomic behavior, missing/corrupt-key fail-closed outcome, test injection strategy, and rotation rule. Include the attempt nonce in `artifact_key` derivation or explicitly justify and test any intended cross-attempt linkability.
4. **Complete the daily storage contract.** Define the `Event` field and exact `DailyPresentationAttemptV1` phase schema, event/dedupe/provenance/status/sequence identities, timestamp/deadline representation, final-outcome nullability, and linked-finalized-event validation. Add `/home/cube/.hermes/profiles/physique-coach/contracts/checkin-event.schema.json` and contract tests to the file list. Define `complete|partial` from current finalized session facts or replace it with the existing branch vocabulary. Name the restart/on-access expiry scanner and show it runs with the feature disabled.
5. **Complete weekly v1→v2 compatibility.** Define exact v1 and v2 key sets, mixed-chain validation, fence-version handling, allowed transitions, outcome/deadline nullability, and `PinnedScheduledDelivery` fields for both versions. State how existing v1 `prepared` rows load and send exact durable canonical bytes without rerender/model and without mutation. Add migration fixtures copied from current real v1 row shape, corrupt/torn cases, and current-pin mismatch tests.
6. **Complete adaptive origin and row contracts.** Define a typed transient return wrapper for callback and typed-note results; show where Telegram separates origin from serializable `card_payload`; list exact `presentation_claimed`/`publish_pending` fields and outcomes; cover every callback action that can return `status="card"`; and specify conflict equality. Resolve pin-race behavior explicitly. Extend the named startup recovery path so expired presentation claims become canonical pending (or hard-stop, per the chosen authority rule) before existing pending-card recovery, including disabled postprocessor mode.
7. **Protect excluded behavior during legacy deletion.** State explicitly that generic conversation and active-turn interpretation remain unchanged and may retain their shared existing resolver; remove only the finalized coaching-feedback entry, replay call, obsolete key, and imports made dead by that removal. Add non-entry regression tests for both generic conversation and active-turn behavior.
8. **Make free-prose acceptance executable.** Supply a domain-neutral literal prompt compatible with opaque aliases, explain what useful prose can be generated without semantic labels, and require tests that accept previously unseen benign Korean text, reject a finite accepted-output table/source literal fallback, and prove an accepted live candidate is the one provider response validated and locally expanded. Do not persist prose or weaken privacy to do this.
9. **Reconcile rollout evidence and paths.** State whether live proof is four surface schemas or five profile-specific paths and align the P1 matrix/count accordingly. Add runtime duplicate-YAML, SDK/global DEBUG leakage, key provisioning, mixed-ledger, and claimed-state restart tests to the wrapper commands. Give exact paths for ADR-021 and the cross-repository Korean manual, and enumerate both profile copies and the external contract explicitly.
