ADR-006: Tenant Zero rendering — Central=universal Plane 3, Tenant=overlay+fusion

Status: accepted (2026-05-15) Date decided: 2026-05-15 Author: Rob Garcia, with Claude Code session 01

Context

The avocado.md hand-authored exemplar (TZ-2) demonstrated what a “rich commodity profile” looks like — fusing universal Plane 3 GDF data (scientific name, post-harvest specs, market share by variety) WITH tenant-side operational data (8,855 cases across 6 RP581 samples, $290K wholesale, brand-specific arrivals).

That fusion is the long-term vision for the doc projection layer (per project_living_wiki_drill_through_vision.md memory + Engineering Spec §3.4 mixed-section model + §12.5 embedded charts). But it raises a real demarcation question: which renderer produces the fused content, and which intranet does it land in?

The data sovereignty constraint matters:

  • Universal Plane 3 (golden profile, AGROVOC URI, scientific classification) — tenant-neutral; lives in FIN Central
  • Tenant operational (lots received, sales generated, vendor pricing observed, photos uploaded) — tenant-private; never leaves tenant_id='nathel' scope without anonymization gate
  • External market data (Blair pricing, USDA AMS) — universal but enters via tenant-side channels (intel@fintail.net inbox)

Three options considered:

Option A: Single Central renderer fuses everything

Central renderer reads tenant DB + external feeds + universal data, produces one doc per commodity per tenant.

Why rejected: Central reaching into tenant DB violates tenant data sovereignty. Also doesn’t scale — Central can’t be on the critical path for every tenant’s operational queries.

Option B: Tenant-only renderer, no Central layer

Each tenant intranet renders its own commodity profiles from scratch including the universal sections.

Why rejected: Duplicates the Plane 3 universal data across N tenant intranets. Updates to AGROVOC URIs / scientific classification have to propagate to N places. Also burdens tenants with knowledge they don’t own.

Option C (chosen): Two-layer renderer pattern

LayerRenderer locationReads fromWrites to
Central universalfin-central Workerknowledge.domain=commodity (Plane 3 only)fin-central-intranet/docs/entity_profiles/commodities/<id>.md
Tenant overlay+fusionnathel-tenant WorkerTenant events + signal.* + universal Taproot mirror + external sources via intel@nathel-intranet/docs/commodities/<id>_overlay.md

Drill-through experience: tenant intranet shows BOTH on the same page (via composition / iframe / render-time merge — UI-side decision).

Decision

Adopt Option C — two-layer rendering with explicit demarcation.

Central renderer (Plane 3 universal)

Implemented today (2026-05-15):

  • commodity_profile_renderer in packages/mcp-server/src/ingestion/renderers/commodity-profile-renderer.ts
  • Reads ONLY the GDF v2.1.1 golden profile JSONB from knowledge.domain=commodity (or local knowledge/commodities/<id>_adapted.json fixtures)
  • Renders 4 sections from Plane 3 data: Identity & Botany, Varieties, Post-Harvest Handling, Market Intelligence
  • Writes to fin-central-intranet/docs/entity_profiles/commodities/<id>.md
  • All sections kind: projected, confidence: external_authoritative or observed, data_plane: universal_intelligence, visibility: tenant_private
  • Snapshot pattern (§11.6) supported
  • 85 commodities seeded today

Tenant overlay renderer (per-tenant, lands in nathel-intranet)

Pending — design here, implementation in T7 follow-up.

Will implement:

  • commodity_overlay_renderer (new module, nathel-tenant worker)
  • Reads from:
    • events filtered by commodity_id (lot.received, sales.invoice_generated, qc.inspection_completed, signal.market_pricing_observed, signal.vendor_quote_observed, etc.)
    • Tenant-side knowledge.domain=customer/vendor aggregates
    • _taproot_mirror/ (read-only sync of Central universal profile)
    • External sources via signal.* events (intel@fintail.net feeds Blair pricing, vendor harvest comms, etc.)
  • Renders sections like:
    • Recent Arrivals — chart from lot.received events (last 30 days)
    • Pricing Trend — chart from signal.market_pricing_observed (Blair) + internal sales.invoice_generated
    • Vendor Availability — table from signal.vendor_quote_observed
    • Best Recent Lots Gallery — photos from artifact.uploaded events filtered to commodity (with customer-visibility rules from CLAUDE.md enforced)
    • Active Inspections — linked lots from qc.inspection_completed
    • Customer Demand Snapshot — from sales.order_received events
    • Operator Notes — authored
  • Writes to nathel-intranet/docs/commodities/<id>_overlay.md
  • All projected sections cite event_ids per §13.6 cite-then-claim contract
  • walk_provenance chains every claim back to raw events

Drill-through page composition

When a Nathel user navigates to /commodities/avocado:

  • Tenant intranet renderer (Quartz or equivalent) merges:
    • _taproot_mirror/commodities/avocado.md (from Central, read-only)
    • docs/commodities/avocado_overlay.md (tenant-specific)
  • Result: single living-intelligence page with universal facts at the top + operational fusion below
  • Each claim has its own provenance chain (Central facts → AGROVOC; tenant facts → events)

Consequences

Positive

  • Tenant data stays in tenant scope — Central renderer never reads tenant events; tenant overlay never serves universal Plane 3 to public agents
  • Central scales independently — 339 universal profiles rendered once, mirrored to N tenants via Taproot sync
  • Tenant data fusion is rich — sales + Blair pricing + photos + lots all flow into the overlay without polluting Central
  • Customer-visibility hard rule enforceable — overlay renderer applies CLAUDE.md customer-info boundary at render time (cost stripped, vendor names rolled up, etc.)
  • Anonymization gate for Wave 3+ Harvest Directory — when Central pulls cross-tenant aggregates back from tenant overlays, the anonymization policy (docs/policies/anonymization/FIN_ANONYMIZATION_POLICY_V1_2026-05-15.md) applies at the contributor adapter, not at the renderer
  • Clear T7 scope — two renderer types, two doc-CQRS rebuild queues (Central writes Plane 3 entity_doc_projection rows; Tenant writes overlay rows on its own DB)

Negative

  • Two renderers to maintain instead of one. Mitigation: shared section abstraction (e.g., renderTemperatureSection(spec) works for both Central universal and tenant overlay)
  • Drill-through UI must compose two docs — page renderer needs to know how to merge them. Quartz default merging works for a starter implementation; full compositional UI is a Phase 5+ feature
  • Renderer-version coordination — when Central bumps commodity_profile renderer to v1.1, tenant overlay renderers don’t auto-bump. Acceptable; each side versions independently

Neutral

  • The data_plane frontmatter field disambiguates which layer a doc came from (universal_intelligence vs relationship_data) — agents and the drill-through composer use this to decide layout

Implementation

Done 2026-05-15:

  • Central layer: commodity_profile_renderer shipped with 16/16 tests passing
  • 85 commodity profiles rendered via the bulk render script + seeded into fin-central-intranet
  • Schema (DocContent + EntityProfileContent) supports both layers
  • Renderer worker interface (entity-doc-renderer-worker.ts) declared

Pending (T7 follow-up + tenant work):

  • Tenant overlay renderer (commodity_overlay_renderer.ts)
  • Tenant overlay seeding script (mirror of bulk-render-commodity-profiles.ts but for tenant intranet)
  • Drill-through UI composition (Quartz + custom plugin OR a separate intranet renderer)
  • Vega-Lite chart embedding (§12.5)
  • Photo gallery embedding (extension to linked_artifact_ids[] or new embedded_photos[] schema field)
  • intel@fintail.net → signal.market_pricing_observed pipeline (extends EIF S5 — see project_email_intel_fusion_program.md)
  • Anonymization gate for cross-tenant aggregates (Wave 3 Harvest Directory)
  • Living-intelligence vision memory: project_living_wiki_drill_through_vision.md
  • Email Intel Fusion program: project_email_intel_fusion_program.md
  • Engineering spec basis: Intranet Projection Engineering Spec v0.2 §3.4 (sections), §12.5 (charts), §13.6 (cite-then-claim)
  • Customer-visibility boundary: fin-agentic/CLAUDE.md §“Customer Information Boundaries (HARD RULES)”
  • Anonymization policy: Anonymization Policy v1
  • Demarcation plan: Demarcation Plan
  • TZ-5 (deeper design — to be authored): docs/design/TZ_5_LIVING_INTELLIGENCE_DESIGN_2026-05-15.md