M MemberIntel KB
Activity Decisions

decision

ADR-0033: External-only citations in the member UI

ADR-0033 (**Proposed** — supersedes ADR-0029 (Citation content access and entitlement)., 2026-07-07): External-only citations in the member UI.

Status: Proposed — supersedes ADR-0029 (Citation content access and entitlement).
Reverses the display half of #245/#252; the measurement half (ADR-0030/0032) stands.
Date: 2026-07-07
Deciders: Seth Shoultes (Lead Architect), Blair Williams (CEO)
Author: Seth Shoultes

Context

ADR-0029 (#245/#252) let the member UI surface cited brain sources: the Sources panel
rendered playbook/brain citations with a pop-out that fetched the full entry body via
GET /api/v1/brain/sources/{id}, and answers carried inline [n] citation pills.

The 2026-07-07 Blair × Seth Q2 review reversed the user-surface policy (caseproof/innovations#231,
#235): the brain is the secret sauce, and personalization is the product. Playbook/brain
material is delivered by weaving it into the answer, never by exposing the source. Surfacing
internal playbook titles/summaries/bodies invites distillation of the corpus and erodes the moat.
External, publicly available sources (e.g. hive_docs linking to memberpress.com) may still
be cited and linked.

The forces: (1) protect the corpus from distillation; (2) keep the analytics that ADR-0030
(entry-performance) and ADR-0032 (retrieval-event log) depend on — those unnest the FULL
messages.sources payload to count playbook citations; (3) don’t regress answer quality.

Decision

Filter to external-only at the client-serving boundary; keep the stored payload full.

  • New retrieval.citations.external_sources_only(sources) keeps only sources whose allowlisted
    metadata carries a non-empty string url. It runs at the two points that serve sources to
    the browser: the non-streaming chat response (api/chat/router.py) and history reload
    (api/conversations/router.py) — the latter is how the streaming client hydrates sources.
  • format_citations and the persisted messages.sources column are unchanged: the stored
    row still contains every cited entry (internal playbooks included), so the entry-performance
    rollup and retrieval-event log keep working. Measurement stays; display goes.
  • Member UI is prose-only. renderMarkdown strips inline [n] markers from displayed
    answers (stored content keeps them, so ADR-0032 cited-detection is unaffected). SourcesPanel
    lists only external (url-bearing) sources. The CitationModal component and the affordance
    are deleted.
  • The member endpoint GET /api/v1/brain/sources/{id} (added for the modal) is deleted — no
    member surface fetches internal brain bodies. Admin brain endpoints (CF-Access-gated) are
    untouched; operators keep full visibility.

Consequences

Positive:

  • The chat surface (answers, the Sources panel, history reload) no longer exposes internal brain
    content — closes the primary distillation surface and matches the #231 secret-sauce boundary.
    The serve-boundary filter also re-applies the #261 metadata allowlist, so even legacy
    pre-allowlist rows can’t leak internal keys (author/path/content_hash) on reload.
  • Measurement is fully preserved: messages.sources, retrieval_events, entry_performance
    and the citation-grounding eval are byte-for-byte unchanged, because the model still emits and
    we still store [n] markers + the full sources list.
  • Answer quality is unaffected — the model’s generation is identical; only presentation changes.

Known open surface (NOT closed by this ADR):

  • POST /api/v1/search is member-authenticated (get_current_user, not admin-gated) and returns
    raw brain content + numbered context + full metadata. The member app does not call it, but
    any authenticated member could — the same distillation surface the chat path just closed. Its
    fate (delete / admin-gate / external-only filter) is a product/scope decision tracked as a
    follow-up (caseproof/innovations#247). Until then, “the brain is
    invisible” holds for the chat UI but NOT for a member hitting /api/v1/search directly.

Negative / costs:

  • Users lose the ability to open the full text of a cited playbook. This is intentional.
  • Inline citation anchoring is gone for external sources too (prose-only). External links remain
    available in the Sources panel, just not number-anchored in the prose.
  • Filtering must stay at every client-serving read path; a new path that serves messages.sources
    must call external_sources_only. A unit + router test lock this.

Mitigations:

  • external_sources_only is a single shared helper with unit tests; the two call sites carry
    comments pointing here. The history-endpoint test asserts the stored row stays full while the
    served payload is external-only.
  • The done-when gate requires an eval/regression-replay confirming answer quality holds with
    citations hidden (caseproof/innovations#235) — tied to the eval-harness work (#230).

Alternatives considered

  • Filter inside format_citations (strip internal before storing) — rejected: breaks the
    entry-performance rollup and retrieval-event log, which unnest messages.sources to count
    playbook citations. ADR-0029’s non-goal was explicit: measurement must not change.
  • Frontend-only filter (SourcesPanel hides non-url sources) — rejected: internal source
    metadata (titles, summaries) would still travel to the browser in the API JSON, leaving the
    distillation surface open to anyone inspecting network traffic.
  • Renumber and keep inline anchors for external sources — rejected for V1: requires rewriting
    citation markers in the answer text (fragile) for marginal UX gain over a plain external-links
    list. Prose-only is the lowest-risk match to “the brain is invisible.”
  • Admin-gate rather than delete the sources endpoint — rejected: no member surface needs it,
    and the admin SPA already has its own CF-Access-gated brain endpoints. Deleting avoids a dormant
    member-reachable route (no-legacy).
For: S Seth Shoultes A AI Engineer B Blair Williams S Santiago Perez Asis P Product Lead