# NutriCoach Channel Inbox v1.5.0

## Goal

Let customers contact NutriCoach through Telegram Channel Direct Messages while
staff manage every customer in one forum-like channel inbox. A customer must
only see their own conversation. The existing bot-DM and owner operations-room
flows remain the default and remain available for rollback.

The same candidate adds `nutricoach_multi_customer_v1`: a bounded admission
capability that permits several consenting customers without weakening
per-customer activation, storage, routing, or delivery authority.

## Release boundary

- Semantic version: `v1.5.0` compatible feature.
- Capability: `nutricoach_channel_inbox_v1`.
- Capability: `nutricoach_multi_customer_v1`.
- Default: OFF for every profile and customer.
- Development base: frozen v1.1 source in an isolated sibling worktree.
- Integration target: combine the verified v1.4 source and both v1.5
  capabilities into one candidate before the seven-day observation.
- This worktree never modifies the sealed v1.4 candidate, its upgrade package,
  live profile data, or the Aug 31 watcher.

## Actors and authority

- Customer: an enabled, consenting registry customer whose Telegram user ID
  exactly matches `DirectMessagesTopic.user.id`.
- Owner/staff: Telegram channel administrators. Their messages are not accepted
  as customer input by the bot.
- Bot: must have the channel right required to manage direct messages.
- Profile authority: exact direct-messages chat ID, parent channel ID, bot ID,
  loaded candidate digest, and a derived capability digest.
- Startup calls `getChat` and `getChatMember`; the direct-messages parent and
  `can_manage_direct_messages` must match before ingress or outbound routing is
  enabled. A config flag or arbitrary digest alone is insufficient.

## Surfaces

- Customer surface: a private direct-message conversation with the NutriCoach
  channel.
- Owner surface: Telegram's forum-like channel direct-message inbox, one topic
  per customer.
- Existing surface: bot private DM and the owner-only operations group remain
  unchanged while the capability is OFF.

## State and data

1. `OFF`: channel-DM updates are not routed into NutriCoach.
2. `ARMED`: config, channel ID, bot authority, registry, and capability receipt
   are valid; customers still use their existing bot-DM routes.
3. `ROUTED`: a permission-sealed activation has changed one customer's
   canonical registry route to the exact channel direct-messages chat/topic.
4. `DISABLED`: the flag is OFF. A separately approved registry rollback returns
   the customer to the archived bot-DM route.

The runtime adds no mutable routing sidecar. The existing customer registry
remains the sole route authority. Topic discovery and the proposed registry
mutation are activation evidence outside the runtime; they require a separate
one-use permission seal before `ROUTED`.

The shipped migration core is pure: it canonicalizes registry bytes, binds
before/after hashes, derives an exact approval phrase, applies only the expected
customer route in memory, and proves byte-identical rollback. A later live
controller must additionally stop the Gateway, verify no pending customer
workflow, persist atomically, restart, and smoke-test.

## Bounded multi-customer admission

- A legacy registry without the capability remains limited to one enabled
  customer.
- A versioned, frozen admission policy sets an explicit limit from 2 through
  20. The initial live pilot uses 5.
- Multiple disabled drafts remain allowed while the capability is OFF.
  Enabling each customer still requires that customer's consent, onboarding,
  owner review, membership, readiness, and activation transaction.
- Activation receipts are durable per customer. Adding or activating another
  customer cannot invalidate an unrelated customer's receipt. Changing a
  customer's projection, the owner, or the admission policy fails closed.
- Activations remain serialized by the profile authority lock. Crash recovery
  reconciles one in-flight activation without sharing receipts or data roots.
- When multiple customers are enabled, the production operator console
  requires an explicit `customer_key`; it never selects the first customer.
- Every customer retains a distinct registry key, Telegram route, data root,
  wizard state, event store, schedule fence, draft, delivery claim, and
  customer-surface receipt.
- Expanding capacity is a separately permission-sealed registry migration.
  Removing the policy is allowed only after all but one customer are disabled;
  rollback never silently disables customers.

## Inbound contract

An update is accepted as customer input only when all are true:

- capability is enabled and receipt-bound;
- `chat.is_direct_messages` is exactly true;
- chat ID equals the configured direct-messages chat ID;
- `message.direct_messages_topic` exists;
- topic ID is a positive Telegram-safe integer;
- `topic.user` exists;
- `message.from_user.id == topic.user.id`;
- exactly one enabled, consenting registry customer has that exact
  user/chat/topic route;
- no conflicting registry route exists.

The normalized source uses the direct-messages chat as `chat_id`, the topic as
`thread_id`, and the topic user as `user_id`. This preserves existing customer
resolution and Telegram's `direct_messages_topic_id` outbound routing.

Admin/channel-originated messages, anonymous senders, unknown customers,
ambiguous registry entries, wrong channels, malformed topics, and conflicts are
ignored or fail closed before session creation or persistence.

## Outbound contract

- A live reply stays in the triggering direct-message topic.
- A scheduled send may use the registry topic only when the capability,
  receipt, and registry identity still match.
- Otherwise delivery falls back to the existing bot-DM route.
- `message_thread_id` and `direct_messages_topic_id` are never sent together.
- No cross-customer topic fallback is allowed.

## Privacy and observability

- Customers never join the owner operations group.
- Telegram supplies customer isolation through Channel Direct Messages.
- Logs contain customer keys and typed reason codes, not message bodies or raw
  user IDs.
- Durable nutrition events remain authoritative; the Telegram inbox is an
  operational view.

## Migration and rollback

- No historical message migration.
- Existing customers remain on bot DM until a channel topic is proven and a
  permission-sealed registry migration is committed.
- Turning the capability OFF prevents channel-inbox ingress. Rolling a customer
  registry route back to its archived bot-DM address restores automated sends.
- Removing the v1.5 overlay restores v1.4 behavior without rewriting customer
  events, registry entries, or historical receipts.

## Verification

- RED/GREEN tests for inbound identity, admin-loop prevention, route collision,
  disabled behavior, synthetic scheduled routing, and bot-DM fallback.
- RED/GREEN tests for legacy one-customer behavior, bounded capacity, two
  independently activated customers, per-customer receipt persistence,
  cross-customer route/storage isolation, concurrent activation serialization,
  restart recovery, capacity rollback, and explicit console selection.
- Existing Telegram topic, reply-mode, delivery, rich-message, nutrition, and
  operations-room tests remain green.
- Strict type, Ruff, compile, no-excuse, privacy-negative, and module-size gates.
- Real `python-telegram-bot` objects exercise a customer message, owner reply isolation,
  a scheduled send, a wrong-topic attack, and rollback to bot DM.
- Live channel setup, customer migration, and bot-right changes require a later
  permission-sealed activation and are out of scope here.
