# Apollo licence-scoped reading — 2026-09-08

## Owner decision

Apollo may answer using the current conversation licence's database records and associated
WhatsApp/email/storage channels. Email bodies and WhatsApp conversations may be consulted only
when the user explicitly requests them. This is a read-only feature; it does not authorize
sending messages, changing files, starting jobs or accessing another licence.

## Implemented boundary

- `AiConversationService` continues to authorize the administrator and resolve the immutable
  conversation owner. Per-turn email/WhatsApp opt-in is separate from the model/project options,
  recorded in `ai.execution.start`, defaults to `None`, and resets after sending or changing session.
- `HermesHarness` selects only `apollo_read`; arbitrary project toolsets fail closed. It no longer
  passes PostgreSQL credentials to the agent. Existing libpq environment settings are cleared.
- A random 256-bit capability and loopback port belong to one execution. They are not prompt
  text or command-line arguments. Cancellation/completion disposes the listener and capability.
- The plugin accepts only a dataset and bounded offset. The host offers explicit projections
  for `organizations`, `companies`, `documents`, `channels`, `storage`, `stored_documents`.
  It accepts no SQL, owner ID, database name, file path, URL or credential selector.
- Each database read uses the configured non-bypass reader, a read-only transaction,
  transaction-local owner context, explicit owner predicates and a five-second statement limit.
  Results contain at most 50 rows plus pagination metadata. Offset is capped at 10,000.
  A scoped/page result is not evidence of global totals. These are an initial query catalogue,
  not unrestricted reporting across every Argus table.
- `stored_documents` joins each document to an enabled storage connection of the same owner.
  It returns indexed records, not arbitrary remote files or a live storage listing. No local or
  remote paths, connection settings, credential references or raw extraction/message JSON are exposed.
- `channels` lists enabled email/WhatsApp intake bindings for the owner. Sender identities and
  agent-supplied registration metadata are deliberately excluded.
- Quiet runs emit sanitized progress every five seconds. Missing plugin readiness fails within
  approximately 20 seconds instead of allowing a successful ungrounded answer. Disposing a run
  cancels/drains any pending process read before disposing the process enumerator.
- The selected session's parent project remains open and pinned against saved collapsed state.
  An older bookmarked session is included even outside the eight recent entries. Branch preference
  keys include the project and selected licence; navigation races cannot apply an older route result.

## Important incomplete integration — do not describe this as live message access

The existing `ChannelBinding` is a **document-intake sender association**, not a mailbox or chat
history authorization. There is no verified licence-scoped full-message connector in this slice.
Therefore `email_messages` / `whatsapp_messages` return `explicit_user_consent_required` without
the matching opt-in, and `scoped_connector_unavailable` with it. Consent is necessary, not sufficient.
There is no fallback to Hermes' global mailbox, sessions, terminal, cron or web tools.

Before enabling message content, implement a server-owned connector with a verified mapping of
licence → mailbox/chat → permitted messages, provider read-only permissions, bounded date/search
scope, revocation checks and cross-licence tests. Do not infer mailbox ownership from sender bindings.
Likewise, live storage file content/search needs a separate scoped reader; only its index is included here.

## Activation (not performed by the implementation turn)

This changes the required Hermes web configuration. Do not deploy the new host alone and expect
the previous broad tool configuration to keep working.

1. Install the tracked `local/hermes/apollo-read` plugin into the **web** Hermes home/profile,
   not the WhatsApp/email gateway. `local/hermes/install-read-plugin.ps1 -HermesHome <web-home> -WhatIf`
   previews the operation. The installer refuses existing destinations and does not edit config/secrets.
2. Merge `apollo-read` into `plugins.enabled` and `apollo_read` into `known_plugin_toolsets` for each
   enabled web profile. Keep unrelated settings. The web pool identity needs read access to these files.
3. Set `tools.tool_search.enabled: off` in those profiles to expose the single read tool directly.
   The installed Hermes runtime otherwise presents its native discovery facade; its underlying
   catalogue was checked to remain limited to `apollo_read_dataset`.
4. Remove old Apollo project toolset overrides or set them to exactly `apollo_read`.
5. Configure `AiHarness:Hermes:ReadToolsEnabled=true` on the new web host. Keep the existing
   SELECT-only/NOBYPASSRLS `ReaderConnectionString` in external secrets; no new database grants
   or migrations are required. `false` (default) refuses runs with a configuration error.
6. Run the guarded release workflow and an authorized live acceptance turn for each licence.
   Verify the reported organisation is the selected one, disallowed requests have no fallback,
   progress/cancellation work, and no cron/delegation task is created. No paid live inference was
   performed as part of the local implementation tests.

## Verification

- Preview PostgreSQL tests: selected organisation/company isolation (including A → B → A pooled
  reads), closed SQL/table/page inputs, rejection of privileged connections, valid projections.
- Capability tests: wrong execution token, per-turn consent separation, malformed requests,
  no internal error disclosure, listener revocation.
- Harness tests: fixed tools, no child database password, missing-plugin failure, heartbeat ordering,
  draining/cancelling a child when the consumer leaves on a heartbeat.
- Existing lifecycle test now verifies first-turn email consent is absent on the next resumed turn.
- Playwright: PT/EN at 1440/390, old selected session visible/open, navigation/reload preserves its
  branch despite a saved collapsed preference, consent defaults/reset, no page overflow.
- Python plugin unit tests and offline tool-catalogue check against the installed Hermes runtime.

Local verification: the complete `local/test.ps1` run passed **1,027 tests** (Platform 526,
Argus 236, Browser 151, TenantIsolation 58, API 56), with zero failures/skips. The Python plugin
unit suite passed 3 tests; the installed-runtime catalogue check also passed without inference.

The extraction bench and held-out quality rulings are unchanged. These tests are access-control,
lifecycle and UI evidence, not extraction-quality evidence or a completed live acceptance set.
