Mosaic one model, many lenses

ADR 0021 — Re-author mosaic-pages as tessera.yaml

The mosaic-pages app (pages.tessera) is written in the legacy .tessera text format, which the CLI no longer loads (ADR 0008: YAML is canonical; mosaic-cli rejects the legacy format). This ADR re-authors the app as tessera.yaml, using the features P5–P7 just landed so the port doubles as their first real-world consumer.

Context

  • pages.tessera (Sep 26) predates the YAML migration and the knowledge RAG work: its vectordb docs is inline-docs-only (three seeded texts), its chat is a workflow search node, and its deploy story doesn't exist (no deploys — the tessera has no deploy specs at all).
  • The README documents three known workarounds from that era: a disabled @env api-key binding (codegen bug, since fixed), a post-render patch adding missing web-crate deps (fixed), and the note that published docs don't reach the RAG index (still true — there is no upsert-to-vectordb DSL; documented, not solved).
  • P5 (ADR 0018) gave vector_dbs[] retrieval/chunk config + source-scoped search; P6 (ADR 0019) made re-indexing delta per source; P7 (ADR 0020) gave deploy specs host/ingress_class/proxy_buffering and the app content_root.

Decision

Rewrite mosaic-pages as tessera.yaml (dropping pages.tessera), keeping the same app shape — the Doc aggregate, the RBAC policy, the SSO identity, the ask-docs workflow, the operator guide — and adding what the legacy tessera lacked:

  1. Knowledge sources, the ee-pages way. vectordb docs gets real sources alongside the inline docs:
    • a books/ folder (kind auto — epub/docx get the chapter model, pdfs the page model),
    • a git source (K8: a committed git bundle, CI-hermetic),
    • a data-URI source (ADR 0015: memory://docs-remote, proving the OpenDAL seam — empty on a fresh boot, fail-soft),
    • retrieval: {mode: hybrid, top_k: 5} and chunk: {chars: 2000, overlap: 200} (P5 — the ee-pages defaults),
    • strategy: {books: true} explicit. Source-scoped search is e2e-proven: a search restricted to the git source's label finds that source's text and nothing else (and an unknown label returns empty).
  2. content_root: true (P7): the portal root belongs to the content — the web lens emits no root route; the UI keeps its other pages as a hidden entry point.
  3. A deploy spec (ADR 0010 + P7): deploys: [{name: prod, target: k8s, host: pages.example.com, ingress_class: nginx, proxy_buffering: off}] — the chart gains the ingress with the SSE annotation (the workflow run endpoint streams over ?stream=true).
  4. e2e: the legacy four scenarios (health/publish/list/get) plus token-scoped authz (viewer 403 / owner 200 on publish — the permission gate the README showcases) and the source-scoped search scenarios.

The api_key part-config env binding is restored (the codegen bug the README worked around is gone) and the README is rewritten for the YAML format (no more post-render patch).

Consequences

  • mosaic-pages becomes loadable and e2e-green again; out/ stays git-ignored and regenerable.
  • The knowledge corpus (books/, the git bundle) is committed to the app repo — the same posture as examples/full-stack/knowledge/.
  • The upsert-to-vectordb gap (published docs don't reach the RAG index at runtime) remains; it is a DSL feature request, tracked separately.
  • The legacy pages.tessera is deleted, not kept (the CLI cannot read it; keeping it would be dead weight that drifts).