# Task26 one-invite harness operations

All commands run from `/home/cube/projects/richard/traning coach`. The current
live verdict is **FAIL** (`gateway.lock` exists), so none of the mutating or
waiting commands below is authorized for execution yet.

## Exact prerequisites

- Candidate `2e0894eac92bc396cc4723bf1f18ebc653b95018dd41574df435941c235da925`, wheel `af4a9d0a...`, amended plan `7ace03c6...`, and runbooks `1f55b8f9...` / `f4f2d234...` remain hash-equal.
- Loaded `gateway.platforms.telegram_customer_bootstrap` bytes equal wheel member SHA-256 `145515d5e110dcebb94fcaa554bcee29544b3a75dcfe058fae944ea3b70042b2`.
- Profile is private/current-layout; gateway is stopped, Telegram disconnected, and `gateway.lock` absent through the separately sealed reset path (never hand-delete it).
- Every prior customer-bootstrap session is terminal and has no role claim or recovery attempt; session IDs and SID hashes are unique.
- Registry, room-bootstrap, service-state, publication outboxes, `data/customers`, and customer-specific onboarding authority are empty.
- Actor `8527916639` is the private-DM customer and differs from owner/operator `8693203710`.
- Draft is a private 0600 regular file with a globally new customer key and no `customer_user_id`.
- Receipt, ready, and handoff paths do not exist. Evidence directories are 0700.
- The exact claim observer is READY before the human opens the handoff. Only one Start is accepted.

## Read-only gates

```bash
D='.omo/evidence/task26/task26-invite-harness-st_01a0056a'
PY='/home/cube/projects/richard/hermes-agent/.venv/bin/python'
PROFILE='/home/cube/.hermes/profiles/dualcoachtest'

"$PY" -B "$D/invite_harness.py" dry-run \
  --profile "$PROFILE" --permission "$D/permission-seal.json" \
  --receipt "$D/<run-id>-dry-run.redacted.json"

"$PY" -B "$D/invite_harness.py" verify \
  --profile "$PROFILE" --permission "$D/permission-seal.json" \
  --receipt "$D/<run-id>-verify.redacted.json"
```

Both must exit 0 and report `ready_for_one_invite: true`.

## One canonical preparation (do not run until every prerequisite passes)

```bash
"$PY" -B "$D/invite_harness.py" prepare \
  --profile "$PROFILE" --permission "$D/permission-seal.json" \
  --draft "$D/<run-id>-customer-draft.private.json" \
  --handoff "$D/<run-id>-invite-handoff.private.json" \
  --receipt "$D/<run-id>-preparation.redacted.json" \
  --evidence-root "$D"
```

The command subscribes to `ledger.json` before calling exactly one
`RoomBootstrapStore.prepare_rehearsal_customer_invite`. The 0600 handoff is the
only raw-token location. The preparation receipt contains only deterministic
session/token/SID hashes and event proof.

## Exact claim observation (start before the human action)

```bash
"$PY" -B "$D/invite_harness.py" observe \
  --profile "$PROFILE" --permission "$D/permission-seal.json" \
  --session-id '<session-id-from-redacted-preparation-receipt>' \
  --sid-hash '<sid-hash-from-redacted-preparation-receipt>' \
  --ready "$D/<run-id>-claim-watch-ready.redacted.json" \
  --receipt "$D/<run-id>-claim-observed.redacted.json" \
  --timeout-seconds 120
```

Only after the READY receipt exists may actor `8527916639` privately open the
0600 handoff and press Start once. The observer uses bounded inotify events; it
never calls Telegram/getUpdates, drops updates, reads or edits a cursor, sleeps,
or polls state.

## Expiry and cleanup

If the one invite reaches its deadline unclaimed:

```bash
"$PY" -B "$D/invite_harness.py" expire \
  --profile "$PROFILE" --permission "$D/permission-seal.json" \
  --session-id '<session-id>' --sid-hash '<sid-hash>' \
  --receipt "$D/<run-id>-expiry.redacted.json"
```

This calls `RoomBootstrapStore.expire_unbound` once and requires `EXPIRED`.
It refuses before `expires_at`. There is no candidate cancellation API: before
handoff publication, rollback is token non-publication followed by canonical
expiry; after handoff publication rollback is prohibited. Final archive-first
profile cleanup remains the separately permission-sealed reset controller from
`reset-controller-st_01a0054d`, run as `dry-run`, `verify`, then `execute` with
a fresh run ID and execution approval. No restore or prepopulation is allowed.
