# CRM suite — the skill-of-skills composition manifest.
# It COMPOSES the seven base crm/* skills into ONE DataK3 bucket: shared master data,
# 36 disjoint tables, and a SINGLE deferred graph assembly. It is not a fork and it does
# not re-spec any workflow — the mechanics live in the seven base contracts; this manifest
# is the ordered install DAG + the master-data wiring + the one-graph rule + the union Q&A.
# The human walkthrough is posts/enterprise_software/crm/suite.md (published id crm-suite).
# Lives at skill/enterprise_software/crm/suite.skill.yaml; served over MCP at dodil://skills/suites/crm;
# executed by `dodil skill scaffold suites/crm`. check:skills binds it to its post.

id: suites/crm
version: 1.0.0
summary: >-
  Scaffold a complete, customizable CRM on ONE DataK3 bucket — master data (accounts/contacts/leads/
  opportunities/activities) + six workflows (lead-to-opportunity, qualification-scoring, pipeline-forecast,
  quote-cpq, campaign-to-lead, account-360) + one relationship graph, assembled once. 36 disjoint tables,
  shared masters joined across skills, no ETL and no second copy.
module: crm
workflows: [master-data, lead-to-opportunity, qualification-scoring, pipeline-forecast, quote-cpq, campaign-to-lead, account-360]
pillars: [sql, graph, vector, ignite, models]
industry: software                 # cross-industry base; overlays (crm/overlays/*) reshape per industry
overlays: [saas, manufacturing, finserv, real-estate]

# Binding to the narrative (check:skills verifies these resolve + agree)
post: posts/enterprise_software/crm/suite.md
route: /library/crm-suite

# It composes the seven base skills (no overlay by default). An overlay is an additive diff on top.
composes:
  skills: [crm/core, crm/lead-to-opportunity, crm/qualification-scoring, crm/pipeline-forecast,
           crm/quote-cpq, crm/campaign-to-lead, crm/account-360]
  overlays_apply_on_top: [crm/overlays/saas, crm/overlays/manufacturing, crm/overlays/finserv, crm/overlays/real-estate]

# BUILDING A SUBSET (not all seven). You do NOT have to install the whole suite to get one workflow.
subsets:
  rule: >-
    To build a SUBSET, install crm/core FIRST — it owns the five masters
    (accounts/contacts/leads/opportunities/activities) + the vectors + crm_node/crm_edge that every
    workflow reads — then install only the workflow skills you want. Each workflow skill's `consumes.master`
    lists its master dependencies (e.g. qualification-scoring needs leads/contacts/activities/opportunities;
    pipeline-forecast needs opportunities/accounts); crm/core provides them all.
  standalone_without_core: >-
    Each workflow skill ALSO ships a `when: standalone` stub_masters step that creates just the masters IT
    reads — so a single workflow runs on an empty bucket with no crm/core. But once you want TWO workflows
    sharing the same masters, install crm/core once instead of letting each stub its own (avoids divergent
    seed data).
  graph: >-
    Skip the graph entirely (no CREATE GRAPH) unless you install crm/account-360 — the single deferred
    CREATE GRAPH belongs to it (the last contributor). crm/core's standalone family-rollup graph is only
    built when you run crm/core alone with hierarchy=true.

# One question set for the whole suite. Only the shared/composition-answered ones collapse here;
# per-workflow policy knobs stay (they are genuine best-practice decisions, defaulted by the overlay).
params:
  bucket:        { type: string,  default: crm,   prompt: "Bucket name? (ONE bucket for all seven skills)" }
  seed_data:     { type: enum,    options: [demo, empty], default: demo }  # Acme family + Greyparrot demo, shared by every skill's Test
  overlay:       { type: enum,    options: [none, saas, manufacturing, finserv, real-estate], default: none }
  product_pitch: { type: string,  default: "an AI data platform for enterprise teams" }  # shared: lead-to-opportunity + qualification-scoring gates
  hierarchy:     { type: boolean, default: true }   # core: crm_node/crm_edge + subsidiary_of edges (feeds the single graph)
  framework:     { type: enum,    options: [bant, meddic], default: bant }   # qualification-scoring
  attribution_model: { type: enum, options: [first_touch, last_touch, linear, multi_touch], default: linear }  # campaign-to-lead

# ORDERED install DAG — masters first, the graph's last contributor last. This is the scaffold order.
order:
  - crm/core                  # 1. owns the 5 masters + 3 vectors + crm_node/crm_edge; the system of record
  - crm/quote-cpq             # 2. owns products (account-360 whitespace needs the catalog)
  - crm/lead-to-opportunity   # 3. discovery warehouse + sequence engine; converts orgs -> core leads/opportunities
  - crm/qualification-scoring # 4. scores core.leads (writes lead_scores, reads core masters)
  - crm/pipeline-forecast     # 5. stages/forecast over core.opportunities
  - crm/campaign-to-lead      # 6. campaigns/attribution over core.opportunities; may add campaign graph edges
  - crm/account-360           # 7. LAST graph contributor -> runs the single deferred CREATE GRAPH

# Shared master data — owner skill -> the sibling skills that JOIN it (no copy; one row, many readers).
# Derived from each base contract's provides.owns_master -> siblings' consumes.master.
master_wiring:
  accounts:      { owner: crm/core, consumers: [crm/lead-to-opportunity, crm/pipeline-forecast, crm/quote-cpq, crm/campaign-to-lead, crm/account-360] }
  contacts:      { owner: crm/core, consumers: [crm/lead-to-opportunity, crm/qualification-scoring, crm/quote-cpq, crm/campaign-to-lead, crm/account-360] }
  leads:         { owner: crm/core, consumers: [crm/lead-to-opportunity, crm/qualification-scoring, crm/campaign-to-lead] }
  opportunities: { owner: crm/core, consumers: [crm/lead-to-opportunity, crm/qualification-scoring, crm/pipeline-forecast, crm/quote-cpq, crm/campaign-to-lead, crm/account-360] }
  activities:    { owner: crm/core, consumers: [crm/lead-to-opportunity, crm/qualification-scoring, crm/account-360] }
  organizations: { owner: crm/lead-to-opportunity, consumers: [] }
  products:      { owner: crm/quote-cpq, consumers: [crm/account-360] }
  campaigns:     { owner: crm/campaign-to-lead, consumers: [] }

# THE single CREATE GRAPH — run exactly once, after the last contributor. This is the composition subtlety.
graph_assembly:
  graph: crm_graph
  rule: >-
    In suite mode every graph-touching skill ONLY inserts into crm_node/crm_edge — NO skill runs CREATE
    GRAPH. The snapshot rule (crm-on-dodil.md:170 / crm-core.md:215) means edges added after CREATE are
    invisible, so the suite defers the single CREATE GRAPH crm_graph NODES (crm_node KEY id) EDGES
    (crm_edge SRC src DST dst) to account-360, the last contributor. Node id ranges are pre-partitioned
    (accounts 1-9999, contacts 10000-99999, campaign nodes above) so contributors never collide on id.
  contributors:
    - { skill: crm/core,            edges: [subsidiary_of, works_at] }
    - { skill: crm/campaign-to-lead, edges: [member_of, influenced], when: { multi_touch_graph: true } }
    - { skill: crm/account-360,     edges: [partner_of, competes_with, supplies, owns_product], runs_create_graph: true }
  # LIVE-FOUND composition subtlety (2026-09-02): once account-360's partner_of edges join the single
  # graph, the standalone core family-rollup graph_khop('crm_graph',1,2,'in') OVER-COUNTS (it walks ALL
  # incoming edges regardless of rel — Greyparrot's partner_of edge leaks into the Acme family, $66k not
  # $36k). The suite family rollup MUST constrain the traversal to rel='subsidiary_of'. Type your edges.

# The ordered scaffold plan an agent executes (tools resolve against dodil://commands via check:skills).
steps:
  - id: bucket
    title: One bucket for the whole CRM
    when: always
    tools: [data_bucket_create]
    detail: Create the single DataK3 bucket {{bucket}}. Every one of the seven skills lands here — one data plane.
  - id: core
    title: Install crm/core (the system of record)
    when: always
    tools: [data_table_create, data_table_upsert, data_pg]
    detail: >-
      5 masters (accounts/contacts/leads/opportunities/activities) + 3 VECTOR(2048) tables + crm_node/crm_edge.
      In suite mode core populates node/edge but DEFERS CREATE GRAPH. Seed the Acme family demo if seed_data=demo.
  - id: quote-cpq
    title: Install crm/quote-cpq (products before whitespace)
    when: always
    tools: [data_table_create, data_table_upsert]
    detail: products/price_books/price_book_entries/quotes/quote_lines/discount_policy — the catalog account-360 reads.
  - id: lead-to-opportunity
    title: Install crm/lead-to-opportunity (discovery -> convert into core)
    when: always
    tools: [data_table_create, data_table_upsert, ignite_app_deploy]
    detail: >-
      organizations/org_embeddings + the flow/email tables + conversions. Converts write straight into core's
      leads/opportunities (shared masters) — no re-declaration; product_pitch is the shared gate input.
  - id: qualification-scoring
    title: Install crm/qualification-scoring (scores core.leads)
    when: always
    tools: [data_table_create, data_table_upsert, ignite_app_deploy]
    detail: lead_scores/scoring_policy; reads core.leads + core.opportunity_vectors. framework/threshold are the tunable knobs.
  - id: pipeline-forecast
    title: Install crm/pipeline-forecast (over core.opportunities)
    when: always
    tools: [data_table_create, data_table_upsert, data_sql]
    detail: pipeline_stages/forecast_snapshots/deal_risk; weighted Σ amount×probability over the shared opportunities master.
  - id: campaign-to-lead
    title: Install crm/campaign-to-lead (attribution over core.opportunities)
    when: always
    tools: [data_table_create, data_table_upsert, data_pg]
    detail: >-
      campaigns/campaign_members/touchpoints/attribution; attribution JOINs core.opportunities. If
      multi_touch_graph, projects campaign nodes/edges into crm_node/crm_edge (still deferred — no CREATE here).
  - id: account-360
    title: Install crm/account-360 + run the SINGLE graph assembly (LAST)
    when: always
    tools: [data_table_create, data_table_upsert, data_pg, data_bolt]
    detail: >-
      relationships/account_summary/whitespace. Adds partner_of/competes_with/supplies/owns_product edges,
      THEN the one CREATE GRAPH crm_graph over the fully-populated crm_node/crm_edge. Family rollup filters rel='subsidiary_of'.
  - id: overlay
    title: Apply an industry overlay (optional)
    when: { overlay: [saas, manufacturing, finserv, real-estate] }
    tools: [data_pg, data_table_create, data_table_upsert]
    detail: >-
      Apply crm/overlays/{{overlay}} — an ADDITIVE diff on top of the 36 tables (ALTER-add columns via the pg
      wire + maybe one extra table + gate/edge tweaks). Never rewrites a base skill. See the four overlay contracts.

# The union Q&A (the composition removes redundant asks — proposal §10.1).
customize:
  - param: bucket
    ask: "One bucket name for the whole CRM?"
    effects:
      any: "all seven skills scaffold into this single bucket — one data plane, cross-skill JOINs are free."
  - param: seed_data
    ask: "Load the shared Acme demo dataset, or ship empty schemas?"
    effects:
      demo:  "one demo (Acme family + Greyparrot + the planted near-dup) drives EVERY skill's Test — asked once, not seven times."
      empty: "empty schemas across all 36 tables; each skill's Test switches to structural assertions."
  - param: overlay
    ask: "Which industry? (drives all four overlay diffs at once — subscriptions/dealers/households/listings + gate defaults)"
    effects:
      none:          "the cross-industry base suite."
      saas:          "subscriptions + ARR/seat columns + renewal forecast lane + product-qualified gates."
      manufacturing: "dealers + install_base + territory-weighted graph + quote-to-order."
      finserv:       "households + referrals + KYC hard gate on the MEDDIC scorer."
      real-estate:   "buyer↔listing vector matching + commission splits."
  - param: product_pitch
    ask: "What are you selling, in one line? (shared by lead-to-opportunity + qualification-scoring — asked ONCE)"
    effects:
      any: "the same pitch feeds both the discovery qualify gate and the lead scorer — composition removes the duplicate ask."
  - param: [framework, attribution_model, hierarchy]
    ask: "The per-workflow policy knobs — qualify framework (BANT/MEDDIC), attribution model, family rollups?"
    effects:
      any: "genuine best-practice decisions the overlay may default but you tune; not removed by composition."

# Composition-answered asks REMOVED (documented so the union is auditable):
#  - lead-to-opportunity's "hand-feed leads or discover?" consumes leads/opportunities FROM crm/core — no re-ask.
#  - account-360's "seed a product catalog for whitespace?" — products come from crm/quote-cpq (step 2).
#  - qualification-scoring's opportunity_vectors — come from crm/core, not re-created.
#  - campaign-to-lead's "project campaign nodes into crm_graph?" — crm_node/crm_edge are owned by crm/core; the
#    suite's single deferred CREATE GRAPH covers it (no per-skill graph create in suite mode).

# What proves the ASSEMBLY (the suite's Test is about composition, not re-testing each skill).
tests:
  - "data table list -> 36 tables coexist, NO name collision (34 SQL/vector + crm_node + crm_edge)"
  - "cross-skill JOIN: lead_scores (qualification-scoring) JOIN leads (crm/core) -> Jane qualified 82, Sam disqualified 34"
  - "cross-skill JOIN: attribution (campaign-to-lead) JOIN opportunities (crm/core) -> Q3 Webinar attributed $24,000 to opp-labs, ROI 4x"
  - "single graph: one CREATE GRAPH crm_graph after account-360; partner_of edge (node 4->1) traversable via Bolt"
  - "family rollup filtered rel='subsidiary_of' -> $36,000 (Labs 24k + EU 12k); naive graph_khop('in') over-counts to $66k once partner_of joins"

tested_branches:
  - { seed_data: demo, overlay: none, tested_at: 2026-09-02 }   # default suite — full single-bucket assembly, live-validated
