{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://docs.imboard.ai/api/ontology/schema.json",
  "title": "I'mBoard Board Ontology — response schema",
  "description": "Generated from the OntologyResponse / OntologyDomainResponse / OntologyKpiResponse TypeScript types in @imboard/shared-types. Validate /api/ontology/index.json against #/definitions/OntologyResponse, /api/ontology/{domain}.json against #/definitions/OntologyDomainResponse, and /api/ontology/{domain}/{slug}.json against #/definitions/OntologyKpiResponse.",
  "definitions": {
    "OntologyResponse": {
      "type": "object",
      "properties": {
        "version": {
          "type": "string"
        },
        "releasedAt": {
          "type": "string"
        },
        "kpis": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OntologyKpi"
          }
        }
      },
      "required": [
        "kpis",
        "releasedAt",
        "version"
      ],
      "additionalProperties": false
    },
    "OntologyKpi": {
      "type": "object",
      "properties": {
        "rogueId": {
          "type": "string"
        },
        "slug": {
          "type": "string"
        },
        "domain": {
          "type": "string"
        },
        "defaultLabel": {
          "type": "string"
        },
        "description": {
          "type": "string"
        },
        "fieldType": {
          "type": "string"
        },
        "unit": {
          "type": [
            "string",
            "null"
          ]
        },
        "maturity": {
          "type": "string"
        },
        "suggestedForStages": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/CompanyStageType"
          }
        },
        "defaultOwningFunctions": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "stageRelevance": {
          "$ref": "#/definitions/Record%3Cstring%2CRogueKpiPriorityType%3E"
        },
        "definitionSource": {
          "$ref": "#/definitions/OntologyDefinitionSource"
        },
        "benchmark": {
          "$ref": "#/definitions/OntologyBenchmark"
        },
        "formula": {
          "type": "string"
        },
        "whyItMatters": {
          "type": "string"
        },
        "interpretationGuidance": {
          "type": "string"
        },
        "relatedKpiIds": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "calculationPolicy": {
          "$ref": "#/definitions/OntologyCalculationPolicy"
        },
        "metricBasis": {
          "$ref": "#/definitions/OntologyMetricBasis"
        },
        "inputContract": {
          "$ref": "#/definitions/OntologyInputContract"
        },
        "dependencies": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OntologyDependency"
          }
        }
      },
      "required": [
        "rogueId",
        "slug",
        "domain",
        "defaultLabel",
        "description",
        "fieldType",
        "unit",
        "maturity",
        "suggestedForStages",
        "defaultOwningFunctions",
        "stageRelevance",
        "definitionSource"
      ],
      "additionalProperties": false,
      "description": "The public ontology shape. Both surfaces (runtime backend + docs build) emit exactly this. Optional fields are emitted only when populated to keep the JSON lean for unenriched KPIs."
    },
    "CompanyStageType": {
      "type": "string",
      "enum": [
        "preSeed",
        "seed",
        "seriesA",
        "seriesB",
        "seriesC",
        "public"
      ]
    },
    "Record<string,RogueKpiPriorityType>": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/definitions/RogueKpiPriorityType"
      }
    },
    "RogueKpiPriorityType": {
      "type": "string",
      "enum": [
        "core",
        "recommended",
        "available"
      ]
    },
    "OntologyDefinitionSource": {
      "type": "object",
      "properties": {
        "tier": {
          "$ref": "#/definitions/RogueKpiDefinitionTierType"
        },
        "sourceName": {
          "type": "string"
        },
        "sourceUrl": {
          "type": [
            "string",
            "null"
          ]
        },
        "sectionRef": {
          "type": [
            "string",
            "null"
          ]
        },
        "publicationDate": {
          "type": "string"
        },
        "attributionNotice": {
          "type": [
            "string",
            "null"
          ]
        },
        "authorityLevel": {
          "$ref": "#/definitions/RogueKpiAuthorityLevelType",
          "description": "Sub-tier authority signal (plan §3.5, #2303). Refines what *kind* of authority backs the definition without changing `tier`. Optional and emitted only when populated, so this stays a back-compatible addition to the public `/api/ontology*` + MCP shape."
        }
      },
      "required": [
        "tier",
        "sourceName",
        "sourceUrl",
        "sectionRef",
        "publicationDate",
        "attributionNotice"
      ],
      "additionalProperties": false
    },
    "RogueKpiDefinitionTierType": {
      "type": "string",
      "enum": [
        "published",
        "editorial"
      ]
    },
    "RogueKpiAuthorityLevelType": {
      "type": "string",
      "enum": [
        "recognized-standard",
        "industry-benchmark",
        "self-declared-coalition",
        "imboard-editorial"
      ]
    },
    "OntologyBenchmark": {
      "type": "object",
      "properties": {
        "p25": {
          "type": "number"
        },
        "median": {
          "type": "number"
        },
        "p75": {
          "type": "number"
        },
        "unit": {
          "type": "string"
        },
        "sourceName": {
          "type": "string"
        },
        "sourceYear": {
          "type": "string"
        },
        "higherIsBetter": {
          "type": "boolean"
        }
      },
      "required": [
        "p25",
        "median",
        "p75",
        "unit",
        "sourceName",
        "sourceYear",
        "higherIsBetter"
      ],
      "additionalProperties": false,
      "description": "Reference distribution attached to ontology + MCP KPI responses. Structurally identical to the backend's `PublicBenchmarkSummary`; the backend keeps that alias for back-compat but it is now a re-export of this canonical shape."
    },
    "OntologyCalculationPolicy": {
      "type": "object",
      "properties": {
        "inclusionRules": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "exclusionRules": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "requiredInputs": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "dataSourcePriority": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "edgeCases": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "validationChecks": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "commonMiscomputations": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "inclusionRulesStructured": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OntologyPolicyRule"
          }
        },
        "exclusionRulesStructured": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OntologyPolicyRule"
          }
        },
        "validationAssertions": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OntologyValidationAssertion"
          }
        }
      },
      "additionalProperties": false,
      "description": "Per-KPI agent-safe computation contract (#1483 / v1.1.0).\n\nThe narrative `description`, `formula`, and `interpretationGuidance` fields tell humans *what* a metric means. `calculationPolicy` tells an AI agent\n*how* to compute it correctly from a company's messy data — what to include/exclude, what inputs are required, what data sources to prefer, what edge cases to handle, what validations to apply, and (highest leverage) what common miscomputations to avoid.\n\nThe `*Rules` buckets are free-text strings — the audience is LLM agents at reasoning time, not a deterministic computation engine. A structured DSL would force premature formalization; revisit only when deterministic consumers materialize. The `*RulesStructured` siblings (#2300) are an\n*additive parallel* form: the same statements carrying per-rule `mutability` + `provenance` (see `OntologyPolicyRule`). The string buckets stay the back-compat default; a later major version may collapse the two.\n\nAll subfields optional; emitted only when non-empty so unenriched KPIs keep a lean public JSON footprint. Most KPIs in v1.1.0 carry no policy; a curated set of high-leverage KPIs do — the canonical list of seeded rogueIds lives in the seed unit test (`seeds exactly the N high-leverage KPIs`), which is the single source of truth so the methodology page, changelog, and seed file cannot drift independently.\n\nEach editorial→published promotion (epic #1415) is a natural moment to populate this block."
    },
    "OntologyPolicyRule": {
      "type": "object",
      "properties": {
        "rule": {
          "type": "string"
        },
        "mutability": {
          "type": "string",
          "enum": [
            "invariant",
            "default",
            "company-defined"
          ]
        },
        "provenance": {
          "type": "string",
          "enum": [
            "anchor-derived",
            "imboard-authored"
          ]
        },
        "default": {
          "type": "string"
        },
        "sourceId": {
          "type": "string"
        }
      },
      "required": [
        "rule",
        "mutability",
        "provenance"
      ],
      "additionalProperties": false,
      "description": "One structured calculation rule (#2300 / v1.10.0 — epic #2287 Wave 3).\n\nThe free-text `inclusionRules` / `exclusionRules` buckets above are undifferentiated strings: nothing marks which rules a company may legitimately override versus which are invariants it must never touch, and provenance is pinned only per-metric (`definitionSource`) — a single divergent rule inside a policy can't be attributed to its own source. This *parallel* structured form carries that metadata per-rule without forcing a DSL rewrite of the prose buckets:   - `rule`       — the free-text statement, verbatim (the same audience: an LLM).   - `mutability` — `invariant` (never override — e.g. \"ARR annualizes monthly     × 12\"), `default` (a non-discretionary default the metric assumes), or     `company-defined` (a legitimate per-company policy choice, e.g. how to     treat `paused` subscriptions). Only `company-defined` rules are eligible     for a private override layer (out of scope here — this ships only the     *vocabulary* for overridability; see plan §1.4 / §3.2).   - `provenance` — `anchor-derived` (traceable to a cited standard via     `sourceId`) vs `imboard-authored` (imboard's own editorial rule).   - `default`    — the assumed value WHEN `mutability: 'company-defined'`     (e.g. `\"excluded\"`). Required for that case, omitted otherwise.   - `sourceId`   — per-rule attribution into the `definition-sources` catalog     (e.g. `'smsb-v1'`), finer-grained than today's per-metric `definitionSource`."
    },
    "OntologyValidationAssertion": {
      "type": "object",
      "properties": {
        "assert": {
          "type": "string",
          "description": "Sandboxed boolean expression over `result` + named inputs/refs, e.g. `\"result >= 0\"`, `\"result <= 1.0\"`, `\"result <= finance.gross_burn_rate\"`."
        },
        "severity": {
          "type": "string",
          "enum": [
            "error",
            "warning"
          ],
          "description": "Whether a violation is a hard error or an advisory warning."
        },
        "message": {
          "type": "string",
          "description": "Human explanation — the original `validationChecks` prose, retained."
        },
        "refs": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "rogueIds the expression references beyond `result` (cross-metric invariants), e.g. `[\"finance.gross_burn_rate\"]`. The evaluator's scope keys match these rogueIds verbatim."
        }
      },
      "required": [
        "assert",
        "severity"
      ],
      "additionalProperties": false,
      "description": "A single executable conformance rule on a `calculationPolicy` (#2301 / v1.10.0, epic #2287 Wave 3).\n\nWhere `validationChecks` is prose an LLM reads, each `validationAssertion` compiles a cross-metric invariant — \"ARR ≥ 0\", \"GRR ≤ 100%\", \"net burn ≤ gross burn\", \"NRR − GRR ≥ 0\", \"logo churn + retention = 100%\" — into a boolean `assert` expression any agent or checker can RUN against its computed output. The expression is evaluated by the sandboxed evaluator in `ontology-assertion-evaluator.ts` over a numeric scope binding `result` (the metric's own computed value) plus any cross-metric `refs` keyed by rogueId. No arbitrary code — whitelisted operators/identifiers only.\n\nAdditive + omitted-when-empty (the `calculationPolicy` discipline); piloted on the cross-metric invariants, absent elsewhere."
    },
    "OntologyMetricBasis": {
      "type": "object",
      "properties": {
        "timeBasis": {
          "type": "string",
          "enum": [
            "point_in_time",
            "period_flow",
            "trailing_window"
          ],
          "description": "When the value is measured."
        },
        "moneyBasis": {
          "type": "string",
          "enum": [
            "contracted_arr",
            "recognized_revenue",
            "cash",
            "bookings"
          ],
          "description": "Which kind of money — monetary KPIs only. `fieldType` already says \"currency\"; this says WHICH money, a distinction `fieldType` cannot express. Omitted for non-monetary KPIs and for pipeline/forecast/ valuation amounts (which are none of these realized money-kinds)."
        },
        "cohortBasis": {
          "type": "string",
          "enum": [
            "closed_start_cohort",
            "all_active"
          ],
          "description": "Cohort definition — cohort-based metrics (retention) only."
        },
        "dateBasis": {
          "type": "string",
          "enum": [
            "go_live",
            "signed",
            "period_close"
          ],
          "description": "Which contract event qualifies a record — the ARR/bookings family only."
        },
        "production": {
          "type": "string",
          "enum": [
            "computed",
            "primary"
          ],
          "description": "How the value is produced: `computed` (a deterministic function of other Rogue KPIs — ratios, rollups, waterfall-derived) vs `primary` (a raw measurement a human or source system enters). An agent uses this to decide \"compute this\" vs \"fetch this\". Not inferable from `formula`, which is populated on every KPI."
        }
      },
      "additionalProperties": false,
      "description": "Structured, machine-filterable measurement metadata for a KPI (#1494 / v1.3.0).\n\nWhere `calculationPolicy` is free-text guidance for an LLM, `metricBasis` is a small set of enum axes a consumer can filter and reason on deterministically — \"give me all point-in-time metrics\", \"all cohort-based metrics\", \"all metrics measured on a cash basis\".\n\nEvery sub-field is independently optional and omitted when not meaningful (the `calculationPolicy` pattern). The axes have very different applicability: `timeBasis` / `production` are near-universal among numeric KPIs; `moneyBasis` applies only to monetary KPIs; `dateBasis` only to the ARR/bookings family; `cohortBasis` only to retention metrics. A KPI carries only the axes that mean something for it — there is deliberately no `not_applicable` value. Narrative (`fieldType: 'text'`) KPIs carry no `metricBasis` at all."
    },
    "OntologyInputContract": {
      "type": "object",
      "properties": {
        "fields": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OntologyInputContractField"
          }
        }
      },
      "required": [
        "fields"
      ],
      "additionalProperties": false
    },
    "OntologyInputContractField": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Canonical input field the metric reasons about."
        },
        "type": {
          "type": "string",
          "enum": [
            "enum",
            "currency",
            "number",
            "date",
            "string",
            "boolean"
          ],
          "description": "Declared shape of the field."
        },
        "required": {
          "type": "boolean",
          "description": "Whether the metric cannot be computed without this field."
        },
        "description": {
          "type": "string",
          "description": "Short note on how the field feeds the metric."
        },
        "values": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Allowed members — for `type: 'enum'`."
        },
        "normalizationMap": {
          "$ref": "#/definitions/Record%3Cstring%2Cstring%3E",
          "description": "Maps raw source values → the metric's normalized concept. The piece that otherwise lives in the prompt, e.g. `{ past_due: 'at_risk', trialing: 'excluded', canceled: 'excluded' }`."
        }
      },
      "required": [
        "name",
        "type",
        "required"
      ],
      "additionalProperties": false,
      "description": "Typed input contract for a KPI (#2299 / v1.9.0 — epic #2287 Wave 2).\n\nWhere `calculationPolicy.requiredInputs` names *what* concepts a metric needs (free text), `inputContract` names the *shape* of those inputs — declared field names, types, enumerations, and (highest leverage) the raw→concept `normalizationMap` that says how a messy source column maps onto the metric's own notion of \"active\" / \"live\" / \"excluded\". That mapping otherwise lives in the agent's prompt and is the single biggest source of cross-agent divergence (the motivating ARR-from-CSV failure). Moving it into the base means two agents with two prompts compute the same number.\n\nimboard *declares* the contract; it does not execute it — no connectors, no compute. Optional + omitted-when-empty (the `calculationPolicy` discipline): piloted on a curated set of KPIs, absent on the rest. Each `field` carries:   - `name`     — the canonical input field the metric reasons about.   - `type`     — its declared shape (enum / currency / number / date / …).   - `required` — whether the metric cannot be computed without it.   - `values`   — the allowed members, for `type: 'enum'`.   - `normalizationMap` — raw source value → the metric's normalized concept,     e.g. `{ past_due: 'at_risk', trialing: 'excluded', canceled: 'excluded' }`."
    },
    "Record<string,string>": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      }
    },
    "OntologyDependency": {
      "type": "object",
      "properties": {
        "kpi": {
          "type": "string",
          "description": "Target catalog `rogueId` this metric depends on."
        },
        "edge": {
          "$ref": "#/definitions/OntologyDependencyEdge",
          "description": "What kind of relationship the target has to this metric."
        }
      },
      "required": [
        "kpi",
        "edge"
      ],
      "additionalProperties": false
    },
    "OntologyDependencyEdge": {
      "type": "string",
      "enum": [
        "computesFrom",
        "validatesAgainst",
        "contextualizes",
        "narrates"
      ],
      "description": "Typed dependency edge between two KPIs (#2302 / v1.12.0 — epic #2287 Wave 4).\n\n`relatedKpiIds: string[]` conflates three structurally different relationships — \"computed from,\" \"read/validated alongside,\" and \"qualitative narrative for.\" An agent traversing the graph to assemble a computation cannot tell an input from a cross-reference. A typed edge says *what kind* of relationship the target `kpi` has to the metric that declares it:\n\n  - `computesFrom`     — the target is a direct numeric INPUT to this metric                          (an agent must resolve it to compute this one).   - `validatesAgainst` — the target is an INDEPENDENT cross-check this metric                          should reconcile with — not an input (e.g. NRR↔ARR                          movement, GRR↔NRR same-cohort consistency).   - `contextualizes`   — the target supplies interpretive context for reading                          this metric (e.g. the cohort base behind a rate).   - `narrates`         — the target is the qualitative (`fieldType: 'text'`)                          companion that explains movement in this metric.\n\n`kpi` is a catalog `rogueId`; the referential-integrity gate in `validate-ontology.ts` fails the build on a dangling target (the same gate that already covers `relatedKpiIds`). Additive + omitted-when-empty (the `buildOntology*` discipline): piloted on the high-leverage ARR-movement / retention / burn-runway subtrees, absent on the rest. `relatedKpiIds` stays as the flat back-compat list alongside this — see plan §3.4."
    },
    "OntologyDomainResponse": {
      "type": "object",
      "properties": {
        "version": {
          "type": "string"
        },
        "releasedAt": {
          "type": "string"
        },
        "domain": {
          "type": "string"
        },
        "kpis": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OntologyKpi"
          }
        }
      },
      "required": [
        "domain",
        "kpis",
        "releasedAt",
        "version"
      ],
      "additionalProperties": false
    },
    "OntologyKpiResponse": {
      "type": "object",
      "properties": {
        "version": {
          "type": "string"
        },
        "releasedAt": {
          "type": "string"
        },
        "kpi": {
          "$ref": "#/definitions/OntologyKpi"
        }
      },
      "required": [
        "kpi",
        "releasedAt",
        "version"
      ],
      "additionalProperties": false
    }
  }
}
