# Task26 invite harness v2 operations

V2 supersedes only the invite harness; v1 remains byte-identical under
`task26-invite-harness-st_01a0056a/`. It is bound to candidate `2e0894ea...`,
wheel `af4a9d0a...`, plan `7ace03c6...`, Golden/Recovery Runbook v5, reset
manifest `7ef0eb8f...`, and reset evidence digest `d2879b12...`.

## Supported post-reset initializer

The candidate wheel is authoritative. `RoomBootstrapStore.__init__` calls
`_prepare_paths`, recursively creates its state path, creates `ledger.lock`,
and atomically writes an authenticated empty ledger when absent. The subsequent
`prepare_rehearsal_customer_invite` atomically publishes the one PREPARED
session. Therefore an absent baseline has two candidate ledger publications
(initial empty, then PREPARED), one `data/onboarding` creation event, one store
initializer invocation, and one prepare invocation. No operator or harness code
may manually create an empty ledger.

The current runtime umask makes the candidate-created intermediate
`data/onboarding` directory 0775. Its enclosing profile and `data` roots are
0700; the state leaf is 0700 and both authority files are 0600.

## Exact commands

Run from `/home/cube/projects/richard/traning coach`:

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

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

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

Both must report `PASS`, `RESET_BOUND_LEDGER_ABSENT`, `mutations: 0`, and
`ready_for_one_invite: true`. Before preparation, `data/onboarding` must remain
absent and the reset manifest/receipt and cleared sibling baseline must remain
hash-equal and clean.

Exactly one preparation, only under the separately authorized human window:

```bash
"$PY" -B "$D/invite_harness_v2.py" prepare \
  --profile "$PROFILE" --permission "$D/permission-seal-v2.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"
```

Preparation subscribes on the stable 0700 `data` parent first, rechecks that
`onboarding` did not race into existence, and then invokes only the candidate
store and prepare API. A second creation event, unexpected file/tree, wrong
mode, symlink, malformed schema, duplicate session/hash, or nonterminal/open
prior authority fails closed. The 0600 handoff is the sole raw-token artifact.

Start claim observation before exposing the handoff:

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

Only after the READY receipt may actor `8527916639` press Start once in the
private DM. No raw getUpdates, drop-updates, cursor edit, sleep, or polling path
exists.

If the invite expires unclaimed:

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

This invokes only `RoomBootstrapStore.expire_unbound` and refuses before the
candidate deadline.
