{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "$id": "https://fusionbenchmark.com/schema/v1/fusionbenchmark.schema.json",
 "title": "FusionBenchmark Dataset",
 "description": "Entity model for fusion and advanced-nuclear organizations, products, facilities, investment and sourced performance measurements.",
 "$comment": "DESIGN THESIS — five moves carry the future-proofing, in order of importance. (1) Every number in the dataset is a Measurement in one flat collection, addressed by a metric id from a registry; adding a metric is a data change, never a schema change. (2) Gain has no denominator-free metric id, so an undenominated Q is unrepresentable rather than merely invalid. (3) Organizations are one entity with roles, not separate companies/investors collections, because Eni, Google, Mitsubishi, BWXT and Bezos Expeditions each occupy several roles at once. (4) Controlled terms live in a SKOS-lite vocabulary with broader/deprecated/replacedBy, so taxonomy churn is additive. (5) Every entity is additionalProperties:false but carries an unvalidated `ext` object, so typos are caught while genuinely new fields can land ahead of the schema.",
 "type": "object",
 "required": [
  "schemaVersion"
 ],
 "additionalProperties": false,
 "$comment_fragments": "Only schemaVersion is required, because data ships as FRAGMENTS — data/organizations/*.json, data/measurements/<subject>.json and so on, each validating against this same schema. The build merges the fragments and only then checks referential integrity, uniqueness and derived fields. Hand-editing one flat multi-megabyte file is what made the template's data hard to review.",
 "properties": {
  "$comment": {
   "type": "string",
   "description": "Free-text annotation for human maintainers. Permitted on every object so hand-maintained JSON can carry reasoning, since JSON has no comment syntax."
  },
  "schemaVersion": {
   "type": "string",
   "pattern": "^\\d+\\.\\d+\\.\\d+$",
   "$comment": "Semver of THIS schema that the instance targets. Major bump = breaking. Readers must refuse a major they do not know."
  },
  "generatedAt": {
   "$ref": "#/$defs/Timestamp"
  },
  "vocabularies": {
   "type": "object",
   "description": "Controlled terms keyed by scheme name. The key matches the id prefix: vocabularies.approach holds ids like 'approach:tokamak'.",
   "additionalProperties": {
    "type": "array",
    "items": {
     "$ref": "#/$defs/VocabularyTerm"
    }
   },
   "$comment": "additionalProperties is open ON PURPOSE. A new scheme (say 'coolant') is added by writing data, not by editing this file."
  },
  "sources": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Source"
   }
  },
  "organizations": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Organization"
   }
  },
  "people": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Person"
   }
  },
  "products": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Product"
   }
  },
  "facilities": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Facility"
   }
  },
  "assets": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Asset"
   }
  },
  "deals": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Deal"
   }
  },
  "relationships": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Relationship"
   }
  },
  "measurements": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Measurement"
   }
  },
  "milestones": {
   "type": "array",
   "items": {
    "$ref": "#/$defs/Milestone"
   }
  }
 },
 "$defs": {
  "Id": {
   "type": "string",
   "pattern": "^[a-z][a-z0-9]*:[a-z0-9][a-z0-9-]*$",
   "description": "Stable namespaced identifier, e.g. 'org:commonwealth-fusion-systems'. IMMUTABLE — it survives rebrands, which is why it is not the URL slug.",
   "maxLength": 120
  },
  "OrgRef": {
   "allOf": [
    {
     "$ref": "#/$defs/Id"
    }
   ],
   "pattern": "^org:"
  },
  "PersonRef": {
   "allOf": [
    {
     "$ref": "#/$defs/Id"
    }
   ],
   "pattern": "^person:"
  },
  "ProductRef": {
   "allOf": [
    {
     "$ref": "#/$defs/Id"
    }
   ],
   "pattern": "^product:"
  },
  "FacilityRef": {
   "allOf": [
    {
     "$ref": "#/$defs/Id"
    }
   ],
   "pattern": "^facility:"
  },
  "SourceRef": {
   "allOf": [
    {
     "$ref": "#/$defs/Id"
    }
   ],
   "pattern": "^src:"
  },
  "MetricRef": {
   "allOf": [
    {
     "$ref": "#/$defs/Id"
    }
   ],
   "pattern": "^metric:"
  },
  "Slug": {
   "type": "string",
   "pattern": "^[a-z0-9][a-z0-9-]*$",
   "maxLength": 100,
   "description": "URL segment. MAY change; see previousSlugs for redirect generation."
  },
  "PartialDate": {
   "type": "string",
   "pattern": "^\\d{4}(-\\d{2}(-\\d{2})?)?$",
   "description": "ISO 8601 truncated to the precision actually known: '2022', '2022-12' or '2022-12-05'. Never pad to a false precision — 'founded 2018' must not become 2018-01-01.",
   "examples": [
    "2018",
    "2021-09",
    "2022-12-05"
   ]
  },
  "Timestamp": {
   "type": "string",
   "format": "date-time"
  },
  "DateQualifier": {
   "type": "string",
   "enum": [
    "exact",
    "circa",
    "before",
    "after",
    "estimated"
   ],
   "default": "exact"
  },
  "ConfidenceLevel": {
   "type": "string",
   "enum": [
    "high",
    "medium",
    "low"
   ]
  },
  "ClaimStatus": {
   "type": "string",
   "enum": [
    "achieved",
    "planned",
    "claimed",
    "disputed",
    "retracted",
    "superseded"
   ],
   "description": "achieved = measured and independently checkable. planned = target or roadmap, NEVER leaderboard-eligible. claimed = asserted by the operator, not independently confirmed. disputed = credible challenge exists. retracted = withdrawn by the claimant. superseded = a later measurement replaces it (kept for history, not deleted).",
   "$comment": "Ported from entangledfuture's achieved-vs-planned rule, widened. 'claimed' and 'disputed' are the additions that let the rejection page exist as data rather than prose."
  },
  "Money": {
   "type": "object",
   "required": [
    "currency"
   ],
   "additionalProperties": false,
   "description": "Structured money. Replaces the template's freeform strings ('$65M+', 'CA$23M+', 'EUR 2.35M', '~$8.94M across 3 rounds'), which cannot be sorted, summed or currency-converted.",
   "properties": {
    "amount": {
     "type": "number",
     "minimum": 0,
     "description": "Whole units of `currency` — 1800000000, not 1.8. Omit when only a bound or a label is known."
    },
    "currency": {
     "type": "string",
     "pattern": "^[A-Z]{3}$",
     "description": "ISO 4217."
    },
    "isMinimum": {
     "type": "boolean",
     "default": false,
     "description": "true encodes the '+' in '$65M+' honestly instead of dropping or overstating it."
    },
    "isUndisclosed": {
     "type": "boolean",
     "default": false
    },
    "asOf": {
     "$ref": "#/$defs/PartialDate",
     "description": "Required for anything conversion- or inflation-sensitive."
    },
    "usdEquivalent": {
     "type": "number",
     "minimum": 0,
     "description": "Build-time convenience for cross-currency sorting. DERIVED — never hand-edited. Record the rate date in asOf."
    },
    "display": {
     "type": "string",
     "maxLength": 80,
     "description": "Optional human string when the structured form loses nuance. Presentation only; never parsed."
    },
    "note": {
     "type": "string"
    }
   }
  },
  "Location": {
   "type": "object",
   "additionalProperties": false,
   "properties": {
    "country": {
     "type": "string",
     "pattern": "^[A-Z]{2}$",
     "description": "ISO 3166-1 alpha-2. Code, not name — names change and do not sort."
    },
    "region": {
     "type": "string"
    },
    "city": {
     "type": "string"
    },
    "address": {
     "type": "string"
    },
    "postalCode": {
     "type": "string"
    },
    "latitude": {
     "type": "number",
     "minimum": -90,
     "maximum": 90
    },
    "longitude": {
     "type": "number",
     "minimum": -180,
     "maximum": 180
    },
    "precision": {
     "type": "string",
     "enum": [
      "exact",
      "city",
      "region",
      "country"
     ],
     "description": "Stops a city centroid being rendered as a site location on the map."
    }
   }
  },
  "ExternalIds": {
   "type": "object",
   "additionalProperties": {
    "type": "string"
   },
   "description": "Identifiers in outside registries, for reconciliation and as provenance anchors. Open map so a new registry never needs a schema change.",
   "propertyNames": {
    "pattern": "^[a-zA-Z][a-zA-Z0-9]*$"
   },
   "examples": [
    {
     "wikidata": "Q55641393",
     "ror": "05gzmn429",
     "lei": "5493001KJTIIGC8Y1R12",
     "cik": "0001862463",
     "openCorporates": "us_de/6742049",
     "crunchbase": "commonwealth-fusion-systems",
     "nrcDocket": "05200050",
     "iaeaArisId": "VOYGR",
     "euTransparencyRegister": "…"
    }
   ]
  },
  "Provenance": {
   "type": "object",
   "required": [
    "sources"
   ],
   "additionalProperties": false,
   "description": "Attached to every claim that could be wrong. The whole site rests on this object.",
   "properties": {
    "sources": {
     "type": "array",
     "minItems": 1,
     "items": {
      "$ref": "#/$defs/SourceRef"
     }
    },
    "confidence": {
     "$ref": "#/$defs/ConfidenceLevel"
    },
    "verifiedAt": {
     "$ref": "#/$defs/PartialDate",
     "description": "When a human last checked this against the source. Absent = never verified; renders as unverified."
    },
    "verifiedFields": {
     "type": "array",
     "items": {
      "type": "string"
     },
     "description": "Field names on THIS record that have been checked against the archived capture. A blanket verified/unverified is coarser than the truth: a company name and website can be confirmed mechanically against the source, while its sector and founding year cannot. Naming which fields were checked is more useful than a single flag, and harder to overstate."
    },
    "verifiedBy": {
     "type": "string"
    },
    "note": {
     "type": "string",
     "description": "Why this is believed, or what could not be confirmed. Kept separate from any user-facing description field so it is never fed to SEO meta."
    }
   }
  },
  "Lifecycle": {
   "type": "object",
   "additionalProperties": false,
   "properties": {
    "status": {
     "$ref": "#/$defs/Id",
     "description": "Vocabulary ref, e.g. 'status:operating', 'status:acquired', 'status:defunct', 'status:stealth'."
    },
    "statusNote": {
     "type": "string"
    },
    "foundedDate": {
     "$ref": "#/$defs/PartialDate"
    },
    "endDate": {
     "$ref": "#/$defs/PartialDate",
     "description": "Dissolution, decommissioning or discontinuation."
    },
    "successor": {
     "$ref": "#/$defs/Id",
     "description": "Where the entity went: acquirer, renamed entity, replacement device."
    }
   }
  },
  "Record": {
   "type": "object",
   "description": "Editorial bookkeeping mixed into every top-level entity.",
   "additionalProperties": false,
   "properties": {
    "createdAt": {
     "$ref": "#/$defs/PartialDate"
    },
    "updatedAt": {
     "$ref": "#/$defs/PartialDate"
    },
    "verifiedAt": {
     "$ref": "#/$defs/PartialDate"
    },
    "completeness": {
     "type": "string",
     "enum": [
      "stub",
      "partial",
      "complete"
     ]
    },
    "editorialNote": {
     "type": "string"
    }
   }
  },
  "VocabularyTerm": {
   "type": "object",
   "required": [
    "id",
    "label"
   ],
   "additionalProperties": false,
   "description": "SKOS-lite controlled term. Hierarchy via `broader` gives rollup queries for free: filtering approach:magnetic-confinement transitively catches spherical tokamaks.",
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/Id"
    },
    "label": {
     "type": "string"
    },
    "shortLabel": {
     "type": "string",
     "description": "For table headers and chips."
    },
    "definition": {
     "type": "string"
    },
    "broader": {
     "$ref": "#/$defs/Id",
     "description": "Parent term in the same scheme."
    },
    "aliases": {
     "type": "array",
     "items": {
      "type": "string"
     },
     "description": "Search synonyms and historical names."
    },
    "deprecated": {
     "type": "boolean",
     "default": false
    },
    "replacedBy": {
     "$ref": "#/$defs/Id",
     "description": "Set with deprecated:true. Readers follow the pointer, so retiring a term never orphans existing records."
    },
    "sources": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/SourceRef"
     }
    },
    "sortOrder": {
     "type": "integer",
     "description": "Explicit ordering where alphabetical is wrong (regulatory stages, TRL)."
    },
    "dimension": {
     "type": "string",
     "description": "METRIC TERMS ONLY. Physical dimension, e.g. 'temperature', 'time', 'dimensionless', 'triple-product'."
    },
    "canonicalUnit": {
     "type": "string",
     "description": "METRIC TERMS ONLY. Unit all values are normalised to at build time."
    },
    "betterDirection": {
     "type": "string",
     "enum": [
      "higher",
      "lower",
      "none"
     ],
     "description": "METRIC TERMS ONLY. Lets leaderboards sort a metric they have never seen before."
    },
    "appliesTo": {
     "type": "array",
     "items": {
      "type": "string",
      "enum": [
       "organization",
       "product",
       "facility",
       "person",
       "deal"
      ]
     },
     "description": "METRIC TERMS ONLY."
    },
    "numerator": {
     "type": "string",
     "description": "METRIC TERMS ONLY — gain metrics. Prose definition of what is measured out."
    },
    "denominator": {
     "type": "string",
     "description": "METRIC TERMS ONLY — gain metrics. Prose definition of what is counted in. THE key field of this whole schema: because gain is split into metric:gain-target / gain-plasma / gain-engineering / gain-wallplug and no bare 'metric:gain' exists, an undenominated Q cannot be written down at all."
    },
    "inverseLabel": {
     "type": "string",
     "description": "RELATIONSHIP TERMS ONLY. Label read from `to` back to `from`, e.g. supplies / is supplied by."
    },
    "symmetric": {
     "type": "boolean",
     "description": "RELATIONSHIP TERMS ONLY. true for partner-with; false for supplies."
    }
   },
   "$comment": "One term shape with scheme-specific optional fields, rather than a per-scheme schema. Adding 'coolant' or 'licence-class' needs no schema edit."
  },
  "Source": {
   "type": "object",
   "required": [
    "id",
    "type",
    "independence"
   ],
   "additionalProperties": false,
   "description": "Deduplicated bibliography entry. Claims reference these by id, so one paper cited by forty measurements is stored once and can be corrected once.",
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/SourceRef"
    },
    "type": {
     "type": "string",
     "enum": [
      "peer-reviewed",
      "preprint",
      "conference",
      "regulatory-filing",
      "official",
      "financial-disclosure",
      "press-release",
      "news",
      "interview",
      "dataset",
      "book",
      "personal-communication"
     ],
     "description": "Evidence class ONLY. The template conflated this with the publisher ('sourceType': 'ibm'); publisher belongs in `publisher`."
    },
    "independence": {
     "type": "string",
     "enum": [
      "independent",
      "operator",
      "aggregator",
      "circular",
      "unknown"
     ],
     "description": "independent = cannot have come from us or from the subject. operator = the subject's own publication (fine, but not confirmation). aggregator = a directory that may be recycling. circular = provably reproduces our own copy; MUST NOT be cited as evidence.",
     "$comment": "Encodes entangledfuture's circular-sourcing rule as data rather than prose, so audit-provenance can compute it instead of a human remembering."
    },
    "title": {
     "type": "string"
    },
    "url": {
     "type": "string",
     "format": "uri"
    },
    "doi": {
     "type": "string"
    },
    "arxiv": {
     "type": "string"
    },
    "publisher": {
     "type": "string",
     "description": "Journal, agency, outlet or company."
    },
    "authors": {
     "type": "array",
     "items": {
      "type": "string"
     }
    },
    "publishedDate": {
     "$ref": "#/$defs/PartialDate"
    },
    "accessedDate": {
     "$ref": "#/$defs/PartialDate"
    },
    "archiveUrl": {
     "type": "string",
     "format": "uri",
     "description": "Wayback or perma.cc. Fusion press pages rot fast; capture on first cite."
    },
    "quote": {
     "type": "string",
     "maxLength": 2000,
     "description": "The exact sentence supporting the claim. Makes later re-verification cheap and catches drift."
    },
    "paywalled": {
     "type": "boolean"
    },
    "language": {
     "type": "string",
     "pattern": "^[a-z]{2}(-[A-Z]{2})?$",
     "description": "Native-language sources are often the only non-circular route for JP/CN/KR/FR organizations."
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   }
  },
  "Organization": {
   "type": "object",
   "required": [
    "id",
    "slug",
    "name",
    "roles"
   ],
   "additionalProperties": false,
   "description": "Every corporate-ish actor: developers, investors, suppliers, utilities, national labs, universities, regulators, government programmes. ONE collection.",
   "$comment": "No totalRaised / employees / headcount fields. Those are Measurements — dated, sourced and historical — not scalars that silently go stale.",
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/OrgRef"
    },
    "slug": {
     "$ref": "#/$defs/Slug"
    },
    "previousSlugs": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Slug"
     },
     "description": "Drives 301s. Rebrands are frequent in this sector."
    },
    "name": {
     "type": "string"
    },
    "legalName": {
     "type": "string"
    },
    "nativeName": {
     "type": "string",
     "description": "Non-Latin script name — the search key that finds non-circular coverage."
    },
    "aliases": {
     "type": "array",
     "items": {
      "type": "string"
     }
    },
    "roles": {
     "type": "array",
     "minItems": 1,
     "items": {
      "$ref": "#/$defs/Id",
      "pattern": "^role:"
     },
     "description": "e.g. role:fusion-developer, role:reactor-developer, role:investor, role:supplier, role:operator, role:utility, role:regulator, role:research-institute, role:government-programme. Multiple roles are the norm, not the exception."
    },
    "sector": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Id",
      "pattern": "^sector:"
     },
     "description": "sector:fusion | sector:fission | sector:fuel-cycle | sector:supply-chain. Array, because BWXT and Mitsubishi span several."
    },
    "approaches": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Id",
      "pattern": "^approach:"
     },
     "description": "Confinement or reactor approach pursued. Array — TAE and Helion each carry more than one lineage."
    },
    "supplyCategories": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Id"
     },
     "uniqueItems": true,
     "description": "What this organisation supplies, as cat: terms. Coarser than Product.category, which stays the finer-grained home once product records exist. Assigned ONLY where the category's own terminology appears verbatim in an archived capture alongside a supply verb -- a page that merely mentions gyrotrons is describing an application, not a product line."
    },
    "fuels": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Id",
      "pattern": "^fuel:"
     }
    },
    "shortSummary": {
     "type": "string",
     "maxLength": 400,
     "description": "One or two sentences for cards. Safe for meta description."
    },
    "description": {
     "type": "string",
     "description": "Long-form body copy."
    },
    "website": {
     "type": "string",
     "format": "uri"
    },
    "location": {
     "$ref": "#/$defs/Location",
     "description": "Headquarters. Additional sites are Facilities."
    },
    "logo": {
     "$ref": "#/$defs/AssetRef",
     "description": "Convenience pointer to the primary logo Asset. DERIVED — the build resolves it from assets where subject matches and role is primary. Never hand-edited."
    },
    "socials": {
     "type": "object",
     "additionalProperties": {
      "type": "string",
      "format": "uri"
     },
     "description": "Open map — a new platform is a data change."
    },
    "externalIds": {
     "$ref": "#/$defs/ExternalIds"
    },
    "lifecycle": {
     "$ref": "#/$defs/Lifecycle"
    },
    "listing": {
     "type": "object",
     "additionalProperties": false,
     "description": "Public-market facts. Absent for private companies.",
     "properties": {
      "ticker": {
       "type": "string"
      },
      "exchange": {
       "type": "string",
       "description": "MIC code preferred, e.g. XNYS, XNAS."
      },
      "listedDate": {
       "$ref": "#/$defs/PartialDate"
      },
      "listingType": {
       "type": "string",
       "enum": [
        "ipo",
        "spac",
        "direct-listing",
        "uplisting"
       ]
      }
     }
    },
    "investorProfile": {
     "type": "object",
     "additionalProperties": false,
     "description": "Present only when roles includes role:investor. Portfolio is DERIVED from deals — never stored here as a name list.",
     "properties": {
      "investorType": {
       "$ref": "#/$defs/Id",
       "description": "e.g. invtype:venture, invtype:corporate-vc, invtype:sovereign-wealth, invtype:government-agency, invtype:family-office, invtype:angel, invtype:crossover, invtype:strategic."
      },
      "fundSize": {
       "$ref": "#/$defs/Money"
      },
      "fundVintage": {
       "$ref": "#/$defs/PartialDate"
      },
      "stagesFocus": {
       "type": "array",
       "items": {
        "$ref": "#/$defs/Id"
       }
      },
      "thesis": {
       "type": "string"
      }
     }
    },
    "keyPeople": {
     "type": "array",
     "description": "Inline roster. Use the Person entity + affiliation relationships when someone needs their own page.",
     "items": {
      "type": "object",
      "required": [
       "name"
      ],
      "additionalProperties": false,
      "properties": {
       "person": {
        "$ref": "#/$defs/PersonRef"
       },
       "name": {
        "type": "string"
       },
       "role": {
        "type": "string"
       },
       "isFounder": {
        "type": "boolean"
       },
       "startDate": {
        "$ref": "#/$defs/PartialDate"
       },
       "endDate": {
        "$ref": "#/$defs/PartialDate"
       }
      }
     }
    },
    "provenance": {
     "$ref": "#/$defs/Provenance"
    },
    "record": {
     "$ref": "#/$defs/Record"
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   }
  },
  "Person": {
   "type": "object",
   "required": [
    "id",
    "slug",
    "name"
   ],
   "additionalProperties": false,
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/PersonRef"
    },
    "slug": {
     "$ref": "#/$defs/Slug"
    },
    "name": {
     "type": "string"
    },
    "nativeName": {
     "type": "string"
    },
    "bio": {
     "type": "string"
    },
    "externalIds": {
     "$ref": "#/$defs/ExternalIds",
     "description": "orcid, wikidata, linkedin."
    },
    "provenance": {
     "$ref": "#/$defs/Provenance"
    },
    "record": {
     "$ref": "#/$defs/Record"
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   },
   "$comment": "Affiliations are Relationships, not fields — people move, and employment history is an edge with dates."
  },
  "Product": {
   "type": "object",
   "required": [
    "id",
    "slug",
    "name",
    "vendor",
    "productType"
   ],
   "additionalProperties": false,
   "description": "A TYPE: something offered, licensed or sold, which may have many physical instances. NuScale's VOYGR is a Product; a specific plant built from it is a Facility.",
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/ProductRef"
    },
    "slug": {
     "$ref": "#/$defs/Slug"
    },
    "name": {
     "type": "string"
    },
    "aliases": {
     "type": "array",
     "items": {
      "type": "string"
     },
     "description": "Acronyms and alternative names. JET, W7-X, EAST and LHD are how these devices are universally referred to; without this neither search nor dedup can match them."
    },
    "vendor": {
     "$ref": "#/$defs/OrgRef"
    },
    "codevelopers": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/OrgRef"
     }
    },
    "productType": {
     "$ref": "#/$defs/Id",
     "pattern": "^ptype:",
     "description": "ptype:power-plant-design, ptype:reactor-module, ptype:component, ptype:material, ptype:fuel, ptype:subsystem, ptype:instrument, ptype:software, ptype:service, ptype:isotope, ptype:energy-offtake."
    },
    "category": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Id"
     },
     "description": "Finer classification within the type, e.g. cat:hts-tape, cat:gyrotron, cat:tritium-handling, cat:divertor, cat:triso-fuel."
    },
    "shortSummary": {
     "type": "string",
     "maxLength": 400
    },
    "description": {
     "type": "string"
    },
    "approaches": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Id",
      "pattern": "^approach:"
     }
    },
    "fuels": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Id",
      "pattern": "^fuel:"
     }
    },
    "maturity": {
     "type": "object",
     "additionalProperties": false,
     "description": "Readiness. TRL is dated and sourced because self-reported TRL inflates.",
     "properties": {
      "stage": {
       "$ref": "#/$defs/Id",
       "description": "pstage:concept | pstage:development | pstage:prototype | pstage:qualified | pstage:commercial | pstage:discontinued."
      },
      "trl": {
       "type": "integer",
       "minimum": 1,
       "maximum": 9
      },
      "trlAsOf": {
       "$ref": "#/$defs/PartialDate"
      },
      "trlSelfReported": {
       "type": "boolean",
       "default": true
      },
      "firstDelivery": {
       "$ref": "#/$defs/PartialDate"
      }
     }
    },
    "certifications": {
     "type": "array",
     "description": "Design approvals and qualifications — the commercial gate in nuclear.",
     "items": {
      "type": "object",
      "required": [
       "body",
       "name"
      ],
      "additionalProperties": false,
      "properties": {
       "body": {
        "$ref": "#/$defs/OrgRef",
        "description": "Regulators are Organizations, so each gets a page for free."
       },
       "name": {
        "type": "string"
       },
       "status": {
        "$ref": "#/$defs/ClaimStatus"
       },
       "date": {
        "$ref": "#/$defs/PartialDate"
       },
       "reference": {
        "type": "string",
        "description": "Docket, certificate or application number — an outside-checkable anchor."
       },
       "sources": {
        "type": "array",
        "items": {
         "$ref": "#/$defs/SourceRef"
        }
       }
      }
     }
    },
    "supersedes": {
     "$ref": "#/$defs/ProductRef"
    },
    "supersededBy": {
     "$ref": "#/$defs/ProductRef"
    },
    "lifecycle": {
     "$ref": "#/$defs/Lifecycle"
    },
    "provenance": {
     "$ref": "#/$defs/Provenance"
    },
    "record": {
     "$ref": "#/$defs/Record"
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   },
   "$comment": "Specifications are NOT fields here. Rated power, field strength, enrichment and so on are Measurements with subject = this product id, so every spec carries its own date, source and status."
  },
  "Facility": {
   "type": "object",
   "required": [
    "id",
    "slug",
    "name",
    "facilityType"
   ],
   "additionalProperties": false,
   "description": "An INSTANCE: a physical machine, plant or installation. SPARC, NIF, W7-X, ITER, JET, and each built reactor unit.",
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/FacilityRef"
    },
    "slug": {
     "$ref": "#/$defs/Slug"
    },
    "name": {
     "type": "string"
    },
    "aliases": {
     "type": "array",
     "items": {
      "type": "string"
     },
     "description": "Acronyms and alternative names. JET, W7-X, EAST and LHD are how these devices are universally referred to; without this neither search nor dedup can match them."
    },
    "facilityType": {
     "$ref": "#/$defs/Id",
     "pattern": "^ftype:",
     "description": "ftype:fusion-device, ftype:power-reactor, ftype:research-reactor, ftype:test-stand, ftype:enrichment-plant, ftype:fuel-fabrication, ftype:manufacturing, ftype:laser-facility."
    },
    "operator": {
     "$ref": "#/$defs/OrgRef"
    },
    "owners": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/OrgRef"
     }
    },
    "basedOn": {
     "$ref": "#/$defs/ProductRef",
     "description": "The Product design this instance realises, when there is one. Research one-offs like JET have none."
    },
    "approaches": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Id",
      "pattern": "^approach:"
     }
    },
    "fuels": {
     "type": "array",
     "items": {
      "$ref": "#/$defs/Id",
      "pattern": "^fuel:"
     }
    },
    "location": {
     "$ref": "#/$defs/Location"
    },
    "shortSummary": {
     "type": "string",
     "maxLength": 400
    },
    "description": {
     "type": "string"
    },
    "timeline": {
     "type": "object",
     "additionalProperties": false,
     "description": "Convenience mirrors of the canonical Milestone records, for cheap card rendering.",
     "properties": {
      "constructionStart": {
       "$ref": "#/$defs/PartialDate"
      },
      "firstPlasma": {
       "$ref": "#/$defs/PartialDate"
      },
      "firstCriticality": {
       "$ref": "#/$defs/PartialDate"
      },
      "gridConnection": {
       "$ref": "#/$defs/PartialDate"
      },
      "shutdown": {
       "$ref": "#/$defs/PartialDate"
      }
     }
    },
    "predecessor": {
     "$ref": "#/$defs/FacilityRef"
    },
    "successor": {
     "$ref": "#/$defs/FacilityRef",
     "description": "Trenta → Polaris, SPARC → ARC, JET → ITER."
    },
    "lifecycle": {
     "$ref": "#/$defs/Lifecycle"
    },
    "provenance": {
     "$ref": "#/$defs/Provenance"
    },
    "record": {
     "$ref": "#/$defs/Record"
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   },
   "$comment": "Separating Facility from Product is the fix for the template's known friction, where Quantinuum's H2 and Helios had to be crammed into one company record. Fusion breaks that model outright: JET, ITER and W7-X have no vendor, while CFS has both SPARC and ARC."
  },
  "Measurement": {
   "type": "object",
   "required": [
    "id",
    "subject",
    "metric",
    "status",
    "provenance"
   ],
   "additionalProperties": false,
   "description": "ONE dated, sourced observation of ONE metric about ONE subject. Every number in the dataset is one of these — performance, product specs, headcount, capacity, funding totals.",
   "$comment": "The central future-proofing move. Fixed metric fields (performance.tripleProduct, performance.ionTemperature_keV) rot: the first time someone needs tritium breeding ratio or shots-per-day, the schema changes and every consumer breaks. Here a new metric is a vocabulary row. Cost: weaker JSON-Schema-level typing and a build-time join. Both are paid once, in the validator and in Astro.",
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/Id",
     "pattern": "^meas:"
    },
    "subject": {
     "$ref": "#/$defs/Id",
     "description": "Any entity id — facility, product, organization, person or deal."
    },
    "metric": {
     "$ref": "#/$defs/MetricRef"
    },
    "value": {
     "type": "number"
    },
    "valueText": {
     "type": "string",
     "description": "For genuinely non-numeric results ('ignition achieved', 'D-T'). Exactly one of value / valueText / range should be present."
    },
    "range": {
     "type": "object",
     "additionalProperties": false,
     "description": "When only bounds are known. Distinct from uncertainty on a point value.",
     "properties": {
      "min": {
       "type": "number"
      },
      "max": {
       "type": "number"
      }
     }
    },
    "unit": {
     "type": "string",
     "description": "As reported. The validator converts to the metric's canonicalUnit and writes valueCanonical."
    },
    "valueCanonical": {
     "type": "number",
     "description": "DERIVED — normalised to canonicalUnit for sorting and charting. Never hand-edited."
    },
    "uncertainty": {
     "type": "object",
     "additionalProperties": false,
     "properties": {
      "plus": {
       "type": "number",
       "minimum": 0
      },
      "minus": {
       "type": "number",
       "minimum": 0
      },
      "type": {
       "type": "string",
       "enum": [
        "absolute",
        "relative",
        "sigma",
        "confidence-interval"
       ]
      },
      "level": {
       "type": "number",
       "description": "e.g. 0.95 for a 95% interval."
      }
     }
    },
    "qualifier": {
     "type": "string",
     "enum": [
      "peak",
      "sustained",
      "average",
      "nominal",
      "rated",
      "design",
      "cumulative"
     ],
     "description": "Peak ion temperature and sustained ion temperature are different achievements. Conflating them is a listed red flag."
    },
    "date": {
     "$ref": "#/$defs/PartialDate",
     "description": "When the measurement was TAKEN, not when it was reported."
    },
    "dateQualifier": {
     "$ref": "#/$defs/DateQualifier"
    },
    "status": {
     "$ref": "#/$defs/ClaimStatus"
    },
    "supersedes": {
     "$ref": "#/$defs/Id",
     "pattern": "^meas:",
     "description": "Prior measurement this replaces. Chains build the progress charts and make regressions visible instead of silently overwritten."
    },
    "conditions": {
     "type": "object",
     "description": "Shot-level context. Deliberately OPEN — conditions are unbounded across approaches and cannot be enumerated in advance.",
     "additionalProperties": true,
     "properties": {
      "shotId": {
       "type": "string"
      },
      "campaign": {
       "type": "string"
      },
      "fuel": {
       "$ref": "#/$defs/Id"
      },
      "pulseDuration_s": {
       "type": "number"
      },
      "isSimulated": {
       "type": "boolean",
       "description": "true marks a modelled rather than measured result — a listed red flag when presented as achievement."
      },
      "isDesignParameter": {
       "type": "boolean",
       "description": "True when the figure is what the machine was DESIGNED to do, not what it has done. Critical for leaderboards: the IAEA survey lists WEST at a 1000 s pulse length and KSTAR at 300 s as design specifications, while their achieved records are far lower. Ranking the two together publishes a design brochure as an achievement table. Distinct from status:planned, which describes a whole device that does not yet exist."
      }
     }
    },
    "provenance": {
     "$ref": "#/$defs/Provenance"
    },
    "record": {
     "$ref": "#/$defs/Record"
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   }
  },
  "Milestone": {
   "type": "object",
   "required": [
    "id",
    "subject",
    "milestoneType",
    "status"
   ],
   "additionalProperties": false,
   "description": "A dated event or gate: technical, regulatory or commercial. One shape for all three so the fusion and nuclear trackers share machinery.",
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/Id",
     "pattern": "^ms:"
    },
    "subject": {
     "$ref": "#/$defs/Id"
    },
    "milestoneType": {
     "$ref": "#/$defs/Id",
     "pattern": "^mstype:",
     "description": "mstype:technical | mstype:regulatory | mstype:commercial | mstype:financial | mstype:construction."
    },
    "stage": {
     "$ref": "#/$defs/Id",
     "description": "Ordered stage within a track, e.g. regstage:construction-permit-issued. sortOrder on the term drives the timeline."
    },
    "title": {
     "type": "string"
    },
    "description": {
     "type": "string"
    },
    "status": {
     "$ref": "#/$defs/ClaimStatus"
    },
    "statusNote": {
     "type": "string",
     "description": "Why the status is what it is. Rule 6 says never delete a record to resolve a problem -- downgrade the status, add a note, let the ledger show the history -- but Milestone had nowhere to put the note, so the mechanism the rules mandate could not actually be used here."
    },
    "date": {
     "$ref": "#/$defs/PartialDate",
     "description": "Actual date when status is achieved."
    },
    "targetDate": {
     "$ref": "#/$defs/PartialDate",
     "description": "Current target when status is planned."
    },
    "targetHistory": {
     "type": "array",
     "description": "Every previously stated target, with when it was stated. SLIP TRACKING — the most informative and least published field in advanced nuclear, and the reason a target date is never overwritten in place.",
     "items": {
      "type": "object",
      "required": [
       "targetDate",
       "statedOn"
      ],
      "additionalProperties": false,
      "properties": {
       "targetDate": {
        "$ref": "#/$defs/PartialDate"
       },
       "statedOn": {
        "$ref": "#/$defs/PartialDate"
       },
       "sources": {
        "type": "array",
        "items": {
         "$ref": "#/$defs/SourceRef"
        }
       }
      }
     }
    },
    "regulator": {
     "$ref": "#/$defs/OrgRef"
    },
    "reference": {
     "type": "string",
     "description": "Docket, licence or application number."
    },
    "provenance": {
     "$ref": "#/$defs/Provenance"
    },
    "record": {
     "$ref": "#/$defs/Record"
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   }
  },
  "Deal": {
   "type": "object",
   "required": [
    "id",
    "target",
    "instrument",
    "announcedDate"
   ],
   "additionalProperties": false,
   "description": "A financing or commercial event. Modelling only VC rounds fails immediately in this sector, where DOE milestone awards, ARPA-E grants, UK STEP funding and Microsoft-style offtake agreements move as much capital as equity.",
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/Id",
     "pattern": "^deal:"
    },
    "target": {
     "$ref": "#/$defs/OrgRef",
     "description": "Who received the capital or commitment."
    },
    "instrument": {
     "$ref": "#/$defs/Id",
     "pattern": "^instr:",
     "description": "instr:equity-round | instr:grant | instr:milestone-award | instr:debt | instr:convertible | instr:ipo | instr:spac | instr:acquisition | instr:offtake-ppa | instr:prize | instr:in-kind | instr:loan-guarantee."
    },
    "roundLabel": {
     "$ref": "#/$defs/Id",
     "description": "round:seed, round:series-a … Only meaningful for equity; absent for grants."
    },
    "announcedDate": {
     "$ref": "#/$defs/PartialDate"
    },
    "closedDate": {
     "$ref": "#/$defs/PartialDate"
    },
    "amount": {
     "$ref": "#/$defs/Money"
    },
    "valuationPre": {
     "$ref": "#/$defs/Money"
    },
    "valuationPost": {
     "$ref": "#/$defs/Money"
    },
    "participants": {
     "type": "array",
     "description": "Every participant is an OrgRef or PersonRef — no unresolvable name strings. If an investor is genuinely unknown, use undisclosedCount rather than inventing a placeholder.",
     "items": {
      "type": "object",
      "required": [
       "investor"
      ],
      "additionalProperties": false,
      "properties": {
       "investor": {
        "$ref": "#/$defs/Id"
       },
       "role": {
        "type": "string",
        "enum": [
         "lead",
         "co-lead",
         "participant",
         "follow-on",
         "strategic"
        ]
       },
       "amount": {
        "$ref": "#/$defs/Money"
       },
       "isNewInvestor": {
        "type": "boolean"
       }
      }
     }
    },
    "undisclosedCount": {
     "type": "integer",
     "minimum": 0,
     "description": "'and 6 undisclosed investors' — counted honestly instead of faked as a named entry."
    },
    "awardProgramme": {
     "$ref": "#/$defs/Id",
     "description": "For grants and milestone awards, e.g. prog:doe-milestone-based-fusion, prog:arpa-e-bethe, prog:uk-step."
    },
    "offtake": {
     "type": "object",
     "additionalProperties": false,
     "description": "For PPAs and supply agreements — capacity commitments, not cash.",
     "properties": {
      "counterparty": {
       "$ref": "#/$defs/OrgRef"
      },
      "capacityMW": {
       "type": "number"
      },
      "deliveryStart": {
       "$ref": "#/$defs/PartialDate"
      },
      "termYears": {
       "type": "number"
      },
      "isBinding": {
       "type": "boolean",
       "description": "MOUs and LOIs are routinely reported as sales. This separates them."
      }
     }
    },
    "description": {
     "type": "string"
    },
    "provenance": {
     "$ref": "#/$defs/Provenance"
    },
    "record": {
     "$ref": "#/$defs/Record"
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   },
   "$comment": "Cumulative totals raised are DERIVED by summing deals, never stored on the organization. That is what makes '$65M+' and '~$8.94M across 3 rounds' impossible to write."
  },
  "Relationship": {
   "type": "object",
   "required": [
    "id",
    "type",
    "from",
    "to"
   ],
   "additionalProperties": false,
   "description": "A typed, dated, sourced edge. Kept as its own collection so a new relation type is a vocabulary row rather than a new array on two entities.",
   "properties": {
    "$comment": {
     "type": "string",
     "description": "Free-text annotation for human maintainers."
    },
    "id": {
     "$ref": "#/$defs/Id",
     "pattern": "^rel:"
    },
    "type": {
     "$ref": "#/$defs/Id",
     "pattern": "^reltype:",
     "description": "reltype:supplies | partner-with | operates | owns | subsidiary-of | spun-out-of | licensee-of | customer-of | regulates | member-of | acquired | employs | collaborates-with."
    },
    "from": {
     "$ref": "#/$defs/Id"
    },
    "to": {
     "$ref": "#/$defs/Id"
    },
    "role": {
     "type": "string",
     "description": "Free-text nuance, e.g. 'HTS tape supply for TF coils'."
    },
    "startDate": {
     "$ref": "#/$defs/PartialDate"
    },
    "endDate": {
     "$ref": "#/$defs/PartialDate",
     "description": "Ended relationships are kept, not deleted — history is the point."
    },
    "status": {
     "$ref": "#/$defs/ClaimStatus"
    },
    "value": {
     "$ref": "#/$defs/Money",
     "description": "Contract value where disclosed."
    },
    "provenance": {
     "$ref": "#/$defs/Provenance"
    },
    "record": {
     "$ref": "#/$defs/Record"
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   },
   "$comment": "Stored once and rendered from both ends using the term's inverseLabel. The template embedded partnerships[] on the company, which duplicated every edge and let the two copies drift."
  },
  "Ext": {
   "type": "object",
   "additionalProperties": true,
   "description": "ESCAPE HATCH. Unvalidated bag for fields that do not exist yet. Every entity is otherwise additionalProperties:false, so typos are caught — but genuinely new data can land today and be promoted to a real field in a later minor version. Nothing in `ext` may be relied on for rendering."
  },
  "AssetRef": {
   "allOf": [
    {
     "$ref": "#/$defs/Id"
    }
   ],
   "pattern": "^asset:"
  },
  "Asset": {
   "type": "object",
   "required": [
    "id",
    "assetType",
    "subject",
    "file",
    "sha256",
    "licenseBasis"
   ],
   "additionalProperties": false,
   "description": "A captured binary artifact — logo, product image, facility photo, diagram. Carries provenance and a licence basis like any other claim, because a company logo is a trademark and using one is a legal act, not a styling choice.",
   "$comment": "Assets are captured through the same fetch/hash/archive path as textual sources (WORKFLOW.md §3.1). The file is content-addressed by sha256, so the same logo fetched twice is stored once and a silently changed logo is detectable.",
   "properties": {
    "$comment": {
     "type": "string"
    },
    "id": {
     "$ref": "#/$defs/AssetRef"
    },
    "assetType": {
     "$ref": "#/$defs/Id",
     "pattern": "^atype:",
     "description": "atype:logo | atype:wordmark | atype:icon | atype:product-image | atype:facility-photo | atype:diagram | atype:headshot."
    },
    "subject": {
     "$ref": "#/$defs/Id",
     "description": "Entity the asset depicts."
    },
    "role": {
     "type": "string",
     "enum": [
      "primary",
      "alternate",
      "historical"
     ],
     "description": "historical keeps superseded logos after a rebrand rather than deleting them."
    },
    "file": {
     "type": "string",
     "description": "Filename in the asset store, content-addressed: <sha256>.<ext>. Not a path, not a URL."
    },
    "sha256": {
     "type": "string",
     "pattern": "^[a-f0-9]{64}$"
    },
    "format": {
     "type": "string",
     "enum": [
      "svg",
      "png",
      "webp",
      "jpeg",
      "avif"
     ]
    },
    "width": {
     "type": "integer",
     "minimum": 1
    },
    "height": {
     "type": "integer",
     "minimum": 1
    },
    "bytes": {
     "type": "integer",
     "minimum": 1
    },
    "background": {
     "type": "string",
     "enum": [
      "transparent",
      "light",
      "dark",
      "unknown"
     ]
    },
    "legibleOn": {
     "type": "array",
     "items": {
      "type": "string",
      "enum": [
       "light",
       "dark"
      ]
     },
     "description": "Which site themes this asset is actually readable on, MEASURED not assumed. A near-black wordmark disappears on the #1a0f14 ember surface; without this field the dark theme silently loses logos."
    },
    "sourceUrl": {
     "type": "string",
     "format": "uri"
    },
    "capturedAt": {
     "$ref": "#/$defs/PartialDate"
    },
    "licenseBasis": {
     "$ref": "#/$defs/Id",
     "pattern": "^license:",
     "description": "license:trademark-nominative | license:press-kit | license:permission-granted | license:cc-by | license:cc-by-sa | license:public-domain | license:unknown. license:unknown must never be published — it is a placeholder that fails the gate."
    },
    "attribution": {
     "type": "string",
     "description": "Required text where the licence demands it (CC-BY, Wikimedia)."
    },
    "altText": {
     "type": "string",
     "description": "Accessibility text. For a logo this is the organization name, never \"logo\"."
    },
    "supersededBy": {
     "$ref": "#/$defs/AssetRef",
     "description": "Set on rebrand. The old mark is retained so historical pages stay accurate."
    },
    "provenance": {
     "$ref": "#/$defs/Provenance"
    },
    "record": {
     "$ref": "#/$defs/Record"
    },
    "ext": {
     "$ref": "#/$defs/Ext"
    }
   }
  }
 }
}