{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://pluginbloatanalyzer.com/schema/collector-manifest.json",
  "title": "PBA Collector Manifest",
  "description": "Budget contract every collector must declare. Enforced by the ScanRunner at runtime and by CI at build. See BLUEPRINT.md Section 25.",
  "type": "object",
  "required": [
    "id",
    "confidence_tier",
    "max_exec_ms",
    "max_db_queries",
    "max_memory_mb",
    "timeout_behavior",
    "failure_behavior",
    "remote_calls",
    "frontend_impact"
  ],
  "additionalProperties": false,
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]*$",
      "description": "Stable snake_case id. Never reused after deprecation."
    },
    "confidence_tier": {
      "enum": ["verified", "estimated"],
      "description": "verified = deterministic fact; estimated = probabilistic, hedged, off by default."
    },
    "max_exec_ms": {
      "type": "integer",
      "minimum": 1,
      "maximum": 10000,
      "description": "Hard execution ceiling on the host. Total static scan budget is <10s (§7.4)."
    },
    "max_db_queries": {
      "type": "integer",
      "minimum": 0,
      "maximum": 200,
      "description": "Counted via the query wrapper. Unbounded queries are forbidden."
    },
    "max_memory_mb": {
      "type": "integer",
      "minimum": 1,
      "maximum": 64,
      "description": "Memory delta, not absolute. Scan must fit 128MB on a 50-plugin site."
    },
    "timeout_behavior": {
      "enum": ["partial", "skip", "abort-scan"],
      "description": "What the runner does when this collector exceeds max_exec_ms."
    },
    "failure_behavior": {
      "enum": ["log-and-skip"],
      "description": "A collector failure is NEVER fatal to the whole scan."
    },
    "remote_calls": {
      "type": "array",
      "items": {
        "enum": [
          "wporg_plugin_api",
          "vuln_feed",
          "freemius",
          "pba_sync"
        ]
      },
      "uniqueItems": true,
      "description": "Allowlist only. Any other endpoint fails review. [] means no remote calls."
    },
    "frontend_impact": {
      "const": "none",
      "description": "MUST be 'none'. No collector may add measurable visitor-facing load."
    }
  }
}
