# Diagnostic Isolation — Constructor Closure Plan

## Scope
Incorporates Stage 33 and changes only three construction contracts. User approved Ultragoal execution after consensus.

## RALPLAN-DR
Principles: durable identity differs from process identity; registered adaptive paths must be impossible to construct without paired canonical persistence; dormant control must not require an enabled coordinator. Drivers: restart safety, constructor-enforced invariants, testability. Chosen option is explicit typed factories and sealed bindings. Duck typing, ambient globals and optional production fallbacks are rejected.

## 1. Canonical transaction factory and enforcement
Add `CanonicalEventTransaction.for_customer_runtime(runtime: CustomerRuntime)` in `store.py`. `CustomerRuntime` exposes canonical resolved `customer_root`, `wizard_root`, and `nutrition_plans_root`; factory derives:
- events: `wizard_root/events.jsonl`
- sequence: `nutrition_plans_root/canonical-sequence.jsonl`
- lock: `wizard_root/.events.lock`
It validates containment/privacy/no symlinks and returns the only registered-customer transaction.

`WizardService.__init__(wizard_root, *, canonical_transaction=None, adaptive_registered=False)` enforces `adaptive_registered => canonical_transaction is not None` before creating `EventStore`. `NutritionCoachingCoordinator._configure_registry` builds the transaction from each enabled `CustomerRuntime` and passes both arguments. Customer admin activation/reconciliation and every adaptive wizard construction use the same factory. `EventStore` receives the transaction and registered finalization calls paired transaction append; if absent in a registered/adaptive flow it rejects before event bytes. Standalone nonregistered `WizardService`/`EventStore` retains event-only behavior.

Tests: registered customer cannot construct/finalize without transaction; exact paths/lock; coordinator/admin/reconcile all use factory; standalone unchanged; batch/single append paired; no nested flock.

## 2. Dormant delivery authority and transport factory
Add `DiagnosticDeliveryAuthority` independent of any coordinator. Constructor takes verified spec, boot epoch, profile session store, registry/artifact loaders and immutable adapter/test-bot identity. Methods:
- `verify_reservation(session_digest, generation, reservation_id, destination_digest, deadline)`
- `verify_provider_start(...)`
- `terminalize_cancel_or_timeout(...)`
Every method reloads current session/spec/owner/registry/bot/pins under the established admission/profile locks.

Add `TelegramCustomerTransport.for_diagnostic(adapter, verified_spec, authority)`. It requires the dormant adapter’s exact verified bot identity, exact customer destination from spec, sealed diagnostic method version, and native Telegram async sender. It does not require `NutritionCoachingCoordinator`. It returns a transport whose only diagnostic method is:
`async send_diagnostic_customer(body, *, reservation_id, session_digest, generation, deadline_monotonic, destination_digest)`.
The method delegates authority verification then calls the exact adapter bot with bounded connect/pool/write/read timeouts. Generic send compatibility is unavailable on this object.

Construction order: validate roots/spec/bot → create session store and `DiagnosticDeliveryAuthority` → create sealed diagnostic transport → create `DiagnosticHost` → create dormant `DiagnosticControlService` → register Topic-59 only. Activation later creates the child coordinator and injects the already frozen transport explicitly. No circular coordinator dependency.

## 3. Durable transport binding digest
Define canonical preimage:
`{schema_version:"diagnostic_transport_binding_v1", adapter_kind:"telegram-test-bot", test_bot_digest, customer_destination_digest, max_provider_timeout_seconds, method_version:"send_diagnostic_customer_v1", authority_spec_digest}`.
`diagnostic_transport_binding_digest = sha256(canonical_json(preimage))`.

The digest is computed before session prepare from verified durable inputs and stored in spec approval/config projection, every diagnostic session/transition/capability/reservation row, and preflight digest map. It never contains Python object identity. On restart the factory reconstructs authority/transport from the same verified spec and adapter identity and requires exact digest equality before Topic-59 prepare/revalidate. Changed bot/destination/timeout/method/spec invalidates old sessions.

`DiagnosticHost` additionally freezes the process-local transport reference and checks `transport is self._transport` under admission lock from reservation through provider completion. This is an extra runtime TOCTOU check only. Coordinator setters reject while diagnostic state is nonterminal.

Tests: digest vectors; same-value restart rebind succeeds only current boot revalidation; any preimage mutation rejects; object swap with same marker/digest rejects by identity; old process object is never serialized; dormant factory requires no coordinator; setter race provider zero.

## Pre-mortem and verification
- Missing transaction writes unpaired event: constructor/finalization rejection.
- Dormant transport accidentally needs coordinator: constructor test with no coordinator.
- Restart trusts object identity: digest-vector/reconstruction tests.
- Swap after validation: frozen-reference admission test.

Run full profile pytest+compileall; focused gateway tests+py_compile including diagnostic sibling; full gateway truthfully; wheel/policy and read-lock/race tests from Stage 33; cleaner/Architect/QA/Critic. No live Telegram, credentials, manual P2–P6 or deployment.

## Acceptance
All registered adaptive wizard events are paired by factory construction; dormant host builds without coordinator; durable binding is canonical/restartable and process identity is only an additional check; all Stage-33 contracts remain unchanged.

## Intent reconciliation
No new user choice: this only makes the previously approved isolated diagnostic design constructible and restart-safe.

## ADR
Use typed factories and a durable binding digest; reject optional production fallbacks and process-object persistence.

# Revision 35 Binding Addendum
This addendum resolves every Stage-34 finding.

## Sealed registered versus standalone construction
Add frozen `RegisteredCustomerBinding` to `customer_coaching.py`, created only by committed/diagnostic registry loaders with a module-private sentinel. Fields: customer key digest, resolved data-root digest, registry digest/version, activation digest, mode, binding digest, and nonserializable private sentinel. `CustomerRuntime` from a registry always carries it.

`WizardService` and `EventStore` have no permissive public constructor defaults. Their internal constructors require a module-private construction token. Public factories are disjoint:
- `WizardService.for_registered(runtime: CustomerRuntime)` requires and validates `RegisteredCustomerBinding`, builds `CanonicalEventTransaction.for_customer_runtime(runtime)`, and constructs registered EventStore.
- `WizardService.for_standalone(home)` explicitly creates event-only standalone mode and rejects a `CustomerRuntime`/registered binding.
- `EventStore.for_registered(transaction, binding)` and `EventStore.for_standalone(home)` mirror this split.

Direct `WizardService(home)`, `EventStore(home)`, omitted transaction/binding, forged binding, or standalone factory passed a registered runtime reject before opening/appending bytes. Every production registered callsite in gateway coordinator, wizard handlers, customer admin, reconciliation, activation and tests uses `for_registered`. Explicit standalone tools/tests use `for_standalone`. Loader absence/corruption rejects before service construction. Tests assert direct bare construction and omitted binding cannot append, while standalone remains compatible through its explicit factory.

## Exact diagnostic delivery bridge
Add typed `DiagnosticDeliveryRequest(session_digest, generation, reservation_id, destination_digest, transport_binding_digest)`; it contains no body. Add frozen `VerifiedDiagnosticReservation` containing canonical reserved body bytes, body digest, exact destination binding, reservation row digest, receipt pin, session/generation/binding digests, and a module-private verification token.

`DiagnosticDeliveryAuthority` exact methods, all requiring the host’s private admission-lock token:
- `verify_reservation(request, *, lock_token) -> VerifiedDiagnosticReservation`: reload session/spec/owner/registry/reservation, require active/current generation/binding, verify reservation row and immutable body/body digest, and return canonical reserved bytes. Missing/stale/conflict raises `DiagnosticAuthorityError` before provider.
- `verify_provider_start(verified, *, deadline_monotonic, lock_token) -> VerifiedDiagnosticReservation`: revalidate every live pin, exact reservation/body/destination/binding and positive deadline; return the same verified object only if unchanged.
- `terminalize_cancel_or_timeout(verified, *, receipt, reason, lock_token) -> Mapping`: append exactly one linked unknown when receipt absent or audit-pending when receipt present; idempotently return existing exact terminal, conflict fails closed.

Add `DiagnosticHost.deliver(request)`. While holding `_admission_lock`, it calls verify_reservation, creates/validates durable admission as required by Stage 33, calls verify_provider_start, then calls the frozen transport directly with `VerifiedDiagnosticReservation`—never the ordinary `send_adaptive_customer` discovery branch. Transport signature:
`async send_diagnostic_customer(verified: VerifiedDiagnosticReservation, *, deadline_monotonic: float) -> Mapping`.
It sends only `verified.body_bytes` to `verified.destination_binding`; caller-supplied body/destination is impossible. Host persists receipt/audit or uses terminalize method on timeout/cancellation before releasing lock. Activated child coordinator delegates diagnostic send to `DiagnosticHost.deliver`; generic customer transport path is prohibited in diagnostic mode.

Integration tests cover exact successful argument propagation; stale reservation/generation/body/destination/authority/binding provider zero; cancellation/timeout terminal once; close race; ordinary generic sender never called.

## Acyclic digest schema
Amend `DiagnosticIsolationSpecV1` with fields `spec_core_digest` and `diagnostic_transport_binding_digest`. Canonical hash order:
1. `spec_core_preimage` = all durable spec inputs except `spec_core_digest`, `diagnostic_transport_binding_digest`, `spec_digest`, `authority_digest`, and approval metadata. `spec_core_digest=SHA256(canonical_json(spec_core_preimage))`.
2. transport preimage = `{schema_version:"diagnostic_transport_binding_v1", adapter_kind:"telegram-test-bot", test_bot_digest, customer_destination_digest, max_provider_timeout_seconds, method_version:"send_diagnostic_customer_v1", spec_core_digest}`. Hash to `diagnostic_transport_binding_digest`.
3. `spec_digest=SHA256(canonical_json({spec_core_digest, diagnostic_transport_binding_digest}))`.
4. `authority_digest=SHA256(canonical_json({spec_digest, approved_by, approved_at_kst, supersedes_digest}))`.

The owner-approved spec/config projection stores all four digests and exact schema versions. Every session/transition/capability/reservation stores spec, authority and transport-binding digests. On restart the transport factory recomputes steps 1–4 from verified bytes and current adapter identity, requiring exact equality before revalidation. Python object identity is never serialized; `is` remains an additional process-local check.

Golden vector tests parse fixed raw spec bytes and independently recompute all four digests, reject field/exclusion/order mutation, and prove no cycle or object representation enters canonical JSON.

## Acceptance additions
- Registered and standalone persistence are reachable only through disjoint sealed factories; no optional fallback exists.
- Diagnostic provider receives only authority-returned reservation-pinned bytes/destination through `DiagnosticHost.deliver`.
- Digest derivation is ordered, acyclic, byte-reproducible and restart-safe.
