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
| Layer | Renderer location | Reads from | Writes to |
|---|---|---|---|
| Central universal | fin-central Worker | knowledge.domain=commodity (Plane 3 only) | fin-central-intranet/docs/entity_profiles/commodities/<id>.md |
| Tenant overlay+fusion | nathel-tenant Worker | Tenant 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_rendererinpackages/mcp-server/src/ingestion/renderers/commodity-profile-renderer.ts- Reads ONLY the GDF v2.1.1 golden profile JSONB from
knowledge.domain=commodity(or localknowledge/commodities/<id>_adapted.jsonfixtures) - 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_authoritativeorobserved,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-tenantworker)- Reads from:
eventsfiltered bycommodity_id(lot.received, sales.invoice_generated, qc.inspection_completed, signal.market_pricing_observed, signal.vendor_quote_observed, etc.)- Tenant-side
knowledge.domain=customer/vendoraggregates _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.receivedevents (last 30 days) - Pricing Trend — chart from
signal.market_pricing_observed(Blair) + internalsales.invoice_generated - Vendor Availability — table from
signal.vendor_quote_observed - Best Recent Lots Gallery — photos from
artifact.uploadedevents 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_receivedevents - Operator Notes — authored
- Recent Arrivals — chart from
- Writes to
nathel-intranet/docs/commodities/<id>_overlay.md - All projected sections cite
event_idsper §13.6 cite-then-claim contract walk_provenancechains 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_profilerenderer to v1.1, tenant overlay renderers don’t auto-bump. Acceptable; each side versions independently
Neutral
- The
data_planefrontmatter field disambiguates which layer a doc came from (universal_intelligencevsrelationship_data) — agents and the drill-through composer use this to decide layout
Implementation
Done 2026-05-15:
- Central layer:
commodity_profile_renderershipped 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 newembedded_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)
Related
- 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