# Gate-D Hardening Plan — Architect Review

## Summary
The chosen host-owned control-plane direction correctly rejects conflating Topic 59 with the canonical owner and retains the required no-retry delivery posture. This plan is not execution-ready: it lacks the shared authority transaction, sealed scheduled-claim migration, and deterministic P2–P6 state-machine definitions required to prove its safety claims. All unresolved findings must return to planner revision before implementation.

## Claims
- The reviewed immutable planner artifact is `stage-20-planner.md`, SHA-256 `f27c02720daac1601d1a58c219ecb9f90f48d142bbabe0428c7c96654b322ea9`; the RALPLAN index records that same planner receipt.
- The planner correctly identifies the current Topic-59/canonical-owner conflict and targets ingress/lifecycle separation (planner WS1, lines 40–44).
- Current adaptive callback handling requires the incoming address to equal the live canonical owner while Topic 59 is separately required (`gateway/platforms/telegram.py:5728–5774`), so a canonical owner whose topic differs from 59 cannot operate the current adaptive callback path.
- Current active adaptive config carries only `operator_chat_id` and `operator_topic_id` (`/home/cube/.hermes/profiles/physique-coach/config.yaml:617–622`; `nutrition_coaching_config.py:29–58`), while the alternate full review address is read from `nutrition_coaching.operator_review` (`telegram.py:4186–4210`). This is not one authoritative identity configuration.
- Current scheduler durability is a one-word exclusive claim file (`customer_schedule.py:75–107`); the due tick claims before sending (`telegram.py:5437–5538`), and `customer_admin retry` deletes the claim (`customer_admin.py:4210–4213`). It has no durable destination/body/attempt/receipt state.
- Current profile Gate-D instructions call lifecycle APIs directly with an owner triple (`/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli/PILOT_RUNBOOK.md:274–284`), contrary to the proposed host-only entrypoint boundary.
- Historical audit state records prior suite counts and a rollout-only checkpoint, not validation of this unimplemented plan (`.gjc/.../state/ultragoal-state.json:110–111`). No tests were run for this planning review, as required.

## Analysis

### Stage 1 — Specification compliance
The plan addresses the right problem classes: ingress identity versus audit authority, provider ambiguity, stale authority, menu-only operation, schedule recovery, and human-only live operations. It also keeps real customers disabled and does not turn a manual Gate-D rehearsal into synthetic release evidence.

However, the acceptance criteria cannot currently be proved from the proposed work descriptions. “Refresh full triple under lifecycle/store lock” does not establish an authorization linearization point across the registry, consent/activation writers, adaptive stream, config epoch, and provider handoff. “Additive/read-compatible” schedule migration does not prevent the existing release CLI from reopening a claim with unknowable prior transport. P2–P6 are named but lack an executable revision/state table, so their expected provider counts and ledger outcomes are not testable requirements.

### Stage 2 — Architecture
The strongest antithesis is a minimal callback/config patch: it would be faster and preserve existing operator behavior. It fails the stated goal because the present route conflates identities, exposes a raw owner-triple lifecycle API, uses an adaptive-store-only lock for cross-store authority, and leaves scheduled sends with an erasable claim instead of delivery truth. Conversely, a broad new control plane would be risky if it duplicated proposal, authority, or delivery logic.

The stronger synthesis is a narrow host-owned adapter over the existing canonical coordinator: one typed review authorization capability is minted only after exact Topic-59 ingress; canonical lifecycle methods consume that capability under the existing cross-writer authority transaction; the adapter persists menu/session state and delegates every mutation to the coordinator. It must not create a second lifecycle, a generic append path, or a compatibility path that silently accepts raw triples.

Current delivery structure reinforces the need for a formal boundary. `deliver_latest_once` reserves under the adaptive store lock, then releases that lock before its final authority check and provider call (`nutrition_coaching.py:7502–7706`). A test can make that safe only if the registry/owner writer and delivery path share a documented transaction/CAS contract and deterministic barrier. Existing `approve_latest` contains repeated snapshot comparisons (`nutrition_coaching.py:6259–6345`), but that pattern is not a substitute for a cross-process lock adopted by the independent writers.

### Stage 3 — Constructive synthesis
1. Define `ReviewAuthorityV1`: immutable review-config version/digest, authenticated ingress triple, canonical owner triple plus registry revision/digest, customer key, action, revision digest/epoch, issued-card/message binding, expiry, and opaque nonce. Persist both reviewer and canonical owner in transition audit records; do not rewrite the authenticated reviewer as the owner.
2. Define a single customer authority transaction and lock order shared by registry/consent/activation/safety writers, adaptive transitions, feature-epoch updates, and schedule delivery. At the final linearization point compare the capability snapshot to live authority/pins, append reservation, and either call provider while the required exclusion is held or use an explicit CAS/version proof that precludes stale authority.
3. Define `ScheduledDeliveryIntentV1` records and legal transitions: `prepared -> sending -> delivered|unknown`, then `audited` only from a persisted receipt. Every record pins task kind, customer, KST day, recipient role/destination, rendered-body/template digest, registry/config/authority digest, and attempt ID. A legacy `.claim` imports once as sealed `legacy_unknown` unless independent evidence establishes no send; the old retry command must be rejected or mapped only to a narrow pre-attempt abandon/reconciliation operation.
4. Define a host-owned `AdaptiveOperatorService` with one config model and one deterministic token lookup. UI callbacks contain a bounded opaque nonce; server records bind it to review triple, card message, customer, action, latest revision/digest, epoch, and expiry. The service chooses only current enabled customers and the host-determined KST day, returns Korean typed result text, and never accepts destination/body/digest/free-form lifecycle parameters.
5. Write P2–P6 as separate, immutable test scenarios. Each specifies initial fixture/revision, injected event, exact terminal adaptive/schedule rows, final epoch/overlay, callback UI text, and provider invocation count. Use a test-only barrier after reservation and before final authority validation for P5 revoke and P6 owner rotation; it must be unavailable in production and manual Gate-D evidence must not claim it exercised a race deterministically.

### Stage 4 — Test sufficiency and operational boundaries
Existing focused tests demonstrate normal scheduler claim behavior and successful tick sends (`test_customer_schedule.py:42–160`; `test_nutrition_coaching.py:534–635`) plus draft-outbox recovery (`test_nutrition_coaching.py:888–1005`). They do not cover durable schedule receipt migration, crash boundaries, legacy claim import, retry rejection, or a live-owner rotation/revoke interleaving across independently writing processes. The revised plan must add those tests before relying on historical aggregate suite counts.

Preflight must be read-only over user-provisioned disposable identities, bot, group/topic, and secrets. It may validate isolation, ownership, modes, data root, current plan window, and disabled delivery; it must neither create/manage real accounts/tokens nor activate a customer. Human-only operations remain account/token setup, private secret entry, actual Telegram rehearsal, private evidence capture, and explicit real-customer rollout approval.

## Root Cause
Authority and delivery truth are split across multiple independently durable surfaces: Telegram ingress/config, customer registry, adaptive stream/epoch, and schedule claim files. The plan identifies those symptoms but does not yet specify the one capability plus cross-writer transaction that makes authorization and at-most-once delivery coherent. The resulting gaps would otherwise be hidden by a menu wrapper and an additive ledger fallback rather than repairing the controllable primary contract.

## Findings
1. **HIGH — shared authority transaction and delegated actor are unspecified.** Planner WS1/WS4 lines 40–65. Current callback/config and adaptive-store locking evidence above show that refresh calls alone do not close the cross-process TOCTOU window. Define the shared transaction, typed capability, dual identity audit, lock order, and provider linearization.
2. **HIGH — legacy schedule claims can still be reopened.** Planner WS5 lines 67–73. Current claim files cannot prove provider state and `retry` deletes them. Seal legacy imports as unknown/blocked absent proof, retire/redesign retry, and specify pinning/recovery state tables.
3. **HIGH — P2–P6 are not executable safety scenarios.** Planner WS7/execution order lines 83–99 and acceptance line 115. Add isolated revision fixtures, deterministic interleavings, exact rows/UI text/provider counts, and a production-inaccessible test barrier.
4. **MEDIUM — operator entrypoint/config/token contract is under-specified.** Planner WS2 lines 46–52. Unify current divergent config surfaces, define host service ownership and persistent bounded token mapping, bind callback to message/review triple/epoch, and route directly rather than probing every customer coordinator.
5. **MEDIUM — documentation/manual boundary conflicts with the proposed control plane.** Planner WS6/WS7 lines 75–88. Identify the authoritative profile runbook and synchronized project docs; replace direct lifecycle-call instructions with supported host UI/API steps, and make preflight read-only.

## Recommendations
1. **Block implementation pending planner revision** that resolves Findings 1–3 as normative contracts, state tables, and acceptance tests.
2. Keep option B, but scope it as an adapter over canonical coordinator/lifecycle contracts—not a parallel control plane or compatibility fallback.
3. Put authority/config schema, shared transaction, and callback capability in the first implementation slice; make all mutating lifecycle and delivery paths consume it before adding menu actions.
4. Replace claim-only scheduling and the retry CLI in the next slice; complete migration/recovery tests before enabling any schedule tick against a disposable profile.
5. Implement menu/UI only after the above contracts exist; make its integration tests exercise the actual Telegram handler, not a direct coordinator stand-in.
6. Treat P2–P6 automated injection tests and manual Telegram proof as distinct evidence classes. Only the latter may satisfy Gate-D, and neither authorizes real-customer activation without the explicit final approval.
7. Update the profile runbook, project copies, Korean guide, handoff, and HTML from one stated documentation source of truth. Remove direct lifecycle/retry instructions that contradict the hardened public entrypoint.

## Architectural Status
BLOCK

## Code Review Recommendation
REQUEST CHANGES

## Tradeoffs
| Option | Benefit | Fatal limitation / required constraint |
|---|---|---|
| Minimal callback patch | Smallest diff | Does not repair cross-store TOCTOU, raw authority substitution, or claim-only schedule recovery. Reject as complete solution. |
| Host-owned adapter over canonical lifecycle (revised B) | Production-path operability with one authority/delivery contract | Must use typed capability, shared transaction, sealed schedule ledger, and no duplicate lifecycle. Recommended. |
| New standalone control plane/REPL or direct API script | Fast demonstrations | Bypasses ingress evidence and recreates alternate mutation paths. Reject. |
| Move canonical owner to Topic 59 | Simplifies equality checks | Corrupts audit/domain identity and conflicts with normal routes. Reject. |

No product source was modified and no tests were run.
