# Apollo channel diagnostics and WhatsApp pairing

Implemented, not yet deployed. No schema migration required.

The Admin screen keeps desired configuration separate from observed status. Saving
does not start jobs or gateways. Test connection is licence-admin-only and resolves
the channel and credentials within the selected owner before making any request.

## Email and skills

Microsoft Graph testing obtains an application token and reads mailbox folder
metadata only. It never reads messages, attachments, conversations, Excel or
SharePoint. Missing configured folders are reported as unverified (the bounded
first page is not proof that a folder is absent). Inbox display-name localization
is not treated as a failure.

Graph watchers are cron jobs, not gateway platforms. Their last-run metadata is
shown separately when mapped by the operator. A last run older than 10 minutes is
stale, even if it previously succeeded. No runtime mapping means no watcher claim.
This does not verify the plugin/toolset bindings or document delivery end to end.

Supplier-specific PDF-download instructions belong in the existing Hermes intake
skill. The misleading Prepare link intake and download-host controls were removed
from this screen; legacy database values are preserved when editing. This change
does not activate a new downloader or claim that vendor-specific skill rules have
been located/validated. The current checked-in email intake worker handles
attachments; supplier-link execution still needs a separate integration acceptance
check against the existing Hermes skill, including redirects and destination safety.

## Operator-owned runtime binding

Add bindings under `ApolloChannels:Bindings` in the external web secrets/config,
not in licence-editable fields. Each binding must match all of OwnerId, ChannelId
and AccountIdentifier exactly. Duplicate matches fail closed. Example (placeholders):

```json
{
  "ApolloChannels": {
    "Bindings": [
      {
        "OwnerId": "<selected licence owner UUID>",
        "ChannelId": "<WhatsApp aichan UUID>",
        "AccountIdentifier": "<exact stored WhatsApp account>",
        "BridgePort": 3000,
        "BridgeToken": "<random 32-byte token, base64 encoded>"
      },
      {
        "OwnerId": "<same owner UUID>",
        "ChannelId": "<Graph aichan UUID>",
        "AccountIdentifier": "<exact stored mailbox>",
        "CronStorePath": "C:\\Users\\Administrator\\AppData\\Local\\hermes\\cron\\jobs.json",
        "JobNames": ["apollo-email-gateway", "apollo-email-intake"]
      }
    ]
  }
}
```

The web identity needs read access to that cron metadata file only, not the Hermes
session directory or broad access to the Hermes home. Different runtimes need
separate bindings/ports/tokens. Never bind another licence to this account's bridge.

## WhatsApp extension rollout (maintenance step)

The existing bridge has health but no browser QR endpoint. `local/hermes/whatsapp-admin`
adds an authenticated endpoint to that same process, using its existing Baileys
connection updates and bundled QR library. It neither starts another bridge nor
deletes credentials, forces logout, or initiates re-pairing on a connected session.

1. Run `node local/hermes/whatsapp-admin/install.mjs <absolute bridge.js path>`.
   Default is dry-run. The installed version on this machine passed compatibility
   validation; no live file was modified.
2. During an approved maintenance window, set `APOLLO_WHATSAPP_ADMIN_TOKEN` in the
   existing gateway service environment to the same random token as BridgeToken.
   Keep both locations restricted; never include it in logs or command-line arguments.
3. Run the installer with `--apply`. It backs up bridge.js and adds the module.
   Restart the existing gateway through its normal supervisor; the installer never
   restarts it. Preserve the existing session. Hermes upgrades may replace the
   bridge patch; repeat compatibility review after upgrades.
4. Deploy the web change and operator mapping. Test connected status first.
   When the native bridge emits a QR while disconnected, Test connection then
   Show QR / Linked devices displays it locally. Pending means the bridge has not
   emitted a fresh QR. A stopped/logged-out bridge requires operator recovery;
   the web application never clears a session directory automatically.
5. Pairing responses are authenticated and no-store; only rendered modules live
   in memory for at most 55 seconds. The UI clears them on close/edit/navigation/
   expiry. Never capture a real QR in test screenshots, logs or support tickets.

Rollback: restore the installer's exact bridge.js backup under normal maintenance,
remove the external binding/token, and restart the existing supervisor. Do not
delete WhatsApp session files. No database rollback is necessary.

## Verification

Node tests cover denied access, disabled weak tokens, no-store responses, QR expiry
and clearing on connected/closed. Platform tests cover bounded QR matrices and
real Preview-role cross-licence denial before provider contact; mocked Graph probes
assert only token and folder-metadata requests. Playwright uses synthetic accounts
and synthetic matrices, never production credentials or a live pairing session.

Focused verification on 2026-09-08: Platform 17/17, Browser 6/6 (Playwright enabled,
PT/EN at 1440/390 px), Node 3/3. Web build succeeded with zero warnings/errors.
Installed QR encoder produced a valid boolean matrix with synthetic input; bridge
installer dry-run passed. Impeccable inspection retained the pinned Admin pattern.
These checks are not a full regression suite or live pairing acceptance.
