# DualCoach Golden Path Contract

## Metadata and baseline

- Plan: `dualcoach-production-readiness`, task 2.
- Execution repository: `/home/cube/projects/richard/hermes-agent` (read-only for this artifact).
- Candidate digest: `19ed0e9232e240553b9a96a2e6a33d10f1096be49e5ebafc019287d75984e45c`.
- Baseline verdict: **NO-GO** until tasks 4-27 and F1-F5 prove the planned interfaces below.
- Manifest boundary: 67 candidate paths; shared `gateway/run.py`, `hermes_cli/config.py`, and `tests/gateway/conftest.py` require release attribution; unrelated dirty files stay untouched.

## Actors, channels, and authority

- **Customer / Telegram DM:** the only channel for onboarding answers, health data, role-review content, check-ins, and delivered coaching.
- **Staff/operator / Telegram room:** receives only opaque customer ID, status, timestamps, safe summary, and review controls; never customer health content or customer-specific role output.
- **Generation provider:** produces an untrusted draft; provider success is not approval or delivery.
- **Durable state/outbox:** sole authority for lifecycle, review, delivery intent, attempt, receipt, and reconciliation.

## Non-negotiable invariants

1. Customer health data and customer-specific role outputs **MUST NOT** appear in the staff room, logs, callback payloads, or idempotency keys.
2. `approve` stores an immutable reviewed candidate and enqueues delivery intent; **approval is not delivery**.
3. Only an explicit authorized `deliver` transition may claim delivery authority.
4. A provider/API success string is insufficient: durable receipt or `unknown_delivery` reconciliation is required.
5. Every transition is atomic, idempotent, restart-safe, observable without sleeps, and scoped to one isolated customer.

## Canonical lifecycle

| Step | Actor/channel | Request/event/command | Visible UI | Durable state | Observable completion (no sleep) | Pass/fail | Recovery | Cleanup |
|---|---|---|---|---|---|---|---|---|
| Onboarding | Customer/DM | Customer opens isolated invite and submits answers | DM prompts only; staff sees safe status | customer + DM address + onboarding revision | await committed onboarding event | PASS only if no staff-room health text | disable customer; inspect routing | revoke invite |
| Role review | Customer/DM | request role review | DM shows customer-specific output | role revision + source revision | await role-review committed event | PASS if role output exists only in DM | regenerate from durable inputs | clear test output |
| Activation | Authorized operator | explicit activate command/button | staff sees safe activation status; customer gets DM confirmation | active flag + actor + timestamp | await activation commit | FAIL if implicit/default activation | disable customer | return disabled |
| Check-in | Customer/DM | submit structured check-in | DM acknowledgement; no staff health payload | check-in row + canonical customer ID | await check-in commit | PASS if exactly one row and privacy holds | reject malformed input; retry safely | retain audit row |
| Atomic enqueue | System/durable state | same transaction completes check-in and inserts generation job | staff safe pending indicator | check-in complete + unique generation job | subscribe to transaction/job event | FAIL on complete-without-job | reconciliation inserts only missing job | release lease |
| Async generation | Worker/provider | claim lease; call provider with immutable input revision | staff safe progress only | queued->running->draft_ready or retryable/terminal failure | await state event or bounded provider timeout | PASS if resumable and one immutable draft | expire lease; auth preflight; bounded retry | release lease/token |
| Operator card | System/staff room | render card from durable draft metadata | safe summary + status + controls; no health text | card binding `(chat,message,draft_revision)` | await send/edit receipt | FAIL if content/privacy or stale controls | rebuild from durable state | invalidate stale card |
| Approval | Operator/staff room | authorized approve callback | card becomes approved; no delivery claim | immutable approved revision + audit actor/time + delivery intent | await approval commit and outbox intent | PASS if duplicate is no-op | reload authoritative state | acknowledge callback best-effort |
| Explicit delivery | Operator/staff room -> Customer/DM | separate authorized deliver callback | staff shows sending; customer receives DM | intent->attempt with idempotency key | await attempt commit before provider call | FAIL if approval auto-sends | stop; reconcile intent/attempt | no second send |
| Receipt reconciliation | System/provider boundary | persist Telegram message receipt or classify unknown | staff shows delivered or reconciliation required | attempt->receipt or `unknown_delivery` | await durable receipt/reconciliation event | PASS only one receipt; unknown never retried blindly | query provider/history; operator decides | close resolved attempt |
| Cleanup | Operator/system | disable rehearsal customer; stop test gateway; reconcile PID/state | disabled/clean status | disabled flag; no pending job/outbox; truthful process state | await disable and process-exit events | PASS if no live resource or pending delivery | kill only owned PID; reconcile again | remove test-only runtime data |

## Exactly-once outbox contract

- Idempotency key: `customer_id + approved_revision + delivery_kind`; no health text or mutable UI identifiers.
- States: `intent` (authority requested), `attempt` (provider call may have happened), `receipt` (provider message ID persisted), `unknown_delivery` (response inconclusive).
- The transaction that records approval creates one intent. The transaction that starts delivery records one attempt before I/O.
- Duplicate approve/deliver callbacks return the authoritative state and create no second intent/attempt/DM.
- Restart resumes `intent`; `attempt` without receipt becomes `unknown_delivery`, never blind retry.
- Timeout/network loss after provider call is `unknown_delivery`; reconcile Telegram history/receipt before any new send.
- Receipt uniqueness is enforced by idempotency key and provider message identity.

## Startup and callback boundaries

- Startup classifies PID/process state as `not_running`, `running_owned`, `running_foreign`, or `stale_state`; it never trusts a PID file alone.
- Pending Telegram updates are preserved until durable processing commits; startup must not delete backlog to appear healthy.
- Callback acknowledgement is best-effort and bounded. Ack failure does not roll back a committed business transition; business failure does not claim success because ack succeeded.
- Card edit failure after commit leaves authoritative state intact and marks the card for rebuild.

## Failure matrix

| Failure class | Trigger | Expected state/UI | Recovery | Pass/fail |
|---|---|---|---|---|
| dirty_worktree | unrelated edits exist | manifest digest scopes candidate; no destructive Git action | verify digest/path ownership | FAIL if reset/stash/clean or unrelated bytes change |
| stale_state | stale PID/card/lease/revision | explicit stale classification; no inferred success | query durable state, expire owned lease, rebuild | PASS when UI mirrors authority |
| malformed_input | bad update/callback/provider payload | rejected with safe DM/operator error; no transition | log redacted reason; request valid input | FAIL on partial commit |
| prompt_injection | untrusted customer/provider text requests authority | treated as data; controls/recipient unchanged | discard draft or sanitize display | FAIL if text changes routing/delivery |
| cancel_resume | restart after every durable transition | resumes from last committed state | restart worker/gateway; reconcile | PASS with no lost job or duplicate send |
| hung_or_long_commands | provider/API/card edit does not complete | bounded timeout and explicit retry/unknown state | cancel owned operation; reconcile | FAIL on indefinite wait |
| flaky_tests | async ordering varies | event/state subscription precedes trigger | await bounded exact signal | FAIL if fixed sleep/poll makes pass nondeterministic |
| misleading_success_output | API/UI prints success without commit/receipt | durable query disagrees and verdict remains FAIL | reconcile state/receipt | PASS only when durable evidence matches |
| repeated_interruptions | multiple restarts during generation/delivery | leases/idempotency preserve one logical action | repeat restart matrix | FAIL on duplicate DM or orphaned intent |

## Fresh isolated Telegram rehearsal

Prerequisites: dedicated disabled test customer; verified customer DM address distinct from staff room; authorized test operator; provider auth preflight PASS; empty customer-specific job/outbox; candidate digest match; owned gateway process; live logs with redaction. Execute lifecycle rows in order and capture Telegram DM/staff screenshots or message IDs plus durable queries. Use no real customer and no release activation.

Cleanup: disable the rehearsal customer, revoke invite, reconcile jobs/intents/attempts/receipts, stop only the owned gateway, confirm truthful PID/process state, and prove no pending delivery. Any failed cleanup is NO-GO.

## Planned interfaces and current diagnostics

Tasks 4-17 must implement explicit customer/staff addresses, privacy readiness, generation states/leases, auth preflight, restart-safe cards, callback bounds, pending-update preservation, process reconciliation, and exactly-once outbox. Until then use read-only configuration/state queries and targeted existing tests as diagnostics; diagnostic output is not proof of the planned behavior.

## Mechanical completeness QA

Status: **executor-designed; independent verifier must execute**.

Invocation design:

```text
parse markdown tables -> require lifecycle columns
[Step, Actor/channel, Request/event/command, Visible UI, Durable state,
 Observable completion (no sleep), Pass/fail, Recovery, Cleanup];
require lifecycle rows onboarding, role review, activation, check-in, atomic enqueue,
async generation, operator card, approval, explicit delivery, receipt reconciliation, cleanup;
require all nine failure-class row names;
require tokens customer DM, staff/operator room, approval is not delivery,
intent, attempt, receipt, unknown_delivery, idempotency key;
reject fixed sleep/poll instructions outside the explicit prohibition text.
```

Expected output: `PASS task-2 golden-path contract: lifecycle=11 failures=9 invariants=present sleeps=0`.

## Cleanup receipt and residual risks

This artifact creates no temp files, processes, credentials, customers, runtime state, or product/test changes. Retain only this contract and its verifier evidence. Residual risk remains **NO-GO** until implementation tasks, candidate-level gates, fresh Telegram rehearsal, cleanup, and all release-review lanes pass.
