{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/AndrewAltimit/swarmchasing/blob/main/schemas/sc-sightings-v1.json",
  "title": "SC-Sightings v1",
  "description": "What a swarm left in one operator's own logs, shared without the logs (docs/specs/sightings-v1.md): per flagged cluster, keyed hashes of its linking features, how its requests were made, hour buckets and its agent grade. Never a URL, path, query value, IP address, user agent or cookie; the operator's own hosts, defanged, only when it opts in.",
  "type": "object",
  "additionalProperties": false,
  "required": ["spec", "generated", "rules_version", "shares_hosts", "sightings"],
  "properties": {
    "spec": { "const": "sc-sightings-v1" },
    "generated": { "type": "string", "pattern": "^([0-9]{4}-[0-9]{2}-[0-9]{2})?$", "description": "The day the file was made, YYYY-MM-DD." },
    "rules_version": { "type": "string" },
    "operator": { "type": "string", "maxLength": 121, "description": "How the sender describes itself. Shown to the recipient, never read as who the sender is." },
    "shares_hosts": { "type": "boolean", "description": "The operator chose to include its own host names." },
    "missing_epochs": { "type": "array", "items": { "$ref": "#/$defs/epoch" }, "description": "Epochs with no published key; their features are left out." },
    "sightings": { "type": "array", "maxItems": 500, "items": { "$ref": "#/$defs/sighting" } }
  },
  "if": { "properties": { "shares_hosts": { "const": false } } },
  "then": { "properties": { "sightings": { "items": { "not": { "required": ["hosts"] } } } } },
  "$defs": {
    "epoch": { "type": "string", "pattern": "^[0-9]{4}Q[1-4]$" },
    "sighting": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "tier", "agent_grade", "records", "time_basis", "epochs"],
      "properties": {
        "id": { "type": "string", "pattern": "^S[0-9]{1,4}$", "description": "S1, S2, ... in the analysis's cluster order; never the cluster key." },
        "tier": { "enum": ["suggestive", "significant"] },
        "agent_grade": { "type": "string", "pattern": "^L[0-4]$", "description": "The computed agent grade. L5 is an admin's case decision and is never exported." },
        "records": { "type": "integer", "minimum": 1 },
        "time_basis": { "enum": ["request", "submit", "capture", "mixed", "unknown"] },
        "epochs": { "type": "array", "items": { "$ref": "#/$defs/epoch" } },
        "hours": {
          "type": "array",
          "maxItems": 2000,
          "items": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}Z$" },
          "description": "UTC hours with at least one of the cluster's records."
        },
        "features": { "type": "array", "maxItems": 400, "items": { "$ref": "#/$defs/feature" } },
        "scaffold": { "$ref": "#/$defs/scaffold" },
        "hosts": { "type": "array", "items": { "type": "string", "pattern": "^[a-z0-9\\[\\]_-]+(\\[\\.\\][a-z0-9_-]+)*$" }, "description": "The operator's own hosts the cluster reached, defanged; only with shares_hosts." }
      }
    },
    "feature": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "epoch", "hash"],
      "properties": {
        "kind": { "enum": ["param", "rule", "secret"] },
        "name": { "type": "string", "maxLength": 80, "description": "The parameter name or rule id; empty for a secret." },
        "epoch": { "$ref": "#/$defs/epoch" },
        "hash": { "type": "string", "pattern": "^[0-9a-f]{32}$", "description": "HMAC-SHA256 under the epoch's published key, first 16 bytes as hex." }
      }
    },
    "scaffold": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "relay_stacks": { "type": "object", "additionalProperties": { "type": "integer", "minimum": 1 }, "description": "Relay chains by rule id, outermost first, ending at `target`." },
        "dialect": { "type": "array", "items": { "type": "string", "pattern": "^[a-z0-9_]+\\.[a-z0-9_]+$" } },
        "rules": { "type": "object", "additionalProperties": { "type": "integer", "minimum": 1 } },
        "tactics": { "type": "object", "propertyNames": { "pattern": "^T[1-9]$" }, "additionalProperties": { "type": "integer", "minimum": 1 } },
        "header_sets": { "type": "array", "items": { "type": "string" } },
        "asns": { "type": "object", "propertyNames": { "pattern": "^[0-9]{1,10}$" }, "additionalProperties": { "type": "integer", "minimum": 1 } }
      }
    }
  }
}
