{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "ReportBody",
  "description": "Telemetry report posted by the 404 Solution WP plugin. Covers all report_type values: error, heartbeat, uninstall, support_request.",
  "$comment": "OWNER: 404-solution-server, contracts/schemas/report.schema.json. This exact file is byte-identical in both repos, so it says \"404-solution-server\" rather than \"this repo\". VENDORED COPY: 404-solution (the plugin repo), contracts/schemas/report.schema.json. Why 404-solution-server owns it: this file is compiled directly as the Fastify body schema by src/routes/reports.js, so this is the only copy whose constraints can actually reject a request; the plugin copy is a pre-flight convenience. The server is also the side that can be deployed on demand, while the plugin ships to thousands of sites that update on their own schedule, so a constraint must exist here before any plugin may rely on it. Note on debug_log_evidence.additionalProperties: it is true on purpose. src/server.js sets AJV removeAdditional: false so unknown body fields are preserved into the environment column rather than stripped, which means a closed sub-object does not merely ignore an unrecognised subfield, it 400s the whole report. Since a rejected error report falls back to emailing the developer, closing this object would turn the next additive plugin change into an inbox flood. The bounds below (maxLength, maximum, enum, required, minimum) are what this schema enforces; the field list is deliberately open. Change protocol: (1) edit this copy only, never the vendored one; (2) deploy the server before shipping a plugin that depends on the change, because an un-upgraded server rejects what it does not model (note the const-valued version fields, including debug_log_evidence.schema_version: a plugin that bumps one before this schema is deployed gets HTTP 400); (3) keep edits additive, so add properties and never remove, repurpose or redefine an existing one; (4) run npm run generate:types to refresh report.d.ts; (5) copy this file verbatim to the vendored path, then update EXPECTED_SHA256 in tests/report-schema-drift.test.js here and in tests-js/report-schema-drift.test.js in the plugin. Those two pins are what make a one-sided edit fail loudly. Do not add non-standard root keywords to this file: AJV strict mode (Fastify 5) throws at route build time on any unknown keyword, so this standard $comment is the only place such notes may live.",
  "type": "object",
  "required": ["plugin_version", "db_type"],
  "additionalProperties": true,
  "properties": {
    "payload_schema_version": { "type": "integer", "const": 2 },
    "plugin_version": { "type": "string", "minLength": 1, "maxLength": 32 },
    "wp_version": { "type": "string", "maxLength": 32 },
    "php_version": { "type": "string", "maxLength": 32 },
    "db_type": { "type": "string", "enum": ["mysql", "mariadb", "sqlite", "other"] },
    "db_version": { "type": "string", "maxLength": 64 },
    "is_multisite": { "type": "boolean" },
    "is_uninstall": { "type": "boolean" },
    "report_type": {
      "type": "string",
      "enum": ["error", "heartbeat", "uninstall", "support_request"]
    },
    "locale": { "type": "string", "maxLength": 16 },

    "resource_limits": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "php_memory": { "type": "integer", "minimum": 0 },
        "wp_memory": { "type": "integer", "minimum": 0 },
        "php_max_execution_seconds": { "type": "integer", "minimum": 0 },
        "php_post_max_size": { "type": "integer", "minimum": 0 },
        "php_upload_max_size": { "type": "integer", "minimum": 0 },
        "mysql_max_packet": { "type": "integer", "minimum": 0 },
        "mysql_wait_timeout_seconds": { "type": "integer", "minimum": 0 }
      }
    },

    "extensions": { "type": "object", "additionalProperties": true },
    "active_plugins": {
      "type": "array",
      "maxItems": 500,
      "items": { "type": "string", "maxLength": 256 }
    },
    "active_theme": { "type": "string", "maxLength": 255 },
    "object_cache": { "type": "string", "maxLength": 64 },
    "table_prefix": { "type": "string", "maxLength": 255 },
    "wp_debug": { "type": "boolean" },
    "server_software": { "type": "string", "maxLength": 255 },

    "site_url": { "type": ["string", "null"], "maxLength": 2048 },

    "wp_memory_limit_bytes": { "type": ["integer", "null"], "minimum": 0 },
    "published_posts_count": { "type": ["integer", "null"], "minimum": 0 },
    "published_pages_count": { "type": ["integer", "null"], "minimum": 0 },
    "categories_count": { "type": ["integer", "null"], "minimum": 0 },
    "tags_count": { "type": ["integer", "null"], "minimum": 0 },

    "redirects_active_total": { "type": ["integer", "null"], "minimum": 0 },
    "redirects_manual_count": { "type": ["integer", "null"], "minimum": 0 },
    "redirects_automatic_count": { "type": ["integer", "null"], "minimum": 0 },
    "redirects_regex_count": { "type": ["integer", "null"], "minimum": 0 },
    "redirects_trashed_count": { "type": ["integer", "null"], "minimum": 0 },

    "redirect_hit_count_histogram": {
      "type": ["object", "null"],
      "additionalProperties": false,
      "properties": {
        "zero_hits": { "type": "integer", "minimum": 0 },
        "one_to_ten_hits": { "type": "integer", "minimum": 0 },
        "eleven_to_hundred_hits": { "type": "integer", "minimum": 0 },
        "over_hundred_hits": { "type": "integer", "minimum": 0 }
      }
    },

    "captured_404s_active_total": { "type": ["integer", "null"], "minimum": 0 },
    "captured_404s_new_count": { "type": ["integer", "null"], "minimum": 0 },
    "captured_404s_ignored_count": { "type": ["integer", "null"], "minimum": 0 },
    "captured_404s_later_count": { "type": ["integer", "null"], "minimum": 0 },
    "captured_404s_trashed_count": { "type": ["integer", "null"], "minimum": 0 },

    "log_entries_count": { "type": ["integer", "null"], "minimum": 0 },
    "log_table_size_bytes": { "type": ["integer", "null"], "minimum": 0 },
    "error_count_in_log": { "type": ["integer", "null"], "minimum": 0 },
    "debug_file_size_bytes": { "type": ["integer", "null"], "minimum": 0 },

    "environment_extras": {
      "type": ["object", "null"],
      "additionalProperties": true,
      "properties": {
        "hosting_class": {
          "type": "object",
          "additionalProperties": true,
          "properties": {
            "host": { "type": "string" },
            "server_class": { "type": "string" },
            "panel": { "type": "string" },
            "matched_marker": { "type": "string" }
          }
        }
      }
    },

    "plugin_settings": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "default_redirect": { "type": "integer" },
        "capture_404": { "type": "boolean" },
        "capture_deletion": { "type": "integer" },
        "manual_deletion": { "type": "integer" },
        "log_deletion": { "type": "integer" },
        "send_error_logs": { "type": "boolean" },
        "debug_mode": { "type": "boolean" },
        "log_raw_ips": { "type": "boolean" },
        "maximum_log_disk_usage": { "type": "integer" },
        "remove_matches": { "type": "boolean" },
        "suggest_max": { "type": "integer" },
        "suggest_cats": { "type": "boolean" },
        "suggest_tags": { "type": "boolean" },
        "suggest_minscore": { "type": "integer" },
        "suggest_minscore_enabled": { "type": "boolean" },
        "update_suggest_url": { "type": "boolean" },
        "auto_redirects": { "type": "boolean" },
        "old_permalink_structure_redirects": { "type": "boolean" },
        "auto_slugs": { "type": "boolean" },
        "auto_trash_redirect": { "type": "boolean" },
        "auto_score": { "type": "integer" },
        "auto_score_title": { "type": "integer" },
        "auto_score_category_tag": { "type": "integer" },
        "auto_score_content": { "type": "integer" },
        "auto_deletion": { "type": "integer" },
        "auto_302_expiration_days": { "type": "integer" },
        "auto_cats": { "type": "boolean" },
        "auto_tags": { "type": "boolean" },
        "template_redirect_priority": { "type": "integer" },
        "dest404_behavior": { "type": "string" },
        "redirect_all_requests": { "type": "boolean" },
        "auto_trash_junk_urls": { "type": "boolean" },
        "perpage": { "type": "integer" },
        "page_redirects_order_by": { "type": "string" },
        "page_redirects_order": { "type": "string" },
        "captured_order_by": { "type": "string" },
        "captured_order": { "type": "string" },
        "admin_notification": { "type": "integer" },
        "admin_notification_frequency": { "type": "string" },
        "admin_notification_digest_limit": { "type": "integer" },
        "days_wait_before_major_update": { "type": "integer" }
      }
    },

    "debug_log": { "type": "string", "maxLength": 262144 },
    "debug_log_evidence": {
      "type": "object",
      "additionalProperties": true,
      "required": [
        "schema_version",
        "source",
        "error_excerpt",
        "error_excerpt_in_debug_log",
        "error_line_number",
        "total_evidence_bytes"
      ],
      "properties": {
        "schema_version": { "type": "integer", "const": 1 },
        "source": { "type": "string", "enum": ["debug_log", "crash_beacon"] },
        "error_excerpt": { "type": "string", "maxLength": 65536 },
        "error_excerpt_in_debug_log": { "type": "boolean" },
        "error_line_number": { "type": "integer", "minimum": -1 },
        "total_evidence_bytes": { "type": "integer", "minimum": 0, "maximum": 262144 }
      }
    },

    "uninstall_reason": { "type": ["string", "null"], "maxLength": 255 },
    "uninstall_details": { "type": ["string", "null"], "maxLength": 8192 },

    "contact_email": {
      "type": ["string", "null"],
      "format": "email",
      "maxLength": 254
    },

    "user_message": { "type": "string", "maxLength": 2000 },
    "reply_email": { "type": "string", "maxLength": 254 },
    "triggered_from": { "type": "string", "maxLength": 64 },
    "debug_log_excerpt": { "type": "string", "maxLength": 262144 }
  },
  "allOf": [
    {
      "if": {
        "properties": { "report_type": { "const": "error" } },
        "required": ["report_type"]
      },
      "then": {
        "required": ["debug_log"]
      }
    },
    {
      "if": {
        "properties": { "report_type": { "const": "support_request" } },
        "required": ["report_type"]
      },
      "then": {
        "required": ["user_message", "triggered_from"]
      }
    }
  ]
}
