{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.data-pulse.my/datapulse.schema.json",
  "title": "DataPulse MY dataset manifest",
  "type": "object",
  "required": [
    "$schema",
    "datasets"
  ],
  "properties": {
    "$schema": {
      "description": "Canonical manifest schema identifier, published at the www website origin. The retired GitHub Pages identifier stays accepted only so already-published manifests keep validating until their owner republishes; it is legacy compatibility, not a canonical URL.",
      "enum": [
        "https://www.data-pulse.my/datapulse.schema.json",
        "https://r3dz4r.github.io/datapulse-my/datapulse.schema.json"
      ]
    },
    "datasets": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/dataset"
      }
    }
  },
  "additionalProperties": false,
  "$defs": {
    "healthStatus": {
      "description": "Dataset health statuses emitted by scripts/check.sh.",
      "type": "string",
      "enum": [
        "fresh",
        "aging",
        "stale",
        "discontinued",
        "degraded",
        "browser-dependent",
        "unreachable",
        "unknown",
        "unknown-freshness",
        "reference"
      ]
    },
    "dataset": {
      "type": "object",
      "required": [
        "id",
        "name",
        "source",
        "steward",
        "custodian",
        "url",
        "licence",
        "attribution",
        "refresh_frequency",
        "expected_record_count",
        "geo_coverage",
        "health_report",
        "namespace"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "canonical_id": {
          "description": "Optional data.gov.my static-API dataset identifier. Populated where this dataset has a corresponding data.gov.my static-API identifier; absence is not a validation error and does not mean the dataset is unbound, because required url, steward, custodian, licence, and attribution fields provide source identity.",
          "type": "string",
          "minLength": 1,
          "pattern": "^[a-z0-9][a-z0-9_-]*$"
        },
        "series_code": {
          "description": "Stable code explicitly grouping datasets in the same canonical series.",
          "type": "string",
          "minLength": 1,
          "pattern": "^[a-z0-9][a-z0-9_-]*$"
        },
        "geography": {
          "description": "Exact catalogue geography key used for deterministic relationship matching.",
          "type": "string",
          "minLength": 1
        },
        "schema_id": {
          "description": "Stable identifier for an explicitly shared dataset schema.",
          "type": "string",
          "minLength": 1,
          "pattern": "^[a-z0-9][a-z0-9_.-]*$"
        },
        "supersedes": {
          "description": "Dataset ID or IDs explicitly replaced by this dataset.",
          "oneOf": [
            {
              "type": "string",
              "minLength": 1
            },
            {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "minLength": 1
              }
            }
          ]
        },
        "data_type": {
          "description": "Dataset semantic type. `reference` and `policy-reference` carry no freshness clock and classify as `reference`. `reference-current` is reference content that is still expected to be current, so it remains subject to the normal freshness classification (fresh/aging/stale).",
          "type": "string",
          "enum": [
            "reference",
            "policy-reference",
            "reference-current"
          ]
        },
        "discontinued": {
          "description": "Operator-applied marker that the source has stopped publishing this dataset.",
          "type": "boolean",
          "default": false
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "source": {
          "type": "string",
          "minLength": 1
        },
        "steward": {
          "type": "string",
          "minLength": 1
        },
        "custodian": {
          "description": "Stable publishing-agency ID resolved through custodians.json.",
          "type": "string",
          "minLength": 1,
          "pattern": "^[a-z0-9][a-z0-9_-]*$"
        },
        "url": {
          "type": "string",
          "format": "uri"
        },
        "vertical": {
          "description": "Opt in to the reviewed record-level evidence pipeline. Optional while vertical support is piloted.",
          "type": "boolean"
        },
        "record_evidence_schema": {
          "description": "Record-level evidence envelope version emitted for this vertical.",
          "type": "string",
          "const": "record-evidence/v1"
        },
        "record_source_url": {
          "description": "Raw tabular CSV URL ingested by the record evidence pipeline.",
          "type": "string",
          "format": "uri"
        },
        "licence": {
          "type": "string",
          "minLength": 1
        },
        "attribution": {
          "type": "string",
          "minLength": 1
        },
        "refresh_frequency": {
          "type": "string",
          "minLength": 1
        },
        "expected_record_count": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 0
        },
        "geo_coverage": {
          "type": "string",
          "minLength": 1
        },
        "health_report": {
          "type": "string",
          "pattern": "^data/[A-Za-z0-9_-]+\\.md$"
        },
        "namespace": {
          "type": "string",
          "enum": [
            "economy",
            "government_open_data",
            "environment",
            "weather",
            "healthcare",
            "financial",
            "transport",
            "other"
          ]
        },
        "real_status": {
          "description": "Observed upstream lifecycle state, independent of probe health.",
          "type": "string",
          "enum": [
            "live",
            "discontinued"
          ]
        },
        "verified_at": {
          "description": "Date the upstream lifecycle state was last verified.",
          "type": "string",
          "format": "date"
        },
        "attestation_ref": {
          "type": "string",
          "pattern": "^attestations/[0-9]{4}-[0-9]{2}-[0-9]{2}/(?:revisions/[0-9a-f]{64}/)?[A-Za-z0-9_-]+\\.json$"
        },
        "methodology_version": {
          "type": "integer",
          "minimum": 1
        },
        "probe_note": {
          "description": "Operator note explaining a probe result or limitation.",
          "type": "string",
          "minLength": 1
        },
        "discontinued_reason": {
          "description": "Evidence for marking an upstream dataset discontinued.",
          "type": "string",
          "minLength": 1
        },
        "freshness_policy": {
          "$ref": "#/$defs/freshnessPolicy"
        }
      },
      "additionalProperties": false
    },
    "freshnessPolicy": {
      "type": "object",
      "required": [
        "family",
        "content_date_field",
        "interpretation",
        "discontinued_on_404",
        "reference_table",
        "notes"
      ],
      "properties": {
        "family": {
          "description": "Stable source-family key used to select the probe-side freshness rule.",
          "type": "string",
          "enum": [
            "data_gov_my_openapi",
            "data_gov_my_storage",
            "data_gov_my",
            "dosm_via_data_gov_my",
            "opendosm_storage",
            "opendosm_data_gov_my_storage",
            "data_gov_my_archive",
            "data_gov_my_storage_dosm_gov_my",
            "github_parquet",
            "mbpp_arcgis_observation",
            "st_energy_balance",
            "st_energy_statistics"
          ]
        },
        "content_date_field": {
          "description": "Field that carries the date used for freshness classification.",
          "type": "string",
          "minLength": 1
        },
        "interpretation": {
          "description": "Semantics of the content date: observation period vs publisher publication date.",
          "type": "string",
          "enum": [
            "observation_period",
            "publication_date"
          ]
        },
        "discontinued_on_404": {
          "description": "When true, a probe that observes HTTP 404 classifies the row as discontinued rather than stale.",
          "type": "boolean"
        },
        "reference_table": {
          "description": "When true, the dataset is a lookup/reference table with no freshness clock.",
          "type": "boolean"
        },
        "notes": {
          "description": "Human-readable explanation of the family freshness rule.",
          "type": "string",
          "minLength": 1
        }
      },
      "additionalProperties": false
    }
  }
}
