// ═══════════════════════════════════════════════════════════════════
// REPORT SCHEMA — RPT-v3 (platform canonical)
// The full nested structure of every published Intelligence Report —
// groups → sub-sections → cards — generalized across ASSET CLASSES.
//
// The design seam:
//   • UNIVERSAL_GROUPS — class-agnostic anatomy (thesis, diligence,
//     readiness, sources). One definition, shared by every class.
//   • ASSET_CLASSES[*].sections — the CLASS-SPECIFIC anatomy for the
//     three extension slots (subject RS-10 · instrument RS-11 ·
//     underwriting RS-12). Each class declares its OWN sub-sections and
//     cards, so every class instantiates a real, structurally-distinct
//     section — not a relabeled clone.
//
// A section is instantiated by:  (canonical slot) × (asset class)
//   → { label, ordered sub-sections, each a list of card archetypes,
//       every card bound to the workflow step that fills it from the KB }.
//
// groupsForClass(classId) composes the two into the ordered report:
//   G1 thesis · [subject · instrument · underwriting] · G5 diligence ·
//   G6 readiness · G7 sources.
// ═══════════════════════════════════════════════════════════════════
(function () {
  const CLASS_RS = ["RS-10", "RS-11", "RS-12"]; // the class-specific extension slots

  // Asset-class binding (window.AssetClass) is owned by the Ontology (Schema.jsx).

  // ── flat section registry (RS ids are stable across versions) ──────
  const SECTIONS = [
    { id: "RS-01", name: "Executive Brief",        step: "compose",    req: true, def: "Thesis, platform verdict and headline metrics — generated from the Knowledge Base, never free-written." },
    { id: "RS-02", name: "Source Registry",        step: "parse",      req: true, def: "Every artifact the knowledge derives from — type, author / department, version lineage, related documents, a source-reliability score, how many facts cite it, a page/section citation locator per fact, and an upload · parse timestamp recording when each artifact entered and was processed." },
    { id: "RS-03", name: "Knowledge Base",         step: "kb",         req: true, def: "Sourced datapoints classified V / P / A / H / Q / U with confidence — exactly one source per fact, each stamped with the extraction / search timestamp recording when it was captured. Renders in the sidebar Knowledge views." },
    { id: "RS-04", name: "Deal Architecture",      step: "kg",         req: true, def: "The concrete entity graph — instances and relationships, with at-risk edges marked. Drives the report's Structural Relations section and renders in full in the sidebar Knowledge views." },
    { id: "RS-05", name: "Verification Ledger",    step: "crosscheck", req: true, def: "Every material claim cross-checked against external sources, with finding and class." },
    { id: "RS-06", name: "Contradiction Register", step: "crosscheck", req: true, def: "Internal inconsistencies — source A vs source B, severity, and the resolution path." },
    { id: "RS-07", name: "Evidence Gaps",          step: "gaps",       req: true, def: "What must still be evidenced — each gap names the document that closes it." },
    { id: "RS-08", name: "Strategic Actions",      step: "compose",    req: true, def: "Ranked recommendations, each gated on the evidence above — priority and impact." },
    { id: "RS-09", name: "Readiness Verdict",      step: "review",     req: true, def: "The gate decision — proceed / conditional / blocked — and exactly what clears it." },
    { id: "RS-10", name: "Subject", step: "compose",  req: false, cls: true, def: "Class-specific subject extension — what is being built / sold and the demand evidence, every claim classified. Each asset class declares its own sub-sections (see Asset Classes)." },
    { id: "RS-11", name: "Instrument", step: "compose",   req: false, cls: true, def: "Class-specific instrument extension — how the capital is structured, the wrapper and the rails; absences are gaps. Each asset class declares its own sub-sections." },
    { id: "RS-12", name: "Underwriting Model", step: "market", req: false, cls: true, def: "Class-specific underwriting extension — the sponsor's model restated with its levers and scenario band, classed assumption. Each asset class declares its own sub-sections." },
    { id: "RS-13", name: "Opportunity Register",   step: "market",  req: false, def: "Forward opportunities — growth · partnership · pricing · product · market — each with evidence, confidence and a strategic-impact score. The symmetric upside to the risk register." },
    { id: "RS-14", name: "Change Digest",          step: "compose", req: false, def: "What changed since the last published version — new and updated items flagged NEW by document / report timestamp, so a returning reader sees only the delta." },
  ];
  const SECTION_BY_ID = {}; SECTIONS.forEach((s) => (SECTION_BY_ID[s.id] = s));

  // ── REPORTS — the four delivered reports. Each is self-contained and
  // runs on its own cadence (weekly · monthly · quarterly); together they
  // are a reading layer over the groups, and the publish gate still
  // validates every underlying card.
  //   Executive Brief · Product Passport · Intelligence Report · Strategic Review
  const REPORTS = [
    { id: "brief",     name: "Executive Brief",     cadence: "weekly",    slots: ["thesis"],                                  classSpecific: false, sub: "verdict · thesis · findings · risks — opening with the change digest since the last issue" },
    { id: "passport",  name: "Product Passport",    cadence: "monthly",   slots: ["subject", "instrument", "underwriting"],   classSpecific: true,  sub: "the subject · the instrument · the model — the class-specific passport that consolidates the pricing, revenue, market & technology registries" },
    { id: "intel",     name: "Intelligence Report", cadence: "monthly",   slots: ["diligence", "structure", "sources"],         classSpecific: false, sub: "verification · contradictions · evidence gaps · structural relations · sourcing with document metadata & reliability" },
    { id: "strategic", name: "Strategic Review",    cadence: "quarterly", slots: ["readiness", "opportunities"],              classSpecific: false, sub: "go / no-go gates · ranked actions · the opportunity register" },
  ];
  const PARTS = REPORTS; // back-compat alias

  // ── card archetype vocabulary (the generalization layer) ───────────
  const CARD_TYPES = {
    banner:    "status framing strip — what exists vs what is claimed",
    hypothesis:"hypothesis-framing strip — model targets vs actuals, the classed assumption",
    kpis:      "metric cell strip — headline figures with sub-captions",
    pillars:   "labeled claim rows — the thesis anatomy (label · claim)",
    ledger:    "grid table — rows carrying class / severity pills, citations",
    register:  "rated cards — risks or gaps (blocks · today · closes · due)",
    paths:     "dark-header cards — offering paths or gate paths with points",
    lifecycle: "numbered gated steps — each step names its blocker",
    funnel:    "proportional bars — pool → segment narrowing, classed per stage",
    meter:     "confidence / progress visual — score bar or gate dots",
    xref:      "cross-reference pointer — where adjacent detail lives",
  };

  // ── UNIVERSAL groups — class-agnostic anatomy ──────────────────────
  // each card: [archetype, definition, step-that-fills-it]
  const UNIVERSAL_GROUPS = {
    G1: { id: "G1", rs: ["RS-01"], req: true, label: "Thesis & Verdict", subs: [
      { id: "G1.a", rsRef: "RS-01", label: "Executive Brief", cards: [
        ["kpis",     "Headline metrics — the six figures the whole report reads against (round · valuation · committed · revenue · status · runway)", "compose"],
        ["pillars",  "Investment thesis — headline · claim pillars · italic counterweight", "compose"],
        ["meter",    "Platform verdict — headline · body · confidence /100 · run stat pills", "compose"],
        ["ledger",   "Top findings, classified — class badge per finding + fact-class ledger strip", "crosscheck"],
        ["ledger",   "Strengths / asks before close — paired bullet lists", "compose"],
        ["register", "Risk register — id · title · category · severity · mitigation", "gaps"],
      ] },
    ] },
    G5: { id: "G5", rs: ["RS-05", "RS-06", "RS-07"], req: true, label: "Due Diligence", subs: [
      { id: "G5.a", rsRef: "RS-05", label: "Subject background & checks", cards: [
        ["banner", "What is real vs unverified — the framing finding", "background"],
        ["ledger", "Existential / background checks — class per check, gap refs", "background"],
      ] },
      { id: "G5.b", rsRef: "RS-05", label: "Verification", cards: [
        ["kpis",   "Class-count strip over the checked claims", "crosscheck"],
        ["ledger", "Verification ledger — claim · finding · class · citations (RS-05)", "crosscheck"],
      ] },
      { id: "G5.c", rsRef: "RS-06", label: "Contradictions", cards: [
        ["ledger", "Contradiction register — A vs B · severity · resolution (RS-06)", "crosscheck"],
      ] },
      { id: "G5.d", rsRef: "RS-07", label: "Evidence Gaps", cards: [
        ["register", "Open evidence gaps — blocks · today · closes · due (RS-07)", "gaps"],
      ] },
    ] },
    G6: { id: "G6", rs: ["RS-09", "RS-08"], req: true, label: "Readiness Assessment", subs: [
      { id: "G6.a", rsRef: "RS-09", label: "Gates & Filings", cards: [
        ["paths",  "Gate paths — progress dots · per-gate evidence refs · verdict pill (RS-09)", "review"],
        ["ledger", "Filing & rails pipeline — what must exist and file, in dependency order (generic to any exempt offering)", "compose"],
      ] },
      { id: "G6.b", rsRef: "RS-08", label: "Strategic Actions", cards: [
        ["ledger", "Strategic actions — ranked · rationale · priority · impact (RS-08)", "compose"],
      ] },
    ] },
    G7: { id: "G7", rs: ["RS-02"], req: true, label: "Sources", subs: [
      { id: "G7.a", rsRef: "RS-02", label: "Citation Registry", cards: [
        ["ledger", "Citation registry — id · type · facts cited · reliability score · version · page/section locator · upload & parse timestamp · citation URL; the one place sources are tabled — sourced facts themselves live in the Knowledge Base", "parse"],
        ["facts",  "Document metadata — author · department · version lineage · related documents · source-reliability tier · uploaded / parsed timestamps", "parse"],
      ] },
    ] },
    G8: { id: "G8", rs: ["RS-13"], req: false, label: "Strategic Opportunities", subs: [
      { id: "G8.a", rsRef: "RS-13", label: "Opportunity Register", cards: [
        ["register", "Opportunity register — id · title · category · confidence · strategic-impact · supporting evidence", "market"],
        ["ledger",   "Opportunity-to-action linkage — each upside tied to the action that captures it", "compose"],
      ] },
    ] },
    G9: { id: "G9", rs: ["RS-04"], req: false, label: "Structural Relations", subs: [
      { id: "G9.a", rsRef: "RS-04", label: "Deal Architecture", cards: [
        ["ledger", "Load-bearing & at-risk relationships — the edges the deal hangs on, flagged from the typed entity graph", "kg"],
        ["ledger", "Dependency chains — how control and value flow through the entity graph", "kg"],
        ["meter",  "Structural insight — the synthesized takeaway from the relationship topology", "compose"],
      ] },
    ] },
  };

  // ── ASSET-CLASS registry — the class-specific anatomy per class ────
  // sections.{subject|instrument|underwriting} = { gid, rs, label, subs }
  // each sub: { id, label, cards:[[type,def,step]] }. Distinct per class.
  const CLASS_DEFS = [
    { id: "realEstate",
      sections: {
        subject: { gid: "G2", rs: "RS-10", label: "Asset", subs: [
          { id: "G2.a", label: "Site & Program", cards: [
            ["banner",  "Pre-development framing — nothing built; every claim classified", "kb"],
            ["pillars", "The program claim — site · program · signature, what is asserted to exist", "extract"],
            ["ledger",  "Build scope funded by the raise — categories with shares", "extract"],
            ["xref",    "Pointer to the cost build-up (Underwriting · Cost & Phasing)", "compose"],
          ] },
          { id: "G2.b", label: "Brand & Demand", cards: [
            ["banner",  "Demand verified vs brand unverified — the framing finding", "crosscheck"],
            ["pillars", "The revenue premise — brand × demand", "market"],
            ["ledger",  "Proof milestones — dated events that make the premise real", "extract"],
          ] },
        ] },
        instrument: { gid: "G3", rs: "RS-11", label: "Deal", subs: [
          { id: "G3.a", label: "Capital Stack & Terms", cards: [
            ["banner", "The raise as proposed — platform position on scope and staging", "compose"],
            ["kpis",   "Headline terms — raise · tranche · debt · cost-vs-raise (6 cells)", "extract"],
            ["ledger", "Capital stack — holders, shares and amounts", "extract"],
            ["ledger", "Sources-and-uses reconciliation — absences flagged Q", "crosscheck"],
          ] },
          { id: "G3.b", label: "Structure & Rails", cards: [
            ["banner",    "One instrument · two exemptions — the wrapper in one line", "kb"],
            ["paths",     "Exemption cards — Reg D 506(c) · Reg S — audience · filing · blue sky", "background"],
            ["ledger",    "The instrument — form · lockups · transfer · secondary · counsel", "extract"],
            ["banner",    "Liquidity discipline callout (warn) — no venue, no claim", "compose"],
            ["lifecycle", "Issuance lifecycle — SPV → verify → issue → secondary, each gated", "compose"],
          ] },
        ] },
        underwriting: { gid: "G4", rs: "RS-12", label: "Underwriting", subs: [
          { id: "G4.a", label: "Cost & Phasing", cards: [
            ["ledger", "Cost build-up — $747.5M development estimate by category", "extract"],
            ["funnel", "Use of proceeds — category bars to the development estimate", "extract"],
            ["ledger", "Method reconciliation & spend phasing — calendar / bridge", "crosscheck"],
          ] },
          { id: "G4.b", label: "Revenue Drivers", cards: [
            ["hypothesis", "Hypothesis framing — funnel × per-cap is the revenue line", "kb"],
            ["funnel", "Demand funnel — metro → corridor → park, classed per stage", "market"],
            ["ledger", "Per-caps vs cited benchmark — premium flagged", "crosscheck"],
            ["ledger", "Stabilized targets — attendance · revenue · EBITDA at maturity", "extract"],
          ] },
          { id: "G4.c", label: "Returns & Scenarios", cards: [
            ["hypothesis", "Hypothesis framing — sponsor projections, classed assumption", "kb"],
            ["kpis",   "Headline returns — unlevered / levered IRR · NPV · DSCR · terminal EV", "extract"],
            ["ledger", "Ramp to stabilization — verified baseline to Yr3", "extract"],
            ["ledger", "Scenario band — downside · base · upside; valuation spread", "extract"],
          ] },
        ] },
      } },

    { id: "equity",
      sections: {
        subject: { gid: "G2", rs: "RS-10", label: "Business", subs: [
          { id: "G2.a", label: "Product", cards: [
            ["banner",  "Status framing — nothing shipped; every claim classified", "kb"],
            ["pillars", "The product claim — wedge · roadmap, what is asserted to exist", "extract"],
            ["ledger",  "Build plan funded by the round — milestones with shares", "extract"],
            ["xref",    "Pointer to the model (Financial Model · Financials)", "compose"],
          ] },
          { id: "G2.b", label: "Go-to-Market", cards: [
            ["banner",  "Demand evidence vs conversion proof — what is cited vs asserted", "crosscheck"],
            ["pillars", "The channel thesis — how value reaches buyers", "market"],
            ["ledger",  "Proof milestones — dated traction & retention events", "extract"],
          ] },
        ] },
        instrument: { gid: "G3", rs: "RS-11", label: "Offering", subs: [
          { id: "G3.a", label: "Offering Terms", cards: [
            ["banner", "The raise as proposed — instrument and staging", "compose"],
            ["kpis",   "Headline terms — round · cap · instrument · dilution · min check", "extract"],
            ["xref",   "Ownership math — cap table & dilution stack — lives in Financial Model · Cap Table & Proceeds (G4.a)", "compose"],
          ] },
          { id: "G3.b", label: "Legal Framework", cards: [
            ["banner",    "One instrument · the exemption in one line", "kb"],
            ["paths",     "Exemption cards — audience · solicitation · verification · filing", "background"],
            ["ledger",    "The instrument — SAFE / priced · lockups · transfer · counsel", "extract"],
            ["banner",    "Discipline callout (warn) — portal & verification provider", "compose"],
            ["lifecycle", "Close mechanics — incorporate → verify → subscribe → close, each gated", "compose"],
          ] },
        ] },
        underwriting: { gid: "G4", rs: "RS-12", label: "Financial Model", subs: [
          { id: "G4.a", label: "Cap Table & Proceeds", cards: [
            ["ledger", "Cap table — pre / post-round ownership", "extract"],
            ["funnel", "Use of proceeds — category bars summing to the round", "extract"],
            ["ledger", "Dilution stack — SAFE + pools modeled together", "crosscheck"],
          ] },
          { id: "G4.b", label: "Financials", cards: [
            ["hypothesis", "Hypothesis framing — model targets; no operating actuals", "kb"],
            ["kpis",   "Headline projections — ARR · gross margin · burn · runway", "extract"],
            ["ledger", "Revenue quality — concentration, retention, mix", "extract"],
            ["ledger", "Projection scenarios — downside · base · upside per driver", "extract"],
          ] },
          { id: "G4.c", label: "Unit Economics", cards: [
            ["hypothesis", "Hypothesis framing — unit inputs are targets, not data", "kb"],
            ["ledger", "Unit inputs vs cited benchmark — CAC · payback · LTV flagged", "crosscheck"],
            ["ledger", "Stabilized unit targets — per-customer economics at maturity", "extract"],
          ] },
          { id: "G4.d", label: "Market", cards: [
            ["hypothesis", "Hypothesis framing — top-of-funnel filing volume is public; the pool/TAM/SAM sizing is assumption", "kb"],
            ["funnel", "Demand funnel — TAM → SAM → SOM, classed per stage", "market"],
            ["ledger", "Verified anchors — public market figures, cross-checked", "crosscheck"],
          ] },
        ] },
      } },

    { id: "credit",
      sections: {
        subject: { gid: "G2", rs: "RS-10", label: "Borrower", subs: [
          { id: "G2.a", label: "Borrower & Collateral", cards: [
            ["banner",  "Borrower quality framing — sponsor and track record", "background"],
            ["pillars", "The credit thesis — why this borrower repays", "extract"],
            ["ledger",  "Collateral & security package — assets, liens, priority", "extract"],
            ["xref",    "Pointer to coverage analysis (Credit Analysis)", "compose"],
          ] },
          { id: "G2.b", label: "Use & Repayment", cards: [
            ["banner",  "Use of proceeds vs sources of repayment — the framing finding", "crosscheck"],
            ["ledger",  "Sources of repayment — primary, secondary, collateral fallback", "extract"],
          ] },
        ] },
        instrument: { gid: "G3", rs: "RS-11", label: "Facility", subs: [
          { id: "G3.a", label: "Facility Terms", cards: [
            ["banner", "The facility as proposed — structure and seniority", "compose"],
            ["kpis",   "Headline terms — size · coupon · tenor · LTV · seniority", "extract"],
            ["ledger", "Tranche & seniority structure — who sits where", "extract"],
            ["ledger", "Pricing & fees — spread, OID, call protection", "extract"],
          ] },
          { id: "G3.b", label: "Security & Covenants", cards: [
            ["ledger",    "Security & guarantees — collateral perfection, guarantors", "background"],
            ["ledger",    "Covenant package — maintenance & incurrence, headroom", "extract"],
            ["lifecycle", "Drawdown & amortization — funding → amort → maturity, each gated", "compose"],
            ["banner",    "Default & remedies discipline (warn) — triggers and step-in", "compose"],
          ] },
        ] },
        underwriting: { gid: "G4", rs: "RS-12", label: "Credit Analysis", subs: [
          { id: "G4.a", label: "Coverage & Leverage", cards: [
            ["hypothesis", "Hypothesis framing — model coverage, classed assumption", "kb"],
            ["kpis",   "Headline credit — DSCR · ICR · LTV · net leverage", "extract"],
            ["ledger", "Cash flow to debt service — the coverage build", "extract"],
          ] },
          { id: "G4.b", label: "Stress & Recovery", cards: [
            ["banner", "Stress framing — downside before recovery", "kb"],
            ["ledger", "Downside / stress band — coverage under shock", "extract"],
            ["ledger", "Recovery & loss-given-default — collateral haircut analysis", "crosscheck"],
            ["ledger", "Verified benchmark spreads & comparable credits", "crosscheck"],
          ] },
        ] },
      } },

    { id: "fund",
      sections: {
        subject: { gid: "G2", rs: "RS-10", label: "Strategy", subs: [
          { id: "G2.a", label: "Strategy & Edge", cards: [
            ["banner",  "Strategy framing — what edge is claimed", "background"],
            ["pillars", "The strategy thesis — where returns come from", "extract"],
            ["ledger",  "Sourcing & access — proprietary pipeline evidence", "extract"],
            ["xref",    "Pointer to the track record (Track Record)", "compose"],
          ] },
          { id: "G2.b", label: "Portfolio Construction", cards: [
            ["banner",  "Construction discipline vs drift — the framing finding", "crosscheck"],
            ["ledger",  "Construction rules — sizing, concentration, reserves", "extract"],
            ["ledger",  "Pipeline & deployment pace — coverage of the fund size", "extract"],
          ] },
        ] },
        instrument: { gid: "G3", rs: "RS-11", label: "Terms", subs: [
          { id: "G3.a", label: "Economics", cards: [
            ["banner", "The terms as proposed — economics and alignment", "compose"],
            ["kpis",   "Headline terms — size · GP commit · fee · carry · hurdle", "extract"],
            ["ledger", "Fee & carry detail — management fee basis, offsets", "extract"],
            ["ledger", "Distribution waterfall — European vs deal-by-deal", "extract"],
          ] },
          { id: "G3.b", label: "Structure & Governance", cards: [
            ["ledger",    "Fund structure & domicile — vehicles, parallel funds", "background"],
            ["ledger",    "LPA key terms — key-person, no-fault, GP removal", "extract"],
            ["lifecycle", "Fund life — drawdown → investment → harvest, each gated", "compose"],
            ["banner",    "Alignment discipline (warn) — GP commit & clawback", "compose"],
          ] },
        ] },
        underwriting: { gid: "G4", rs: "RS-12", label: "Track Record", subs: [
          { id: "G4.a", label: "Realized Performance", cards: [
            ["banner", "Attribution framing — realized before marks", "kb"],
            ["kpis",   "Headline performance — DPI · TVPI · net IRR · loss ratio", "extract"],
            ["ledger", "Realized deals — attribution by driver", "extract"],
          ] },
          { id: "G4.b", label: "Unrealized & Benchmarks", cards: [
            ["banner", "Mark framing — unrealized valuation policy", "kb"],
            ["ledger", "Unrealized marks & policy — basis and conservatism", "extract"],
            ["ledger", "Benchmark vs vintage — PME and quartile", "crosscheck"],
            ["ledger", "Verified public benchmark & PME inputs", "crosscheck"],
          ] },
        ] },
      } },

    { id: "commodity",
      sections: {
        subject: { gid: "G2", rs: "RS-10", label: "Asset", subs: [
          { id: "G2.a", label: "Resource & Capacity", cards: [
            ["banner",  "What is in the ground / built vs claimed reserves — every figure classified", "kb"],
            ["pillars", "The resource thesis — reserve × offtake, what is asserted to exist", "extract"],
            ["ledger",  "Reserve / capacity statement — proven · probable · nameplate", "extract"],
            ["xref",    "Pointer to the throughput model (Underwriting · Cost & Capacity)", "compose"],
          ] },
          { id: "G2.b", label: "Operations & Offtake", cards: [
            ["banner",  "Permitted & operating vs planned — the framing finding", "background"],
            ["pillars", "The cash-flow premise — volume × price under contract", "market"],
            ["ledger",  "Offtake & supply agreements — counterparties, tenor, volume", "extract"],
          ] },
        ] },
        instrument: { gid: "G3", rs: "RS-11", label: "Structure", subs: [
          { id: "G3.a", label: "Capital Stack & Terms", cards: [
            ["banner", "The raise as proposed — platform position on scope and staging", "compose"],
            ["kpis",   "Headline terms — raise · capacity · debt · cost-vs-raise (6 cells)", "extract"],
            ["ledger", "Capital stack — holders, shares and amounts", "extract"],
            ["ledger", "Sources-and-uses reconciliation — absences flagged Q", "crosscheck"],
          ] },
          { id: "G3.b", label: "Rights & Rails", cards: [
            ["banner",    "Royalty · revenue-share · equity — the wrapper in one line", "kb"],
            ["paths",     "Exemption cards — audience · filing · blue sky", "background"],
            ["ledger",    "The instrument — royalty / note / unit · lockups · transfer · counsel", "extract"],
            ["banner",    "Liquidity discipline callout (warn) — no venue, no claim", "compose"],
            ["lifecycle", "Issuance lifecycle — SPV → verify → issue → secondary, each gated", "compose"],
          ] },
        ] },
        underwriting: { gid: "G4", rs: "RS-12", label: "Throughput Model", subs: [
          { id: "G4.a", label: "Cost & Capacity", cards: [
            ["ledger", "Capex build-up — development / extraction estimate by category", "extract"],
            ["funnel", "Use of proceeds — category bars to the capex estimate", "extract"],
            ["ledger", "Ramp to nameplate capacity — phasing / bridge", "crosscheck"],
          ] },
          { id: "G4.b", label: "Price & Volume", cards: [
            ["hypothesis", "Hypothesis framing — price deck × volume is the revenue line", "kb"],
            ["ledger", "Price deck vs cited benchmark — premium flagged", "crosscheck"],
            ["ledger", "Stabilized output targets — volume · revenue · EBITDA at maturity", "extract"],
          ] },
          { id: "G4.c", label: "Returns & Scenarios", cards: [
            ["hypothesis", "Hypothesis framing — sponsor projections, classed assumption", "kb"],
            ["kpis",   "Headline returns — unlevered / levered IRR · NPV · DSCR · payback", "extract"],
            ["ledger", "Scenario band — commodity-price sensitivity; valuation spread", "extract"],
          ] },
        ] },
      } },

    { id: "infrastructure",
      sections: {
        subject: { gid: "G2", rs: "RS-10", label: "Asset", subs: [
          { id: "G2.a", label: "Facility & Capacity", cards: [
            ["banner",  "Operating vs pre-COD — every output figure classified", "kb"],
            ["pillars", "The throughput thesis — capacity × availability, what is asserted", "extract"],
            ["ledger",  "Nameplate capacity & availability statement — rated vs realized", "extract"],
            ["xref",    "Pointer to the throughput model (Underwriting · Output & Tariff)", "compose"],
          ] },
          { id: "G2.b", label: "Contracts & Regulation", cards: [
            ["banner",  "Contracted vs merchant exposure — the framing finding", "background"],
            ["ledger",  "Offtake / PPA & concession — counterparty, tenor, tariff", "extract"],
          ] },
        ] },
        instrument: { gid: "G3", rs: "RS-11", label: "Structure", subs: [
          { id: "G3.a", label: "Capital Stack & Terms", cards: [
            ["banner", "The raise as proposed — platform position on scope and staging", "compose"],
            ["kpis",   "Headline terms — raise · capacity · debt · cost-vs-raise (6 cells)", "extract"],
            ["ledger", "Capital stack — holders, shares and amounts", "extract"],
            ["ledger", "Sources-and-uses reconciliation — absences flagged Q", "crosscheck"],
          ] },
          { id: "G3.b", label: "Rights & Rails", cards: [
            ["banner",    "Availability payment · revenue-share · unit — the wrapper in one line", "kb"],
            ["paths",     "Exemption cards — audience · filing · blue sky", "background"],
            ["ledger",    "The instrument — unit / note / revenue-share · lockups · transfer · counsel", "extract"],
            ["banner",    "Liquidity discipline callout (warn) — long-dated, illiquid by default", "compose"],
            ["lifecycle", "Issuance lifecycle — SPV → verify → issue → secondary, each gated", "compose"],
          ] },
        ] },
        underwriting: { gid: "G4", rs: "RS-12", label: "Throughput Model", subs: [
          { id: "G4.a", label: "Cost & Commissioning", cards: [
            ["ledger", "Capex build-up — construction / commissioning estimate by category", "extract"],
            ["funnel", "Use of proceeds — category bars to the capex estimate", "extract"],
            ["ledger", "Ramp to commercial operation (COD) — phasing / bridge", "crosscheck"],
          ] },
          { id: "G4.b", label: "Output & Tariff", cards: [
            ["hypothesis", "Hypothesis framing — availability × tariff is the revenue line", "kb"],
            ["ledger", "Tariff / PPA pricing vs cited benchmark — premium flagged", "crosscheck"],
            ["ledger", "Stabilized output targets — generation · revenue · EBITDA at maturity", "extract"],
          ] },
          { id: "G4.c", label: "Returns & Scenarios", cards: [
            ["hypothesis", "Hypothesis framing — sponsor projections, classed assumption", "kb"],
            ["kpis",   "Headline returns — unlevered / levered IRR · NPV · DSCR · payback", "extract"],
            ["ledger", "Scenario band — availability / tariff sensitivity; valuation spread", "extract"],
          ] },
        ] },
      } },

    { id: "collectible",
      sections: {
        subject: { gid: "G2", rs: "RS-10", label: "Work", subs: [
          { id: "G2.a", label: "Object & Provenance", cards: [
            ["banner",  "Authenticated vs attributed — every claim classified", "background"],
            ["pillars", "The value thesis — rarity × provenance, what is asserted", "extract"],
            ["ledger",  "Provenance chain — ownership history, exhibition & sale record", "extract"],
            ["xref",    "Pointer to comparables (Underwriting · Appraisal & Comps)", "compose"],
          ] },
          { id: "G2.b", label: "Condition & Custody", cards: [
            ["banner",  "Condition verified vs declared — the framing finding", "crosscheck"],
            ["ledger",  "Condition & conservation report — grade, restoration history", "extract"],
            ["ledger",  "Custody & insurance — storage, appraisal cadence, coverage", "background"],
          ] },
        ] },
        instrument: { gid: "G3", rs: "RS-11", label: "Structure", subs: [
          { id: "G3.a", label: "Ownership & Terms", cards: [
            ["banner", "The raise as proposed — fractional split and staging", "compose"],
            ["kpis",   "Headline terms — raise · appraised value · units · cost-vs-raise", "extract"],
            ["ledger", "Ownership split — fractional holders and shares", "extract"],
            ["ledger", "Sources-and-uses reconciliation — absences flagged Q", "crosscheck"],
          ] },
          { id: "G3.b", label: "Rights & Rails", cards: [
            ["banner",    "Fractional title · IP royalty — the wrapper in one line", "kb"],
            ["paths",     "Exemption cards — audience · filing · blue sky", "background"],
            ["ledger",    "The instrument — fractional title / royalty · lockups · transfer · secondary", "extract"],
            ["banner",    "Liquidity discipline callout (warn) — illiquid by default", "compose"],
            ["lifecycle", "Issuance lifecycle — custody → verify → issue → secondary, each gated", "compose"],
          ] },
        ] },
        underwriting: { gid: "G4", rs: "RS-12", label: "Valuation Model", subs: [
          { id: "G4.a", label: "Appraisal & Comparables", cards: [
            ["banner", "Appraisal-method framing — basis, date, appraiser", "kb"],
            ["ledger", "Comparable sales — auction & private records, classed", "crosscheck"],
            ["ledger", "Appraisal reconciliation — method, range, independence", "extract"],
          ] },
          { id: "G4.b", label: "Liquidity & Returns", cards: [
            ["hypothesis", "Hypothesis framing — illiquidity discount, classed assumption", "kb"],
            ["kpis",   "Headline returns — appraised value · holding period · est. yield · liquidity", "extract"],
            ["ledger", "Return scenarios — appreciation vs royalty income band", "extract"],
          ] },
        ] },
      } },
  ];

  // ── join the ontology's class registry (ROOT identity) with the
  // class sections this schema owns. Report Schema builds on the ontology.
  const _root = {}; ((window.ONTOLOGY && window.ONTOLOGY.ASSET_CLASSES) || []).forEach((c) => (_root[c.id] = c));
  const ASSET_CLASSES = CLASS_DEFS.map((d) => { const r = _root[d.id] || {}; return { id: d.id, name: r.name || d.id, glyph: r.glyph || "·", ontology: r.group, codes: r.codes || [], sections: d.sections }; });

  // ── instantiate the ordered report for one asset class ─────────────
  function groupsForClass(classId) {
    const c = ASSET_CLASSES.find((x) => x.id === classId) || ASSET_CLASSES[0];
    const cs = (slot) => { const s = c.sections[slot]; return { id: s.gid, rs: [s.rs], req: false, classSpecific: true, label: s.label, subs: s.subs }; };
    const u = (g) => ({ ...UNIVERSAL_GROUPS[g], classSpecific: false });
    return [u("G1"), cs("subject"), cs("instrument"), cs("underwriting"), u("G5"), u("G6"), u("G7"), u("G8"), u("G9")];
  }

  const IMPLEMENTATIONS = [
    { project: "Raisable Inc. — Seed Round",          tenant: "Raisable Inc.", assetClass: "equity", report: "Offering Dossier · Company Equity class", version: "v3", sections: "9 / 9 + 3 ext", status: "published" },
    { project: "Lionsgate World Resort — Tokenization", tenant: "SAXCAP",      assetClass: "realEstate",  report: "Intelligence Report · Real Estate class", version: "v1", sections: "9 / 9 + 3 ext", status: "published" },
  ];
  // a class is "live" when one or more conforming reports reference it —
  // never hardcode a project into the asset-class config.
  const reportsForClass = (id) => IMPLEMENTATIONS.filter((r) => r.assetClass === id);

  // GROUPS retained as a back-compat alias (default class instantiation)
  const GROUPS = groupsForClass("realEstate");
  window.REPORT_SCHEMA = { id: "RPT-v3", SECTIONS, REPORTS, PARTS, CARD_TYPES, UNIVERSAL_GROUPS, ASSET_CLASSES, groupsForClass, GROUPS, IMPLEMENTATIONS };

  const STEP_LABEL = { parse: "01 · Parse", extract: "01 · Extract", kb: "01 · Knowledge Base", kg: "01 · Knowledge Graph", entity: "01 · Entity Registry", background: "02 · Entity background", crosscheck: "02 · Fact cross-checks", market: "02 · Market research", gaps: "02 · Gap analysis", compose: "03 · Compose", review: "03 · Human review", publish: "03 · Publish" };

  const rsMono = (s, extra) => <span style={{ fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-4)", ...extra }}>{s}</span>;

  function ReportSchemaRef({ go }) {
    const [cls, setCls] = React.useState("realEstate");
    const groups = groupsForClass(cls);
    const SLOT_ORDER = ["thesis", "subject", "instrument", "underwriting", "diligence", "readiness", "sources", "opportunities", "structure"];
    const groupBySlot = {}; SLOT_ORDER.forEach((s, i) => { groupBySlot[s] = groups[i]; });
    const cardCount = groups.reduce((n, g) => n + g.subs.reduce((m, s) => m + s.cards.length, 0), 0);
    const active = ASSET_CLASSES.find((c) => c.id === cls);

    // expandable "Section" rows in the instantiated structure, keyed report|sub
    const [openSecs, setOpenSecs] = React.useState({});
    const allSecKeys = [];
    REPORTS.forEach((rep) => rep.slots.map((s) => groupBySlot[s]).filter(Boolean).forEach((g) => {
      (g.rs || []).forEach((rid) => allSecKeys.push(rep.id + "|" + g.id + "|" + rid));
      if (g.subs.some((s) => s.rsRef) && g.subs.some((s) => !s.rsRef)) allSecKeys.push(rep.id + "|" + g.id + "|derived");
    }));
    const allOpen = allSecKeys.length > 0 && allSecKeys.every((k) => openSecs[k]);
    const setAll = (v) => setOpenSecs(v ? Object.fromEntries(allSecKeys.map((k) => [k, true])) : {});
    const toggleSec = (k) => setOpenSecs((o) => ({ ...o, [k]: !o[k] }));
    // RS sections that live outside the four published reports (surfaced elsewhere)
    const coveredRS = new Set(); groups.forEach((g) => (g.rs || []).forEach((r) => coveredRS.add(r)));
    const offReportRS = SECTIONS.filter((s) => !coveredRS.has(s.id));

    return (
      <div>
        <window.PageHead eyebrow="Platform · Canonical reference" title="Report Schema — RPT-v3"
          sub="The full nested structure of every published Intelligence Report — groups · sub-sections · cards — generalized across asset classes. Universal groups are shared by every class; the class-specific extensions (RS-10/11/12) are declared per asset class, so each instantiates a structurally-distinct section. The composer (Workflow · 03) generates against the instantiated schema; the review gate validates card-level coverage before publish."
          right={
            <div style={{ textAlign: "right" }}>
              <span style={{ display: "block", fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--accent-lo)", marginBottom: 4 }}>{SECTIONS.filter((s) => s.req).length} required + {SECTIONS.filter((s) => !s.req).length} optional sections</span>
              <span style={{ display: "block", fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 4 }}>{ASSET_CLASSES.length} asset classes · 4 universal groups</span>
              <span style={{ fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-4)" }}>{IMPLEMENTATIONS.length} conforming reports</span>
            </div>
          }>
        </window.PageHead>

        <window.SectionHead sub="four self-contained reports, each on its own cadence — together a reading layer over the groups below; the publish gate still validates every underlying card">Reports</window.SectionHead>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 10, marginBottom: 22 }}>
          {REPORTS.map((pt, i) => (
            <div key={pt.id} style={{ border: "1px solid var(--rule-hard)", borderTop: "3px solid " + (pt.classSpecific ? "var(--accent)" : "var(--ink-4)"), borderRadius: "var(--r-3)", background: "var(--bg)", padding: "12px 14px" }}>
              <div style={{ display: "flex", alignItems: "baseline", gap: 7, marginBottom: 5 }}>
                <span style={{ fontFamily: "var(--mono)", fontSize: "11px", fontWeight: 700, color: "var(--ink-4)" }}>{String(i + 1).padStart(2, "0")}</span>
                <span style={{ fontFamily: "var(--ui-display)", fontWeight: 600, fontSize: "var(--fs-sm)", color: "var(--ink)" }}>{pt.name}</span>
              </div>
              <div style={{ marginBottom: 7 }}><window.Pill tone="accent">{pt.cadence}</window.Pill></div>
              <p style={{ margin: "0 0 8px", fontSize: "var(--fs-xs)", color: "var(--ink-3)", lineHeight: 1.5 }}>{pt.sub}</p>
              <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
                {pt.slots.map((s) => <span key={s} style={{ fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.04em", textTransform: "uppercase", color: "var(--ink-4)", border: "1px solid var(--rule-hard)", borderRadius: "999px", padding: "1px 7px" }}>{s}</span>)}
                <window.Pill tone={pt.classSpecific ? "info" : "neutral"}>{pt.classSpecific ? "class-specific" : "universal"}</window.Pill>
              </div>
            </div>
          ))}
        </div>

        <window.SectionHead sub="each class declares its own sub-sections for the three class-specific slots — select a class to instantiate the structure below">Asset Classes</window.SectionHead>
        <div style={{ border: "1px solid var(--rule-hard)", borderRadius: "var(--r-3)", overflow: "hidden", marginBottom: 22 }}>
          <div style={{ display: "grid", gridTemplateColumns: "168px 1fr 1fr 1fr 92px", gap: 1, background: "var(--rule)" }}>
            {["Asset class", "Subject · RS-10", "Instrument · RS-11", "Underwriting · RS-12", "Status"].map((h) => (
              <div key={h} style={{ background: "var(--bg-2)", padding: "8px 12px" }}><window.MonoLabel>{h}</window.MonoLabel></div>
            ))}
            {ASSET_CLASSES.map((c) => {
              const reps = reportsForClass(c.id);
              const live = reps.length > 0;
              return (
              <React.Fragment key={c.id}>
                <div style={{ background: c.id === cls ? "var(--accent-bg)" : "var(--bg)", padding: "9px 12px" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <span style={{ fontSize: 13, color: "var(--accent-lo)" }}>{c.glyph}</span>
                    <span style={{ fontFamily: "var(--ui-display)", fontWeight: 600, fontSize: "var(--fs-sm)", color: "var(--ink)" }}>{c.name}</span>
                  </div>
                  {live && <div style={{ fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.04em", color: "var(--ink-4)", marginTop: 3 }}>{reps.length} conforming report{reps.length > 1 ? "s" : ""}</div>}
                </div>
                <div style={{ background: c.id === cls ? "var(--accent-bg)" : "var(--bg)", padding: "9px 12px", fontSize: "var(--fs-xs)", color: "var(--ink-2)" }}>{c.sections.subject.label}</div>
                <div style={{ background: c.id === cls ? "var(--accent-bg)" : "var(--bg)", padding: "9px 12px", fontSize: "var(--fs-xs)", color: "var(--ink-2)" }}>{c.sections.instrument.label}</div>
                <div style={{ background: c.id === cls ? "var(--accent-bg)" : "var(--bg)", padding: "9px 12px", fontSize: "var(--fs-xs)", color: "var(--ink-2)" }}>{c.sections.underwriting.label}</div>
                <div style={{ background: c.id === cls ? "var(--accent-bg)" : "var(--bg)", padding: "9px 12px" }}><window.Pill tone={live ? "ok" : "neutral"}>{live ? "live" : "available"}</window.Pill></div>
              </React.Fragment>
              );
            })}
          </div>
        </div>

        <window.SectionHead sub="the ownership chain, instantiated for the selected asset class: workflow step ▸ RS section ▸ sub-section ▸ card — each level owns the one below (one step fills many RS · an RS owns its sub-sections · a sub-section its cards). The step is owned by the RS; cards inherit it. Expand an RS to see its sub-sections and cards.">Report Structure — instantiated</window.SectionHead>
        {/* asset-class selector */}
        <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", marginBottom: 14 }}>
          {rsMono("asset class", { flex: "0 0 auto" })}
          <div style={{ display: "inline-flex", border: "1px solid var(--rule-hard)", borderRadius: "var(--r-2)", overflow: "hidden", background: "var(--bg)", flexWrap: "wrap" }}>
            {ASSET_CLASSES.map((c, i) => (
              <button key={c.id} onClick={() => setCls(c.id)} title={c.name} style={{ display: "inline-flex", alignItems: "center", gap: 6, border: "none", borderLeft: i ? "1px solid var(--rule-hard)" : "none", background: cls === c.id ? "var(--bg-dark)" : "transparent", color: cls === c.id ? "#fff" : "var(--ink-3)", padding: "7px 13px", cursor: "pointer", fontFamily: "var(--mono)", fontSize: "9.5px", letterSpacing: "0.06em", textTransform: "uppercase", fontWeight: 600 }}>
                <span style={{ fontSize: 11 }}>{c.glyph}</span>{c.name}
              </button>
            ))}
          </div>
          <span style={{ fontFamily: "var(--mono)", fontSize: "9px", letterSpacing: "0.06em", color: "var(--ink-4)" }}>{REPORTS.length} reports · {groups.length} groups · {groups.reduce((n, g) => n + g.subs.length, 0)} sections · {cardCount} cards{reportsForClass(cls).length > 0 ? "" : " · template (no conforming report yet)"}</span>
          <button onClick={() => setAll(!allOpen)} style={{ marginLeft: "auto", border: "1px solid var(--rule-hard)", background: "var(--bg)", color: "var(--ink-3)", borderRadius: "var(--r-2)", padding: "5px 11px", cursor: "pointer", fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.08em", textTransform: "uppercase", fontWeight: 600 }}>{allOpen ? "Collapse all" : "Expand all"}</button>
        </div>

        <div style={{ display: "flex", flexDirection: "column", gap: 18, marginBottom: 22 }}>
          {REPORTS.map((rep, ri) => {
            const repGroups = rep.slots.map((s) => groupBySlot[s]).filter(Boolean);
            const repCards = repGroups.reduce((n, g) => n + g.subs.reduce((m, s) => m + s.cards.length, 0), 0);
            return (
            <div key={rep.id} style={{ border: "1px solid var(--rule-hard)", borderLeft: "3px solid " + (rep.classSpecific ? "var(--accent)" : "var(--ink-3)"), borderRadius: "var(--r-3)", overflow: "hidden", background: "var(--bg)" }}>
              <div style={{ padding: "11px 15px", borderBottom: "1px solid var(--rule-soft)", display: "flex", alignItems: "baseline", gap: 10, flexWrap: "wrap", background: "var(--bg-2)" }}>
                <span style={{ fontFamily: "var(--mono)", fontSize: "10px", fontWeight: 700, color: "var(--accent-lo)" }}>{String(ri + 1).padStart(2, "0")}</span>
                <span style={{ fontFamily: "var(--ui-display)", fontWeight: 700, fontSize: "var(--fs-md)", color: "var(--ink)" }}>{rep.name}</span>
                <window.Pill tone="accent">{rep.cadence}</window.Pill>
                <span style={{ marginLeft: "auto", display: "inline-flex", gap: 8, alignItems: "center" }}>
                  <window.Pill tone={rep.classSpecific ? "info" : "neutral"}>{rep.classSpecific ? "class-specific" : "universal"}</window.Pill>
                  <span style={{ fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.06em", color: "var(--ink-4)" }}>{repGroups.length} group{repGroups.length > 1 ? "s" : ""} · {repCards} cards</span>
                </span>
              </div>
              <div style={{ padding: "6px 15px 12px" }}>
                {repGroups.map((g, gi) => {
                  // Ownership chain:  WFS ─1:N→ RS ─1:M→ G.x ─1:K→ C.
                  // Each RS owns its sub-sections; a sub-section points up to exactly one RS.
                  // The STEP (WFS) is owned by the RS — cards are leaves that inherit it.
                  const norm = (r) => (r ? (Array.isArray(r) ? r : [r]) : []);
                  const anyRef = g.subs.some((s) => s.rsRef);
                  const subsForRS = (rid) => (anyRef ? g.subs.filter((s) => norm(s.rsRef).includes(rid)) : g.subs);
                  const derivedSubs = anyRef ? g.subs.filter((s) => norm(s.rsRef).length === 0) : [];
                  return (
                  <div key={g.id} style={{ borderTop: gi ? "1px solid var(--rule-soft)" : "none", padding: "11px 0 4px" }}>
                    {/* the Group is a folder over its RS sections */}
                    <div style={{ display: "flex", alignItems: "baseline", gap: 9, flexWrap: "wrap", marginBottom: 8 }}>
                      <span style={{ fontFamily: "var(--mono)", fontSize: "9px", letterSpacing: "0.08em", fontWeight: 700, color: "var(--ink-3)", border: "1px solid var(--rule-hard)", borderRadius: "var(--r-1)", padding: "1px 6px" }}>{g.id}</span>
                      <span style={{ fontFamily: "var(--ui-display)", fontWeight: 600, fontSize: "var(--fs-sm)", color: "var(--ink)" }}>{g.label}</span>
                      <window.Pill tone={g.req ? "accent" : "neutral"}>{g.req ? "required" : "optional"}</window.Pill>
                    </div>
                    {(g.rs || []).map((rid) => {
                      const s = SECTION_BY_ID[rid]; if (!s) return null;
                      const subs = subsForRS(rid);
                      const key = rep.id + "|" + g.id + "|" + rid;
                      const open = !!openSecs[key];
                      const cardN = subs.reduce((n, x) => n + x.cards.length, 0);
                      return (
                      <div key={rid} style={{ padding: "0 0 6px 12px", borderLeft: "2px solid var(--accent-bd)", marginLeft: 2, marginBottom: 7 }}>
                        <button onClick={() => toggleSec(key)} style={{ display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap", width: "100%", border: "none", background: "transparent", cursor: "pointer", padding: "3px 0", textAlign: "left" }}>
                          <span style={{ fontFamily: "var(--mono)", fontSize: "9px", color: "var(--ink-4)", flex: "0 0 auto" }}>{open ? "▾" : "▸"}</span>
                          <span style={{ fontFamily: "var(--mono)", fontSize: "9px", fontWeight: 700, color: "var(--accent-lo)" }}>{s.id}</span>
                          <span style={{ fontFamily: "var(--ui-display)", fontWeight: 600, fontSize: "var(--fs-sm)", color: "var(--ink)" }}>{s.name}</span>
                          <span title="owning workflow step" style={{ fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.04em", color: "var(--ink-3)", border: "1px solid var(--rule-hard)", background: "var(--bg-2)", borderRadius: "var(--r-1)", padding: "1px 6px" }}>← {STEP_LABEL[s.step] || s.step}</span>
                          <span style={{ marginLeft: "auto", fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.06em", color: "var(--ink-4)" }}>{subs.length > 1 ? subs.length + " sub · " : ""}{cardN} card{cardN > 1 ? "s" : ""}</span>
                        </button>
                        {open && (
                          <div style={{ paddingTop: 5 }}>
                            <p style={{ margin: "0 0 8px", fontSize: "var(--fs-xs)", color: "var(--ink-3)", lineHeight: 1.55 }}>{s.def}</p>
                            {subs.map((sub) => (
                              <div key={sub.id} style={{ marginBottom: 7 }}>
                                {subs.length > 1 && (
                                  <div style={{ display: "flex", alignItems: "baseline", gap: 7, marginBottom: 4 }}>
                                    <span style={{ fontFamily: "var(--mono)", fontSize: "8.5px", fontWeight: 600, color: "var(--ink-4)" }}>{sub.id}</span>
                                    <span style={{ fontFamily: "var(--ui-display)", fontWeight: 600, fontSize: "var(--fs-xs)", color: "var(--ink-2)", textTransform: "uppercase", letterSpacing: "0.04em" }}>{sub.label}</span>
                                  </div>
                                )}
                                <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                                  {sub.cards.map(([type, def], ci) => (
                                    <div key={ci} style={{ display: "grid", gridTemplateColumns: "72px 1fr", gap: 10, alignItems: "baseline" }}>
                                      <span title={CARD_TYPES[type]} style={{ fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--accent-lo)", border: "1px solid var(--accent-bd)", background: "var(--accent-bg)", borderRadius: "var(--r-1)", padding: "1px 6px", textAlign: "center" }}>{type}</span>
                                      <span style={{ fontSize: "var(--fs-xs)", color: "var(--ink-3)", lineHeight: 1.5 }}>{def}</span>
                                    </div>
                                  ))}
                                </div>
                              </div>
                            ))}
                          </div>
                        )}
                      </div>
                      );
                    })}
                    {derivedSubs.length > 0 && (() => {
                      const key = rep.id + "|" + g.id + "|derived";
                      const open = !!openSecs[key];
                      const cardN = derivedSubs.reduce((n, x) => n + x.cards.length, 0);
                      return (
                      <div style={{ padding: "0 0 6px 12px", borderLeft: "2px dashed var(--rule-hard)", marginLeft: 2, marginBottom: 7 }}>
                        <button onClick={() => toggleSec(key)} style={{ display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap", width: "100%", border: "none", background: "transparent", cursor: "pointer", padding: "3px 0", textAlign: "left" }}>
                          <span style={{ fontFamily: "var(--mono)", fontSize: "9px", color: "var(--ink-4)", flex: "0 0 auto" }}>{open ? "▾" : "▸"}</span>
                          <span title="working sections with no canonical RS — outside the WFS▸RS▸G.x▸C chain" style={{ fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.04em", color: "var(--ink-4)", border: "1px solid var(--rule-hard)", borderRadius: "var(--r-1)", padding: "1px 6px" }}>no RS · derived</span>
                          <span style={{ marginLeft: "auto", fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.06em", color: "var(--ink-4)" }}>{cardN} card{cardN > 1 ? "s" : ""}</span>
                        </button>
                        {open && (
                          <div style={{ paddingTop: 5 }}>
                            {derivedSubs.map((sub) => (
                              <div key={sub.id} style={{ marginBottom: 7 }}>
                                <div style={{ display: "flex", alignItems: "baseline", gap: 7, marginBottom: 4 }}>
                                  <span style={{ fontFamily: "var(--mono)", fontSize: "8.5px", fontWeight: 600, color: "var(--ink-4)" }}>{sub.id}</span>
                                  <span style={{ fontFamily: "var(--ui-display)", fontWeight: 600, fontSize: "var(--fs-xs)", color: "var(--ink-2)", textTransform: "uppercase", letterSpacing: "0.04em" }}>{sub.label}</span>
                                </div>
                                <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                                  {sub.cards.map(([type, def, step], ci) => (
                                    <div key={ci} style={{ display: "grid", gridTemplateColumns: "72px 1fr 150px", gap: 10, alignItems: "baseline" }}>
                                      <span title={CARD_TYPES[type]} style={{ fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--accent-lo)", border: "1px solid var(--accent-bd)", background: "var(--accent-bg)", borderRadius: "var(--r-1)", padding: "1px 6px", textAlign: "center" }}>{type}</span>
                                      <span style={{ fontSize: "var(--fs-xs)", color: "var(--ink-3)", lineHeight: 1.5 }}>{def}</span>
                                      <span style={{ fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.04em", color: "var(--ink-4)", whiteSpace: "nowrap" }}>← {STEP_LABEL[step] || step}</span>
                                    </div>
                                  ))}
                                </div>
                              </div>
                            ))}
                          </div>
                        )}
                      </div>
                      );
                    })()}
                  </div>
                  );
                })}
              </div>
            </div>
            );
          })}
        </div>

        {offReportRS.length > 0 && (
          <div style={{ border: "1px dashed var(--rule-hard)", borderRadius: "var(--r-3)", background: "var(--bg-2)", padding: "12px 15px", marginBottom: 22 }}>
            {rsMono("also in the schema — surfaced outside the four published reports", { display: "block", marginBottom: 8 })}
            {offReportRS.map((s, i) => (
              <div key={s.id} style={{ display: "grid", gridTemplateColumns: "58px 1fr 150px 96px", gap: 10, alignItems: "baseline", borderTop: i ? "1px solid var(--rule-soft)" : "none", padding: "6px 0" }}>
                <span style={{ fontFamily: "var(--mono)", fontSize: "9px", fontWeight: 700, color: "var(--accent-lo)" }}>{s.id}</span>
                <span style={{ fontSize: "var(--fs-xs)", color: "var(--ink-3)", lineHeight: 1.5 }}><b style={{ color: "var(--ink-2)", fontWeight: 600 }}>{s.name}</b> — {s.def}</span>
                <span style={{ fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.04em", color: "var(--ink-4)", whiteSpace: "nowrap" }}>← {STEP_LABEL[s.step] || s.step}</span>
                <span style={{ display: "inline-flex" }}><window.Pill tone={s.req ? "accent" : "neutral"}>{s.req ? "required" : "optional"}</window.Pill></span>
              </div>
            ))}
          </div>
        )}

        <window.SectionHead sub="the generalization layer — every card in every report is one of these archetypes">Card Archetypes</window.SectionHead>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: 10, marginBottom: 22 }}>
          {Object.entries(CARD_TYPES).map(([k, v]) => (
            <div key={k} style={{ border: "1px solid var(--rule-hard)", borderRadius: "var(--r-2)", background: "var(--bg)", padding: "9px 12px", display: "flex", alignItems: "baseline", gap: 9 }}>
              <span style={{ fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--accent-lo)", border: "1px solid var(--accent-bd)", background: "var(--accent-bg)", borderRadius: "var(--r-1)", padding: "1px 6px", flex: "0 0 auto" }}>{k}</span>
              <span style={{ fontSize: "var(--fs-xs)", color: "var(--ink-3)", lineHeight: 1.5 }}>{v}</span>
            </div>
          ))}
        </div>

        <p style={{ margin: "14px 0 0", fontFamily: "var(--mono)", fontSize: "9px", letterSpacing: "0.06em", color: "var(--ink-4)", lineHeight: 1.7 }}>
          Universal groups are identical across asset classes; the class-specific groups (RS-10/11/12) are instantiated from the asset-class registry — to add a class you declare its three sections here and bind a project to it. Every card declares the workflow step that fills it (← 01/02/03), so a run computes schema coverage: filled · explicit gap · missing. The review gate (Workflow · 03) blocks publish while any required card is missing — an explicit gap passes, silence does not.
        </p>
      </div>
    );
  }

  window.ReportSchemaRef = ReportSchemaRef;
})();
