ADR-004: Variety entities — embedded in commodity profile by default

Status: accepted (2026-05-15) Date decided: 2026-05-15

Context

Engineering Spec §3.1’s entity_type enum allows both commodity AND variety as valid Taproot entity types. The avocado.md exemplar uses typed wikilinks [[variety:hass|Hass]] implying varieties are addressable as their own entities. But the GDF v2.1.1 source profile data (in knowledge-forge/data/ exports/<commodity>_adapted.json) embeds variety information INSIDE the commodity profile’s s04_varieties.variety_summaries[] array.

Two structural patterns are possible:

PatternStorageWikilink resolutionMaintenance
A. Variety as own entity_profileSeparate variety_profile_<id> doc per variety[[variety:hass]] → standalone doc page339 commodities × ~5-50 varieties each = potentially 5,000-15,000 variety profile rows
B. Variety embedded in commodityVariety data in s04_varieties.variety_summaries[] of commodity profile[[variety:hass]] → anchor in commodity profile (commodity_profile_avocado#hass)One doc per commodity covers its varieties
C. Hybrid (selective promotion)Most varieties embedded; some promoted to own entity_profileEmbedded varieties resolve to anchors; promoted varieties resolve to standalone docsConfigurable per-variety basis

Decision

Default to embedded (Pattern B). Promote a variety to its own variety_profile_<id> entity_profile only when one of these conditions holds:

  1. Substantial unique data — the variety has its own regional cultivar variants, GAP certification specifics, or storage/handling that diverges meaningfully from the commodity-level baseline (e.g., “Hass Gem” or “Ataulfo Mango” might warrant their own profiles)

  2. Cross-commodity ambiguity — the variety name is shared across multiple commodities and disambiguation matters (rare but exists — “Lady” varieties span apples + grapes; “Honeycrisp” is unambiguous)

  3. Tenant-specific certification or contract scope — when a customer contract explicitly references a variety by name (e.g., “all Honeycrisp shipments must include Tier-1 certification”) and the agent needs a single citation target

Rendering implications:

  • The commodity_profile_renderer extracts linked_entities for each variety in s04_varieties.variety_summaries[] — that ARRAY is what makes the variety addressable for graph traversal even when no separate doc exists
  • [[variety:hass]] wikilinks resolve via the renderer/parser:
    • First try: lookup entity_doc_projection for entity_domain=variety, entity_key=hass — if a variety_profile_hass doc exists, link to it
    • Fallback: resolve to commodity_profile_<parent_commodity>#<variety_id> anchor (the renderer’s variety section gets per-variety anchors)

Consequences

Positive

  • Maintenance scale stays sane — 339 commodity profiles vs potentially 10,000+ if every variety got its own profile
  • Data lives where it’s authored — knowledge-forge already structures varieties inside commodity profile JSONB; embedded matches the source
  • Renderer simpler — one renderer per commodity covers all its varieties
  • Wikilinks still typed[[variety:hass]] is the canonical form whether it resolves to anchor or standalone doc; authors don’t have to know
  • Cross-commodity variety queries — agent can still answer “which commodities have a variety named X?” via the linked_entities[] index across all commodity_profile docs (no separate variety table needed)

Negative

  • Anchor links may break if a commodity profile is restructured — variety anchors live INSIDE the commodity profile doc, so renaming a section in the commodity renderer breaks any wikilink pointing at the old anchor. Mitigation: variety anchor IDs derived from slugifyForId(variety.name) — stable as long as variety names don’t change
  • No per-variety snapshot pattern — can’t snapshot just “Hass at 2026-W19” without snapshotting the entire avocado profile. Acceptable for v0.2; revisit if a use case emerges

Neutral

  • The entity_type enum keeps variety as a valid value — just used selectively

Implementation

Done 2026-05-15:

  • Schema (packages/shared/src/schemas/contracts/doc.ts) accepts both entity_type: commodity AND entity_type: variety
  • commodity_profile_renderer’s extractLinkedEntities() populates a linked_entities[] entry per variety from s04_varieties.variety_summaries
  • commodity_profile_renderer’s extractRelatedEntities() populates related_entities[] with relation: "variety_of" and weight: market_share_pct/100
  • Rendered profiles show varieties as a table inside the commodity profile with a per-variety implicit anchor (heading slug)

Pending:

  • Wikilink resolver in the doc parser needs the “first try entity_doc_projection, fallback to anchor” logic when T7 wires up the resolver
  • Promotion criteria operationalized — initial list of variety promotions (if any) lands as a follow-up ADR or a config knowledge row