# Owner-Isolated Diagnostic Mode — Revision 26

## Status
Planning only. Supersedes Stage 25 in full.

## Decision summary
Implement an owner-authenticated, 60-minute maximum, current-boot diagnostic session that hot-attaches one marked isolated registry to the already-running ordinary gateway. Ordinary same-user/distinct-full-triple identity records remain legal and unchanged; only diagnostic authority, synthetic replay, isolated enabled-customer visibility and production exclusion require the session.

## Principles and options
Principles: no diagnostic inference; no production reach; bounded/read-only evidence; synthetic replay only; deployable changes exclude runtime state. Drivers: solo-owner usability, security isolation, deterministic incident handling. Chosen option is a Topic-59-authenticated hot-attached diagnostic runtime. Permanent master authority is rejected as unsafe; mandatory three accounts remains optional but is not required.

## 1. Non-ambient bootstrap and gateway admission

### Bootstrap authority
The existing running ordinary gateway and `AdaptiveOperatorService` remain the only ingress. Add explicit typed actions `diagnostic_prepare`, `diagnostic_activate`, `diagnostic_revalidate`, `diagnostic_close`; no natural-language parser and no config-only enablement.

A request enters only through the configured Topic-59 review triple and existing persisted callback/session mechanism. `AdaptiveOperatorService` refreshes the canonical owner and all live pins, then issues a one-use `DiagnosticBootstrapCapability` containing: schema/version, action, opaque capability ID/nonce digest, authenticated review triple/version digest, canonical owner triple/version digest, isolated profile digest, requested disposable-customer digest, test-bot digest, three role-destination digests, issue/expiry KST, gateway boot epoch, config/registry/consent/activation/artifact digests, predecessor and row digest. Maximum capability lifetime is 10 minutes. Raw triples/local CLI/config cannot mint it.

### Loader selection
`TelegramAdapter` starts only the ordinary coordinator. After authenticated `diagnostic_activate`, `AdaptiveOperatorService` passes the consumed bootstrap capability directly in memory to `NutritionCoachingCoordinator.attach_diagnostic_runtime(capability)`. That method calls `load_diagnostic_runtime_customer_registry`; no startup discovery/config/profile scanning can choose it. The hot-attached coordinator is keyed by diagnostic session digest and exact role destinations. Close/expiry/restart detaches it before disabling state. `nutrition_coaching_config.py` may define closed schema defaults but never contains an enable switch or authority.

### Private DM
Diagnostic mode disables legacy trainer private-DM inference. Only an exact pinned trainer `(chat_id, topic_id)` and active diagnostic role capability is accepted. Wrong DM text/callback/active binding terminates with zero downstream/provider work.

## 2. Distinct marked registry and ordinary-loader rejection

Add closed fields to the registry document schema: `registry_mode: ordinary_v1|diagnostic_isolated_v1` and optional `diagnostic_session_digest`. Existing documents default/migrate in memory to `ordinary_v1` without byte rewrite. Ordinary `load_runtime_customer_registry` and `load_committed_customer_registry` reject `diagnostic_isolated_v1`. Only `load_diagnostic_runtime_customer_registry(root, capability)` accepts it after current session/boot/spec validation.

The isolated profile has a physically separate private root and registry. During active diagnostics its one disposable customer is enabled so existing enabled-only workflow code can run. Production profile/registry is never modified. Production gateway cannot consume the marked registry even if misconfigured to its path.

Targets include `customer_coaching.py`, `customer_admin.py`, gateway constructor/service paths and exact registry domain tests.

## 3. Crash-recoverable activation/cleanup protocol

Create `checkin_cli/diagnostic_isolation.py` with append-only `data/diagnostic-isolation.jsonl` and `data/diagnostic-transitions.jsonl`. States: `prepared`, `activating`, `active`, `restart_unvalidated`, and terminal `expired|revoked|closed|recovery_required`. TTL max 60 minutes, exclusive expiry. Every transition validates schema/predecessor/digest under `profile_authority_lock`.

Atomic protocol under the authority lock:
1. append/fsync `transition_prepared` with intent, before-registry digest/bytes digest, disabled customer digest, target marked-registry digest, session/spec/boot digest;
2. atomically replace+fsync the isolated registry with marked diagnostic registry and enabled disposable customer;
3. append/fsync active session row;
4. append/fsync `transition_committed`.

Terminal cleanup reverses order safely: prepare cleanup intent with active registry digest and disabled target digest; detach gateway runtime; atomically replace+fsync registry to marked-but-disabled state; append terminal session; commit transition. It never touches production registry.

Recovery runs at gateway startup before any diagnostic attachment and on a 30-second host tick:
- prepared + registry-before => append abandoned, keep disabled;
- prepared + registry-target + missing active/commit => replace disabled registry, append `recovery_required`, provider zero;
- active from prior boot => detach, mark `restart_unvalidated`, disable registry; explicit Topic-59 `diagnostic_revalidate` creates a fresh current-boot activation transaction;
- expired active => detach, disable, append expired;
- cleanup partial => complete disable idempotently, terminalize; ambiguous digest => `recovery_required`, no attach/provider.

Every request and pre-provider boundary independently checks current time/session/boot/marked registry, so expiry is safe even if the tick is delayed.

## 4. Exact session APIs and state outcomes

Profile APIs: `prepare_diagnostic_isolation`, `activate_diagnostic_isolation`, `revalidate_diagnostic_isolation_after_restart`, `revoke_diagnostic_isolation`, `close_diagnostic_isolation`, `recover_diagnostic_isolation`, `load_active_diagnostic_isolation`.

Outcome matrix:
- no session/prepared: no diagnostic routes, zero lifecycle/provider rows;
- active current boot: exact three routes, operations allowed by capability;
- expired/revoked/closed/recovery_required: no routes, zero new lifecycle/provider rows;
- restart_unvalidated: no routes until fresh revalidation;
- stale/wrong action/owner/profile/customer/bot/destination/pin: reject before append, zero provider;
- mismatch after delivery reservation: preserve existing linked terminal unknown/provider-zero semantics;
- duplicate mutation: original typed duplicate result, no row/provider delta.

## 5. Preflight closed schema

Preserve ordinary `GateDPreflightReceipt` v1 exactly. Add `DiagnosticPreflightOptions` and `DiagnosticPreflightReceipt` schema `diagnostic_preflight_v1`.

Exact receipt fields: `schema_version`, `ready`, `reason_codes`, `checked_at_kst`, `epoch`, `checks`, `counts`, `digests`.

Exact checks: `owner_authenticated`, `same_owner_user`, `full_triples_distinct`, `destinations_distinct`, `review_space_disjoint`, `profile_isolated`, `data_root_private`, `disposable_customer_only`, `test_bot_separate`, `production_routes_absent`, `registry_marked`, `session_state_valid`, `boot_epoch_current`, `ttl_valid`, `customer_state_valid`, `feature_flags_safe`, `plan_window_current`, `schedule_fence_ready`, `artifacts_current`, `consent_current`, `activation_current`, `source_reconciled`, `reservation_baseline_clean`.

Exact counts: `customers`, `role_destinations`, `production_routes`, `reservations`, `schedule_rows`, `schedule_tombstones`, `artifact_kinds`, `session_rows`, `transition_rows`.

Exact digest keys: `owner`, `review_config`, `profile`, `registry`, `session`, `spec`, `test_bot`, `operator_destination`, `customer_destination`, `trainer_destination`, `registration`, `policy`, `catalog`, `constraints`, `source`, `consent`, `activation`, `feature_epoch`, `schedule_fence`.

Bounded reasons: `owner_invalid`, `identity_invalid`, `destination_collision`, `review_collision`, `profile_not_isolated`, `permissions_invalid`, `customer_invalid`, `test_bot_invalid`, `production_route_visible`, `registry_invalid`, `session_missing`, `session_stale`, `restart_revalidation_required`, `ttl_invalid`, `feature_flags_unsafe`, `plan_window_stale`, `schedule_invalid`, `artifact_invalid`, `consent_invalid`, `activation_invalid`, `source_invalid`, `reservation_not_clean`, `corrupt_state`.

The API is byte-for-byte read-only and exposes no raw identifier/path/text/token.

## 6. Coherent read-only incident snapshots

Add `profile_authority_read_lock` that requires the pre-existing 0600 authority lock and opens it read-only/shared without mkdir/create/chmod/write. Snapshot export additionally uses optimistic multi-ledger revision fencing, not an impossible global lock:
1. record canonical size/terminal row digest for every incident source ledger;
2. read bounded required rows through each ledger's existing validator/read lock;
3. record the same revisions again;
4. accept only if all match; otherwise retry up to 3 times then return `snapshot_unstable` without output.

No-mutation tests compare recursive file names, contents, modes, mtimes and sizes; atime is explicitly excluded because reads may update it.

Incident map:
- `incomplete_checkin`: wizard session/event ledger; bounded completed-step bitmap, expected no finalized event; fingerprint branch/state/provider=0.
- `low_adherence`: canonical observations + registration/policy; bounded adherence category/count window; fingerprint decision/operator-only/provider=0.
- `safety_hold`: canonical safety events; bounded reason enum/presence; fingerprint human-review/provider=0.
- `kst_rollover`: wizard/schedule/plan; two KST dates and logical clock boundary; fingerprint exactly one target day.
- `stale_callback|duplicate_callback`: operator session ledger + lifecycle; callback state/action/revision relation; fingerprint reject/duplicate and row/provider delta 0.
- `authority_change`: registry/consent/activation/config/session ledgers; mutation point enum and before/after digests; fingerprint no unauthorized append/provider.
- `provider_timeout_unknown|receipt_audit_pending`: delivery ledger; reservation/receipt/audit booleans and logical interruption point; fingerprint unknown or reconcile-with-provider-0.
- `reservation_race`: schedule/adaptive ledger; contender count bucket and barrier point enum; fingerprint one winner/provider=1.
- `disable_restart`: diagnostic/session/config/schedule ledgers; boot/disable relation; fingerprint detached/disabled/provider=0.

`DiagnosticAuditSnapshot` contains only generated opaque IDs, incident class, KST bounds, normalized state sequence, these bounded inputs/concurrency markers, revision/epoch/digests, booleans/counts and expected fingerprint. Export requires a fresh current-boot owner diagnostic capability. Unsupported/stale/unstable/forged requests create no file.

## 7. Synthetic replay enforcement

`materialize_diagnostic_replay` accepts only the strict snapshot and a digest-bound `DiagnosticReplayMapping`, writes only under the isolated root, rebases KST explicitly, and emits synthetic events with `provenance=diagnostic_synthetic_v1`.

Named enforcement targets:
- profile `models.py`: closed provenance enum/tag;
- `store.py`, `wizard_storage.py`/`wizard.py`, `adaptive_nutrition.py`, `customer_schedule.py`: ordinary loaders reject synthetic provenance; diagnostic replay loaders accept only with active session capability;
- `customer_coaching.py`: diagnostic registry/runtime gate;
- gateway `nutrition_coaching.py`/`telegram.py`: diagnostic context required for synthetic projections.

Materialize only the ledgers named in the incident map. Never copy source capabilities, operational row digests, approvals, destinations, receipt contents or customer text. Verify source expected fingerprint equals replay outcome fingerprint for the incident class.

## 8. Promotion manifest exact boundary

`build_diagnostic_promotion_manifest` is read-only and never deploys. Trust roots: profile source workspace and Hermes repository only. Allow repository-relative `.py`, test files, canonical docs, explicit migration source files, and code-side definitions for these schema keys only: `diagnostic_isolation.schema_version`, `max_ttl_minutes`, `allowed_incident_classes`, `require_disposable_customer`, `require_distinct_destinations`, `require_separate_test_bot`, `restart_requires_revalidation`.

Reject runtime config values, destinations/IDs, `.env`, auth/token/secret files, symlinks, traversal, customer/profile data, ledgers, snapshots, replay fixtures, capabilities, messages and generated artifacts. Manifest records file hashes, verification artifact references, required owner approval, health checks and rollback commands; deployment remains human-owned.

## 9. Files and sequencing

Profile: new `diagnostic_isolation.py`; `customer_admin.py`, `customer_coaching.py`, `models.py`, `store.py`, `wizard.py`, `wizard_storage.py`, `adaptive_nutrition.py`, `customer_schedule.py`, `__init__.py`; tests `test_customer_admin.py`, `test_customer_coaching_domain.py`, `test_customer_schedule.py`, `test_adaptive_nutrition.py`, and a new `test_diagnostic_isolation.py` for focused contracts.

Gateway: `nutrition_coaching_config.py`, `nutrition_coaching.py`, `telegram.py`; tests `test_adaptive_nutrition.py`, `test_nutrition_coaching.py`, `test_telegram_group_gating.py`, `test_telegram_physique_checkin.py`.

Docs: canonical profile `PILOT_RUNBOOK.md`, then hash-aligned pointer/HANDOFF/Korean guide/report only where affected.

One validation-coupled Ultragoal story; mandatory parallel executors by profile state/runtime, gateway bootstrap/routes, snapshot/replay/promotion, and tests/docs. Integrate before a single cleaner + verification + Architect + QA/red-team + terminal Critic gate.

## 10. Verification

Unit: transition/recovery crash points; TTL boundaries; boot fencing; ledger corruption; v1 ordinary receipt unchanged; exact diagnostic schema/reasons; ordinary loader rejects marked registry/synthetic provenance; promotion allowlist; full-tree non-mutation excluding atime.

Integration: Topic-59 bootstrap → hot attach → exact one disposable customer; config/auto-discovery cannot attach; same user at three distinct destinations; private-DM and wrong-route all-ingress provider zero; expiry tick/restart/owner/config/customer/bot/route mutations detach and disable; every partial activation/cleanup recovers fail closed.

Replay: every incident class sources exact ledgers, retries unstable reads, materializes only synthetic bounded rows and proves fingerprint equivalence.

E2E/red-team: all ingress kinds; stale/forwarded/duplicate callbacks; KST rollover; incomplete/low-adherence/safety; unknown/audit pending; thread/subprocess races; disable/restart. P2→P6 automated isolated fixture proves existing at-most-once/audited-only semantics and final flags false, delivery revoked, overlay rollback, disposable customer disabled, session closed and processes stopped. Actual Telegram/account/token/manual P2→P6/real deployment remain human-only.

Observability is typed return/local non-sensitive logs/metrics only; no Telegram diagnostic UI beyond explicit authenticated actions.

## Acceptance criteria
1. Ordinary loaders, same-user/distinct-full-triple legality and v1 preflight remain unchanged.
2. Diagnostic workflows and marked registry visibility—not identity sharing itself—require a current-boot active session and consumed owner-authenticated bootstrap capability.
3. Config alone, automatic discovery, ordinary loader, private DM or production capability can never attach diagnostics.
4. Every crash point converges to attached+active or detached+disabled; ambiguous state is recovery_required/provider zero.
5. Snapshot is coherent by revision fence, non-mutating, bounded and authorized; replay is synthetic and fingerprint-equivalent.
6. Promotion manifest contains only allowlisted reviewed changes and does not deploy.
7. Profile full/focused gateway tests and compile checks pass; unrelated failures are reported.
8. Cleaner zero blockers, Architect CLEAR/APPROVE, executor QA/red-team passed and terminal Critic OKAY.
9. Live Telegram, credentials, manual P2→P6 and real-customer deployment remain human-only.

## Intent reconciliation
The prior solo setup is valid because roles use distinct full triples/destinations even when one user owns them. The implementation adds safe diagnostic admission and isolation rather than changing ordinary identity semantics. Problems are reported and fixes requested in this development session; no natural-language Telegram diagnostic UI is added.

## ADR
Decision: Topic-59-authenticated hot-attached diagnostic runtime with marked registry, crash journal, current-boot fencing, coherent bounded snapshots, synthetic replay and allowlisted promotion manifest. Rejected: permanent master bypass and universal three-account requirement. Consequence: broader implementation/test surface, but no ordinary authorization weakening. Human owns live rehearsal and deployment.

# Revision 27 Binding Addendum
This addendum is normative and resolves every Stage-26 ambiguity; Revision 26 remains in force where not replaced below.

## A. Topology: separate dormant diagnostic gateway process
Replace “hot-attach to the ordinary gateway” with a **separate diagnostic gateway process/TelegramAdapter using the existing test bot and isolated profile**. It reuses production gateway code but has a distinct process, profile root, config file, bot token environment and route table. The production gateway is never modified or dynamically attached.

Why chosen: strongest bot/root/process isolation and simpler crash semantics. Rejected hot attachment because one adapter owns one bot and cross-root dynamic routes are ambiguous. Rejected a bespoke second implementation because it would drift from production behavior.

The diagnostic process starts dormant: only the authenticated test Topic-59 review route exists. It cannot expose customer/trainer/lifecycle/provider routes until an owner-authenticated diagnostic activation succeeds. Config locates resources but never authorizes activation.

## B. Closed spec and bootstrap data path
Store exactly one owner-approved private file at `<isolated_root>/config/diagnostic-isolation.json` (regular file, mode 0600, parent modes private, no symlinks). Closed `DiagnosticIsolationSpecV1` fields:
- `schema_version="diagnostic_isolation_spec_v1"`
- `spec_id` opaque UUID
- `authority_profile_root` canonical absolute path
- `isolated_profile_root` canonical absolute path
- `registry_relpath="customers/registry.json"`
- `disposable_customer_key` (internal only)
- `review_operator`, `operator_destination`, `customer_destination`, `trainer_destination` full triples (internal only)
- `test_bot_account_id`, `production_bot_account_digest`
- `allowed_incident_classes` exact enum tuple
- `max_ttl_minutes` integer 1..60
- `approved_by` full canonical owner triple, `approved_at_kst`
- `supersedes_digest`, `spec_digest`, `authority_digest`.

The diagnostic config contains only `diagnostic_spec_path` relative to the isolated root and the authority root path. On startup `DiagnosticHost.__init__` resolves both roots with `Path.resolve(strict=True)`, verifies allowed configured parent roots, privacy/modes/symlinks, exact spec digest/owner authority, and the dormant adapter’s authenticated bot account ID (live `get_me` only during human-run startup; tests inject a typed identity) against `test_bot_account_id`. It compares the separately owner-approved production bot account digest without reading a production token. A mismatch stops startup. No scan, caller path or alternate spec is accepted.

Topic-59 shows only the exact authenticated `diagnostic_prepare` action for this sole spec. `AdaptiveOperatorService` mints the bootstrap capability from the already-loaded verified spec plus refreshed authority; the capability contains the spec digest, not paths. `DiagnosticHost.activate(capability)` is the only loader branch. Config/spec can locate data but cannot activate without the consumed Topic-59 capability.

`operator_destination` is the configured Topic-59 review triple. Customer and trainer destinations are two other pairwise-distinct test spaces; there is no fourth operator space.

## C. Root ownership, locks and final revalidation
`authority_profile_root` owns canonical owner/config approval and the read-only incident export request ledger. `isolated_profile_root` owns diagnostic spec, session, transition, registry, replay and test state.

Any operation touching both uses resolved-path lexical lock order via `multi_profile_authority_lock((authority_root, isolated_root))`, deduplicated and non-reentrant across roots. Writes acquire exclusive locks; read snapshots use read locks/revision fences. Immediately before session/registry append and again before any provider reservation/call, reload and compare authority owner/version, spec digest, bot identity, registry/session/boot and all lifecycle pins. Concurrent change => no attach/append/provider before reservation; existing terminal linked behavior after reservation.

Gateway boot epoch is `secrets.token_hex(32)` generated once in `DiagnosticHost.__init__`, never persisted as reusable authority, injected into every profile session API and capability, and exposed to tests through an explicit constructor parameter. Persisted rows store its digest. A new process therefore makes prior active rows `restart_unvalidated`.

## D. Exact runtime ownership
Add `DiagnosticHost` in gateway `nutrition_coaching.py` (or a narrowly named sibling module if file size requires) owning:
- `_boot_epoch`, `_verified_spec`, `_diagnostic_session`, `_child_coordinator`, `_role_routes`
- `activate(capability)`, `revalidate(capability)`, `detach(reason)`, `recover_startup()`, `_expiry_loop()`, `start()`, `stop()`.

Startup order: verify dormant spec/bot → recover profile transitions and force old sessions disabled → register only Topic-59 diagnostic control route → start 30-second expiry task. Activation transaction commits profile active state before installing child routes; if route installation fails, detach and run cleanup transaction. Route collision rejects before profile activation. Attach is idempotent by session digest. Detach atomically removes role routes under adapter route lock before isolated registry disable/terminal append. A crash after profile active but before route install is recovered as disabled/restart_unvalidated on next startup; a crash after route removal but before disable is likewise completed on startup. `stop()` cancels/awaits expiry task, removes routes, disables/terminalizes, then closes the adapter. Exceptions leave routes absent and `recovery_required`.

## E. Snapshot source authority and exact revision tokens
Future production incident evidence is exported **in the source process/root**, never by the isolated process reading production directly. Add Topic-59 action `diagnostic_audit_export` to the source gateway. It mints a one-use `DiagnosticAuditCapability` bound to source profile/customer/incident/window/owner/live pins and writes only the bounded snapshot to an owner-designated private local artifact directory already configured by the host. The development session may then read that artifact. The isolated diagnostic capability can import only its digest-verified bounded form.

For pre-Gate-D rehearsal, source type is `synthetic_baseline`; no production source root/customer is read.

Revision-fence table:
- wizard/store/schedule/adaptive/operator/delivery JSONL: `(st_dev, st_ino, size, terminal_row_digest)` before/after using each module’s validated reader;
- registry/config/spec/artifact JSON: `(st_dev, st_ino, size, sha256(bytes))` before/after;
- feature epoch/activation/consent/registration: validated canonical document digest plus file tuple;
- directories: sorted `(relative_name, file_tuple)` manifest for only named files.
Any token changes during three retries => `snapshot_unstable`, no artifact. Source capability permits exactly one source customer; snapshot excludes its raw key.

Exact source paths are resolved through existing APIs, not arbitrary paths:
- incomplete/KST: `WizardStorage` drafts/events plus canonical `store.py` events;
- low-adherence/safety: canonical events through `AdaptiveEventStore` plus approved registration/policy;
- callback/authority: adaptive operator session ledger plus canonical registry/consent/activation/config readers;
- provider/audit/reservation: `AdaptiveEventStore` journals and customer schedule validated ledger APIs;
- disable/restart: diagnostic session/config/schedule readers.

`DiagnosticReplayMappingV1` lives at `<isolated_root>/data/diagnostic-replay-mappings/<snapshot_digest>.json`, mode 0600, and contains schema, snapshot digest, disposable customer digest, role destination digests, KST rebase date, approved owner capability digest/time, mapping digest. It stores no source IDs. Replay entrypoint is `run_diagnostic_replay(snapshot, mapping, host)` and returns the incident fingerprint; only a current active diagnostic host accepts synthetic provenance.

## F. Fixed promotion roots and migrations
Root derivation is compiled/host-owned, not caller supplied:
- profile source root: `Path(checkin_cli.__file__).resolve().parent.parent`
- Hermes root: walk parents from `gateway.platforms.nutrition_coaching.__file__` until exact package markers `gateway/` and `tests/gateway/` exist; require canonical expected repository identity recorded in the promotion policy.

Migration allowlist is empty for v1 unless a plan amendment names an exact repository-relative migration file. Thus the first implementation may promote code/tests/docs and the seven listed config-schema definitions only; no migration artifact is automatically allowed.

## G. Exact verification and row terminology
Profile commands, cwd `/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli`:
- `.venv/bin/python -m pytest -q`
- `.venv/bin/python -m compileall -q checkin_cli`

Gateway cwd `/home/cube/projects/richard/hermes-agent`:
- mandatory focused: `.venv/bin/python -m pytest -q tests/gateway/test_adaptive_nutrition.py tests/gateway/test_nutrition_coaching.py tests/gateway/test_telegram_physique_checkin.py tests/gateway/test_telegram_group_gating.py`
- `.venv/bin/python -m py_compile gateway/platforms/nutrition_coaching.py gateway/platforms/telegram.py gateway/platforms/nutrition_coaching_config.py`
- full `tests/gateway` is run and reported; pre-existing unrelated failures do not silently pass or expand this feature scope, while any changed-surface failure blocks completion.
- docs/hash/link tests and `/tmp` API/algorithm test report are mandatory.
- no network/live Telegram; tests inject bot identity/provider.

Row deltas distinguish `diagnostic_session_rows`, `diagnostic_transition_rows`, `nutrition_lifecycle_rows`, `delivery_rows`, `schedule_rows`, and `provider_calls`:
- prepared: +1 session, no routes/lifecycle/delivery/provider;
- active: prepared/committed transition plus active session, exactly 3 role routes, no nutrition/delivery/provider until action;
- restart_unvalidated/terminal/recovery: cleanup transition+state permitted, routes 0, nutrition/delivery/provider 0;
- stale/wrong/expired capability: all row classes 0 except an optional bounded rejection audit row explicitly excluded from authority, provider 0;
- duplicate: no row/provider delta;
- post-reservation authority loss: existing delivery terminal row only, provider 0;
- recovery: diagnostic transition/session rows only, all product/provider rows 0.

Expected preflight state reasons: prepared=`customer_state_invalid`; prior boot=`restart_revalidation_required`; expired=`session_stale`; terminal=`session_missing`; corrupt=`corrupt_state`; active clean has no reasons.

## H. Parallel ownership
Sequence load-bearing interfaces:
1. Profile-state executor exclusively owns new `diagnostic_isolation.py`, `customer_admin.py`, `customer_coaching.py`, `__init__.py` and publishes typed contracts.
2. After that contract lands, snapshot/replay executor owns a new `diagnostic_evidence.py` plus provenance loader edits; it does not edit `diagnostic_isolation.py`.
3. Gateway executor consumes the finished profile contract.
4. Tests/docs can parallelize only over disjoint files after APIs stabilize. Leader owns integration and all verification.


# Revision 28 Final Binding Addendum
This addendum is normative, resolves Stage-27 review findings, and overrides conflicting wording in Revisions 26–27.

## 1. Exact trust anchors and digest canonicalization
The distinct diagnostic process’s existing configured `profile_root` **is** `isolated_profile_root`; the config cannot provide a second isolated path. Compiled host policy permits only:
- authority root exactly `/home/cube/.hermes/profiles/physique-coach` after strict resolution;
- isolated roots strictly beneath `/home/cube/.hermes/test-profiles/`, never equal to or inside the authority root, Hermes repository, or another profile root.

These constants and derivation rules live in new package resource `checkin_cli/policies/diagnostic-promotion-policy.json`, installed with the profile package and validated against a code-embedded SHA-256 constant in `diagnostic_evidence.py`. Callers/specs cannot replace the policy. Alternate parent/root/spec substitution fails before any Telegram handler is registered.

`spec_digest` is SHA-256 of canonical JSON over every spec field except `spec_digest` and `authority_digest`. `authority_digest` is SHA-256 of `{spec_digest, approved_by full triple, approved_at_kst, supersedes_digest}`. Telegram bot account canonical value is a positive decimal bot account ID string; its digest is SHA-256 canonical JSON `{bot_account_id}`. `production_bot_account_digest` comes from an owner-approved field in the authority profile’s canonical adaptive config, loaded and digest-checked under the authority lock; no production token is read.

## 2. Dormant control service and exact actions
Add `DiagnosticControlService(host, authority_loader, session_store)`; it does **not** require `NutritionCoachingCoordinator`, an enabled registry, or customer/trainer routes. The dormant test-bot `TelegramAdapter` constructs this service after spec/bot/root validation and registers one persistent Topic-59 first-match control route.

Persisted callback actions and transitions are exact:
- `diagnostic_prepare`: no session or terminal -> prepared; consumes one-use 10-minute bootstrap capability; returns a prepared card with `diagnostic_activate` and `diagnostic_close` callbacks.
- `diagnostic_activate`: prepared -> active; separately issued/consumed one-use capability; runs activation transaction then installs role routes.
- `diagnostic_revalidate`: restart_unvalidated -> active; one-use capability after fresh owner/spec/bot/pin validation.
- `diagnostic_close`: prepared|active|restart_unvalidated|recovery_required -> closed; one-use capability; removes role routes then cleanup transaction.
- `diagnostic_audit_export`: source gateway only, handled by `DiagnosticAuditExportService`; never registered in the isolated host.

Each callback uses the existing bounded persisted-session pattern, exact originating message/review triple, action, nonce, expiry and live pins. Duplicate/stale/wrong actions yield no transition/product/provider delta. No natural-language diagnostic command exists.

## 3. Adapter route registry contract
Add adapter-owned `self._diagnostic_route_lock: asyncio.Lock` and `self._diagnostic_routes: Mapping[(chat_id, topic_id), DiagnosticRoleRoute]`.

Methods:
- `async _install_diagnostic_role_routes(session_digest, customer_route, trainer_route)`: under lock verify map empty, Topic-59 persistent review route distinct, exact session current, and no generic/customer/trainer collision; atomically replace map with exactly two entries.
- `async _remove_diagnostic_role_routes(session_digest)`: under lock require matching digest or already empty, then atomically clear.
- `async _lookup_diagnostic_role_route(update)`: under lock snapshot exact route; after lock, revalidate host session immediately before dispatch.

The three diagnostic role destinations are persistent Topic-59 operator route plus the two dynamic customer/trainer entries. All Telegram ingress handlers call review first-match, then diagnostic lookup, then ordinary routing. This isolated adapter has no ordinary customer/trainer coordinator. Install/remove races with expiry/stop have one lock winner; removal wins fail closed and dispatch revalidation prevents a stale snapshot. Tests cover install-vs-expiry, dispatch-vs-close, stop-vs-callback and route collision.

## 4. Source export is the only bounded production/source modification
Replace “production gateway is never modified” with: production/source gateway receives **no diagnostic customer/provider routes and never attaches an isolated root**, but may expose one authenticated `diagnostic_audit_export` action through its existing Topic-59 `AdaptiveOperatorService`.

Closed config field `adaptive_nutrition.diagnostic_audit_export_root` is an absolute path beneath compiled `/home/cube/.hermes/diagnostic-exports`, regular private directory mode 0700, no symlink; it is location only, not authority. Output files are mode 0600.

`DiagnosticAuditExportService` consumes a one-use capability ledger row before reading, builds bytes in memory after stable revision fencing, validates bounded schema/forbidden values, then writes a uniquely generated temporary file with `O_CREAT|O_EXCL|O_NOFOLLOW`, fsyncs, atomically renames to `<opaque_snapshot_id>.json`, and fsyncs directory. Any read/validation/write failure removes temp and produces no final artifact. Repeated capability use creates nothing. No customer/provider/lifecycle state is mutated.

## 5. Exact evidence readers
- Wizard incomplete/KST: `WizardStorage.locked()` plus selected draft path `_drafts / f"{validated_session_id}.json"`; selection is by existing `load(session_id)`/model validation, never glob-based caller input. Token includes draft file tuple+digest. Finalized canonical event is read through `EventStore` validated event lookup/path.
- Canonical check-in/safety/adherence: `AdaptiveEventStore.canonical_events_path` and `canonical_sequence_path`, read via its canonical validators under `AdaptiveEventStore.locked()` read snapshot; approved inputs use canonical admin loaders and JSON token.
- Callback/session: `AdaptiveOperatorService` validated session ledger at its fixed profile data path plus adaptive main `AdaptiveEventStore.path`.
- Authority change: canonical registry loader, consent/activation/config documents and `AdaptiveEventStore.authority_path`, `config_epoch_path`.
- Provider timeout/audit pending: `AdaptiveEventStore.path` only, filtered to exact reservation/receipt/delivered/audit event chain by validated reader.
- Source mapping: `source_day_path` and `source_intent_path` only when the incident fingerprint references source-day resolution.
- Overlay/disable: `overlay_path`, `config_epoch_path`, diagnostic session/transition ledgers.
- Schedule race/restart: public validated `schedule_delivery_ledger()` plus fence/tombstone inventory APIs; no direct arbitrary schedule path.

All JSONL readers use file tuple+terminal row digest; JSON uses file tuple+bytes digest. Lock acquisition is source-module lock first for one bounded read, released before the next; global coherence is established only by the full before/after token retry. No two subsystem locks are held together.

## 6. Promotion identity policy
`checkin_cli/policies/diagnostic-promotion-policy.json` contains schema version, package-relative profile markers (`pyproject.toml`, `checkin_cli/__init__.py`, `tests/`), Hermes markers (`gateway/__init__.py`, `gateway/platforms/nutrition_coaching.py`, `tests/gateway/`), allowed suffix/classes and the empty migration allowlist. `diagnostic_evidence.py` compares package-resource bytes to its embedded digest before deriving roots; candidate roots must contain exact marker relative paths and cannot supply expected identity themselves.

## 7. Exact verification/report commands
Profile cwd `/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli`:
- `.venv/bin/python -m pytest -q`
- `.venv/bin/python -m compileall -q checkin_cli`

Gateway cwd `/home/cube/projects/richard/hermes-agent`:
- `.venv/bin/python -m pytest -q tests/gateway/test_adaptive_nutrition.py tests/gateway/test_nutrition_coaching.py tests/gateway/test_telegram_physique_checkin.py tests/gateway/test_telegram_group_gating.py`
- `.venv/bin/python -m py_compile gateway/platforms/nutrition_coaching.py gateway/platforms/telegram.py gateway/platforms/nutrition_coaching_config.py`
- `.venv/bin/python -m pytest -q tests/gateway` (run and report; changed-surface failure blocks, unrelated pre-existing failures remain explicit).

Docs/hash/link checks are implemented as named tests in `tests/test_diagnostic_isolation.py`: `test_canonical_runbook_hash_and_pointers_match`, `test_diagnostic_runbook_has_no_direct_enable_or_live_command`, `test_promotion_policy_resource_digest_matches`. Thus they run in profile full suite.

Leader creates `/tmp/diagnostic-isolation-test-report.json` only after runs, with exact keys: `generated_at_kst`, `profile.{passed,failed,compileall_exit}`, `gateway_focused.{passed,failed,py_compile_exit}`, `gateway_full.{passed,failed,skipped,unrelated_failures}`, `diagnostic.{session_states,ingress_cases,incident_classes,promotion_rejections,manual_gate_d_run}`, `commands`, `artifact_digests`. Validate with:
`python -c 'import json,pathlib; p=pathlib.Path("/tmp/diagnostic-isolation-test-report.json"); d=json.loads(p.read_text()); assert d["profile"]["failed"]==0 and d["gateway_focused"]["failed"]==0 and d["diagnostic"]["manual_gate_d_run"] is False'`.
No network/live provider is permitted in any command.

## 8. Exact preflight state checks
All common checks remain as listed in Revision 26. State-specific values:
- no session: `session_state_valid=false`, `boot_epoch_current=false`, `ttl_valid=false`, reason `session_missing`;
- prepared current boot: session true, boot true, ttl true, `customer_state_valid=false`, reason `customer_state_invalid`, routes/provider zero;
- active clean: all state checks true, no reason;
- prior boot/restart_unvalidated: session true, boot false, ttl per time, reason `restart_revalidation_required`;
- expired: session false, boot comparison bounded, ttl false, reason `session_stale`;
- revoked/closed: session false, reason `session_missing`;
- recovery_required/corrupt: session false and relevant registry/schedule checks false, reason `corrupt_state`.
Counts/digests remain bounded even on failure; missing values use zero counts and canonical zero digest.

## 9. Compile ownership and manual boundary
If gateway implementation creates `gateway/platforms/diagnostic_isolation.py`, add it to `py_compile`; otherwise keep `DiagnosticHost` and `DiagnosticControlService` in `nutrition_coaching.py`. The plan prefers the sibling module to contain host/control logic and reduce the existing file’s load; compile command then includes it.

Live test-bot startup, `get_me`, Telegram account/token use, manual P2→P6, and real deployment remain human-only. Automated tests inject bot identity and providers.
