{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://moduloa.com/docs/bom-schema-v0.1.json",
  "title": "Moduloa intake BOM v0.1",
  "description": "The build-tree BOM Moduloa's OS accepts at quoting. This is an assembly-scoped manufacturing BOM: lines describe what Moduloa builds and what suppliers deliver — never the internal design tree of a handover part. JSON is the contract; CSV/XLSX are accepted at the edge, normalized to this form, and echoed back for sign-off. PDF BOMs are rejected. Published open, versioned like the thesis, to be corrected in public.",
  "type": "object",
  "required": ["standard", "version", "customer", "lines"],
  "properties": {
    "standard": { "const": "moduloa-bom" },
    "version": { "const": "0.1" },
    "customer": {
      "type": "object",
      "required": ["legal_name", "registry_id"],
      "properties": {
        "legal_name": { "type": "string" },
        "registry_id": { "$ref": "#/$defs/registry_id" }
      }
    },
    "lines": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/line" } }
  },
  "$defs": {
    "registry_id": {
      "type": "object",
      "required": ["scheme", "id"],
      "properties": {
        "scheme": { "enum": ["VAT", "DUNS", "GLN", "LEI"] },
        "id": { "type": "string" }
      },
      "description": "A verifiable company identity. Free-text names cannot be verified by software; registry IDs resolve against public registries with zero human contact."
    },
    "doc": {
      "type": "object",
      "required": ["doc_type", "uri", "sha256"],
      "properties": {
        "doc_type": { "enum": ["step_ap242", "step_ap242_pmi", "pdf_drawing_2d", "acceptance_spec", "interface_spec", "spec_designation", "coc", "material_cert", "fair_as9102", "assembly_work_def", "firmware_ref"] },
        "uri": { "type": "string" },
        "sha256": { "type": ["string", "null"], "description": "Required for file artifacts. The OS verifies it holds the exact artifact the BOM references, not a file with that name." },
        "format": { "type": "string" }
      }
    },
    "criterion": {
      "type": "object",
      "required": ["characteristic_id", "feature", "method", "sampling", "on_fail"],
      "properties": {
        "characteristic_id": { "type": "string" },
        "feature": { "type": "string" },
        "method": { "enum": ["visual", "gauge", "cmm", "electrical_test", "functional_test", "coc_review", "dimensional_sample"] },
        "nominal": { "type": "number" },
        "tol_plus": { "type": "number" },
        "tol_minus": { "type": "number" },
        "units": { "type": "string" },
        "sampling": { "enum": ["100pct", "aql_0_65", "aql_1_0", "aql_2_5", "first_article_only", "skip_lot"] },
        "on_fail": { "enum": ["reject_lot", "quarantine", "notify_customer"] }
      }
    },
    "line": {
      "type": "object",
      "required": ["line_id", "parent_line_id", "line_type", "part_number", "revision", "name", "qty", "uom", "source_type"],
      "properties": {
        "line_id": { "type": "string", "description": "Unique within the file. Immutable across intake revisions — enables diffing." },
        "parent_line_id": { "type": ["string", "null"], "description": "THE structural authority. Null exactly once (the root product). Must resolve; no cycles." },
        "level": { "type": "integer", "minimum": 0, "maximum": 5, "description": "Optional derived depth. If present it must equal the depth computed from parent_line_id — redundancy used as a corruption checksum, never as structure." },
        "line_type": { "enum": ["product", "assembly", "phantom", "handover_part"], "description": "product: the single root. assembly: a level Moduloa physically builds. phantom: a logical grouping never built or stocked as a unit — blown through for work orders and costing. handover_part: what a supplier delivers; always a leaf." },
        "part_class": { "enum": ["mechanical_custom", "pcba_module", "catalogue", "material"], "description": "Required for handover_part lines; drives the documentation matrix." },
        "part_number": { "type": "string", "minLength": 1, "maxLength": 64 },
        "revision": { "type": "string", "minLength": 1, "maxLength": 16, "description": "Every line, including assemblies. Never 'latest'." },
        "name": { "type": "string", "maxLength": 140 },
        "description": { "type": "string", "maxLength": 2000 },
        "qty": { "type": "number", "exclusiveMinimum": 0, "description": "Quantity per ONE unit of the parent line. The OS extends totals; customers never do." },
        "uom": { "enum": ["EA", "M", "MM", "G", "KG", "ML", "L", "M2"], "description": "EA requires integer qty." },
        "ref_designators": { "type": "array", "items": { "type": "string" }, "description": "If present for EA parts, the count must equal qty." },
        "source_type": { "enum": ["built_here", "handover"], "description": "Invariant: product/assembly/phantom are built_here; handover_part is handover." },
        "supplier": {
          "type": "object",
          "required": ["legal_name", "registry_id", "contact_email", "ship_from_country"],
          "properties": {
            "legal_name": { "type": "string" },
            "registry_id": { "$ref": "#/$defs/registry_id" },
            "contact_email": { "type": "string", "format": "email", "description": "The machine-reachable channel the confirmation protocol uses." },
            "ship_from_country": { "type": "string", "pattern": "^[A-Z]{2}$" }
          }
        },
        "supplier_part_number": { "type": "string" },
        "manufacturer": { "type": "string" },
        "mpn": { "type": "string", "description": "Required for catalogue and pcba_module classes — exact, no shorthand. Catalogue electronics are confirmed via parts APIs before any supplier is emailed." },
        "lead_time_days": { "type": "integer", "minimum": 0 },
        "long_lead": { "type": "boolean", "description": "Explicit customer attestation; the OS cross-checks it against lead_time_days." },
        "critical": { "type": "boolean", "description": "Critical lines get tightened receiving and a first-delivery FAIR." },
        "serialized": { "type": "boolean", "description": "Whether Moduloa captures per-unit serials at assembly — feeds traceability and the DPP chain." },
        "alternates_policy": { "enum": ["none", "listed_only", "any_fff_equivalent"], "description": "Explicit per line, never inferred. any_fff_equivalent is also the customer's standing consent to tier-1 component-swap optimization." },
        "alternates": { "type": "array", "items": { "type": "object", "properties": { "manufacturer": { "type": "string" }, "mpn": { "type": "string" } } } },
        "handling": { "type": "object", "properties": { "esd_class": { "type": "string" }, "msl": { "type": "string" }, "shelf_life_days": { "type": "integer" } } },
        "docs": { "type": "array", "items": { "$ref": "#/$defs/doc" } },
        "acceptance": {
          "type": "object",
          "properties": {
            "criteria": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/criterion" } },
            "first_delivery": { "enum": ["fair_as9102", "none"] }
          },
          "description": "The incoming-inspection contract, per line, as typed data — this is what replaces the drawing's tolerance job and lets receiving run with zero human contact."
        },
        "dpp": {
          "type": "object",
          "properties": {
            "product_id_scheme": { "type": "string" },
            "product_id": { "type": "string" },
            "dpp_uri": { "type": "string" }
          },
          "description": "Optional in v0.1, reserved: EU Digital Product Passport identifiers per EN 18219. The intake is DPP-ready before the category mandates land (2027-2030)."
        },
        "notes": { "type": "string" }
      }
    }
  },
  "x-rules": [
    "V1: exactly one root line — parent_line_id null, line_type product.",
    "V2: every parent_line_id resolves; no cycles. If level is present it must equal the derived depth, or the file is rejected as corrupt.",
    "V3: product and assembly lines have at least one child and are source built_here. Every built line carries an assembly_work_def and as-assembled step_ap242 at quoting.",
    "V4: handover_part lines are leaves. Nothing may be nested under one — a part's internal structure is its supplier's tree, and Moduloa contractually refuses to consume it. Attach interfaces, geometry, and acceptance criteria instead.",
    "V5: maximum depth 6 including the root. Deeper trees signal an EBOM leaking into the intake — rejected with guidance to mark subtrees as handover parts or phantoms.",
    "V6: line_id unique; (part_number, revision) unique per part identity; revision required everywhere, 'latest' is not a revision.",
    "V7: documentation per the published matrix, and every file artifact carries a SHA-256 the OS verifies at intake — docs are a cryptographic invariant, not a checkbox.",
    "V8: every handover line carries a supplier registry ID (VAT/DUNS/GLN/LEI), a machine-reachable contact, and the supplier's own part number; catalogue and pcba_module lines carry manufacturer + MPN.",
    "V9: every handover_part carries at least one acceptance criterion — no criteria, no incoming inspection contract, no quote.",
    "V10: alternates_policy listed_only requires the alternates array.",
    "V11: phantoms blow through — they roll into the nearest real parent for work orders and costing, and a phantom may not contain another phantom.",
    "V12: every handover line enters the supplier confirmation protocol with a fixed confirmation window. At the deadline the OS returns a deterministic outcome: lines confirmed, or lines named as unconfirmed with the quote's confidence reduced accordingly. Silence never becomes a human follow-up — it becomes a number.",
    "V13: if the supplier-attested revision differs from the submitted revision, the line fails with exception code REV-MISMATCH — the customer resolves it, not Moduloa."
  ]
}
