{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/AndrewAltimit/swarmchasing/blob/main/schemas/sc-rules-v1.json",
  "title": "SC-Rules v1",
  "description": "A swarmchasing indicator rules file (docs/specs/rules-v1.md), as the JSON value of its TOML. Validates the rules file's structure and value domains; regular expressions are checked by compiling them, which a schema cannot do.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "meta": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "version": { "type": "string", "minLength": 1, "description": "The rules version. Cases pin it; reports and bundles quote it." }
      }
    },
    "thresholds": {
      "type": "object",
      "additionalProperties": false,
      "required": ["suggestive", "significant"],
      "properties": {
        "suggestive": { "type": "integer", "minimum": 1 },
        "significant": { "type": "integer", "minimum": 1 }
      }
    },
    "linking": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "strong_params": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
        "feature_link_window_secs": { "$ref": "#/$defs/secs" },
        "feature_max_hosts": { "$ref": "#/$defs/count" },
        "feature_max_share": { "$ref": "#/$defs/share" },
        "feature_share_min_records": { "$ref": "#/$defs/count" },
        "published_tags": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
        "window_secs": { "$ref": "#/$defs/secs" },
        "run_gap_secs": { "$ref": "#/$defs/secs" },
        "burst_gap_secs": { "$ref": "#/$defs/secs" },
        "burst_min": { "$ref": "#/$defs/count" },
        "pause_min_secs": { "$ref": "#/$defs/secs" },
        "archive_burst_gap_secs": { "$ref": "#/$defs/secs" },
        "archive_burst_min": { "$ref": "#/$defs/count" },
        "archive_burst_ratio": { "type": "number", "minimum": 0 },
        "seam_min_side": { "$ref": "#/$defs/count" }
      }
    },
    "chains": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "max_gap_secs": { "$ref": "#/$defs/secs" },
        "min_variants": { "$ref": "#/$defs/count" },
        "tool_pace_below_secs": { "$ref": "#/$defs/secs" },
        "model_pace_max_secs": { "$ref": "#/$defs/secs" }
      }
    },
    "fanout": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "batch_secs": { "$ref": "#/$defs/secs" },
        "min_batches": { "$ref": "#/$defs/count" },
        "min_width": { "$ref": "#/$defs/count" },
        "min_share": { "$ref": "#/$defs/share" },
        "cross_service_secs": { "$ref": "#/$defs/secs" },
        "cross_service_min": { "$ref": "#/$defs/count" },
        "cross_service_ambient": { "$ref": "#/$defs/count" }
      }
    },
    "escalation": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "window_secs": { "$ref": "#/$defs/secs" },
        "relay": { "$ref": "#/$defs/rule_refs" },
        "program": { "$ref": "#/$defs/rule_refs" },
        "remote_browser": { "$ref": "#/$defs/rule_refs" }
      }
    },
    "soft_links": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "fleet_min_lr": { "$ref": "#/$defs/ratio" },
        "fleet_density": { "type": "number", "minimum": 0 },
        "backbone_alpha": { "$ref": "#/$defs/share" },
        "backbone_overlap": { "$ref": "#/$defs/share" },
        "min_link_lr": { "$ref": "#/$defs/ratio" },
        "max_shared": { "$ref": "#/$defs/count" },
        "shared_pause": { "$ref": "#/$defs/ratio" },
        "dialect": { "$ref": "#/$defs/ratio" },
        "rerun": { "$ref": "#/$defs/ratio" },
        "preamble": { "$ref": "#/$defs/ratio" },
        "path_set": { "$ref": "#/$defs/ratio" },
        "encoding": { "$ref": "#/$defs/ratio" },
        "fanout": { "$ref": "#/$defs/ratio" },
        "cut_length": { "$ref": "#/$defs/ratio" },
        "header_set": { "$ref": "#/$defs/ratio" },
        "escalation": { "$ref": "#/$defs/ratio" },
        "common_feature": { "$ref": "#/$defs/ratio" },
        "shared_secret": { "$ref": "#/$defs/ratio" },
        "relay_stack": { "$ref": "#/$defs/ratio" },
        "callback_service": { "$ref": "#/$defs/ratio" },
        "scaffold": { "$ref": "#/$defs/ratio" },
        "co_activity": { "$ref": "#/$defs/ratio" },
        "co_target": { "$ref": "#/$defs/ratio" },
        "ordered_co_target": { "$ref": "#/$defs/ratio" },
        "scaffold_lineage": { "$ref": "#/$defs/ratio" },
        "prompt_echo": { "$ref": "#/$defs/ratio" },
        "co_group": { "$ref": "#/$defs/ratio" },
        "generator": { "$ref": "#/$defs/ratio" },
        "playbook": { "$ref": "#/$defs/ratio" },
        "campaign": { "$ref": "#/$defs/ratio" },
        "joint": {
          "type": "object",
          "description": "Caps on two families' combined likelihood ratio, at most the measured joint ratio's conservative value; never below the stronger family alone. Keys name the families in the order harness, timeline, targets, identifiers.",
          "additionalProperties": false,
          "properties": {
            "harness_timeline": { "$ref": "#/$defs/ratio" },
            "harness_targets": { "$ref": "#/$defs/ratio" },
            "harness_identifiers": { "$ref": "#/$defs/ratio" },
            "timeline_targets": { "$ref": "#/$defs/ratio" },
            "timeline_identifiers": { "$ref": "#/$defs/ratio" },
            "targets_identifiers": { "$ref": "#/$defs/ratio" }
          }
        }
      }
    },
    "scaffold": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "min_specificity": { "$ref": "#/$defs/count" },
        "min_similarity": { "$ref": "#/$defs/share" },
        "max_holders": { "$ref": "#/$defs/count" },
        "max_chance": { "$ref": "#/$defs/share" },
        "fdr": { "$ref": "#/$defs/share" }
      }
    },
    "co_activity": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "bin_secs": { "type": "integer", "minimum": 1 },
        "max_lag_bins": { "$ref": "#/$defs/count" },
        "min_active_bins": { "$ref": "#/$defs/count" },
        "min_coincidences": { "type": "number", "minimum": 0 },
        "shifts": { "type": "integer", "minimum": 1 },
        "max_chance": { "$ref": "#/$defs/share" },
        "fdr": { "$ref": "#/$defs/share" },
        "clock_tolerance_secs": { "$ref": "#/$defs/secs" },
        "clock_weight": { "$ref": "#/$defs/share" }
      }
    },
    "co_target": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "bin_secs": { "type": "integer", "minimum": 1 },
        "max_lag_bins": { "$ref": "#/$defs/count" },
        "min_hosts": { "$ref": "#/$defs/count" },
        "max_holders": { "$ref": "#/$defs/count" },
        "max_chance": { "$ref": "#/$defs/share" },
        "fdr": { "$ref": "#/$defs/share" },
        "order_min_hosts": { "$ref": "#/$defs/count" }
      }
    },
    "prompt_echo": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "min_words": { "$ref": "#/$defs/count" },
        "min_similarity": { "$ref": "#/$defs/share" },
        "max_holders": { "$ref": "#/$defs/count" },
        "max_chance": { "$ref": "#/$defs/share" },
        "fdr": { "$ref": "#/$defs/share" },
        "common_templates": { "type": "array", "items": { "type": "string", "minLength": 1 } }
      }
    },
    "biclique": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "window_secs": { "$ref": "#/$defs/secs" },
        "min_clusters": { "$ref": "#/$defs/count" },
        "min_features": { "$ref": "#/$defs/count" },
        "max_holders": { "$ref": "#/$defs/count" },
        "swaps": { "$ref": "#/$defs/count" },
        "max_chance": { "$ref": "#/$defs/share" },
        "fdr": { "$ref": "#/$defs/share" }
      }
    },
    "campaign": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "min_senders": { "$ref": "#/$defs/count" },
        "min_habits": { "$ref": "#/$defs/count" },
        "slack": { "type": "integer", "minimum": 0 },
        "core_share": { "$ref": "#/$defs/share" },
        "permutations": { "type": "integer", "minimum": 1 },
        "max_chance": { "$ref": "#/$defs/share" },
        "fdr": { "$ref": "#/$defs/share" }
      }
    },
    "generator": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "max_skew_secs": { "$ref": "#/$defs/secs" },
        "min_skew_secs": { "$ref": "#/$defs/secs" },
        "skew_tolerance_secs": { "$ref": "#/$defs/secs" },
        "counter_max_gap": { "$ref": "#/$defs/count" },
        "max_holders": { "$ref": "#/$defs/count" },
        "max_chance": { "$ref": "#/$defs/share" },
        "fdr": { "$ref": "#/$defs/share" }
      }
    },
    "playbook": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "min_specificity": { "$ref": "#/$defs/count" },
        "min_steps": { "$ref": "#/$defs/count" },
        "min_hosts": { "$ref": "#/$defs/count" },
        "max_holders": { "$ref": "#/$defs/count" },
        "gap_penalty": { "type": "number", "minimum": 0 },
        "permutations": { "type": "integer", "minimum": 1 },
        "max_order_chance": { "$ref": "#/$defs/share" },
        "max_chance": { "$ref": "#/$defs/share" },
        "fdr": { "$ref": "#/$defs/share" },
        "common_orders": {
          "type": "array",
          "items": { "type": "array", "items": { "type": "string", "minLength": 1 } }
        }
      }
    },
    "swarm": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "lockstep_tolerance_secs": { "$ref": "#/$defs/secs" },
        "lockstep_min_events": { "$ref": "#/$defs/count" },
        "lockstep_min_share": { "$ref": "#/$defs/share" },
        "lockstep_max_offset_secs": { "$ref": "#/$defs/secs" },
        "lockstep_permutations": { "type": "integer", "minimum": 1 },
        "lockstep_max_permutations": { "type": "integer", "minimum": 1 },
        "lockstep_max_chance": { "$ref": "#/$defs/share" },
        "dispatch_min_followers": { "$ref": "#/$defs/count" },
        "dispatch_pair_max_chance": { "$ref": "#/$defs/share" },
        "dispatch_lag_tolerance_bins": { "$ref": "#/$defs/count" },
        "dispatch_max_chance": { "$ref": "#/$defs/share" },
        "fdr": { "$ref": "#/$defs/share" }
      }
    },
    "onset": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "window_secs": { "type": "integer", "minimum": 1 },
        "min_clusters": { "$ref": "#/$defs/count" },
        "max_chance": { "$ref": "#/$defs/share" },
        "trigger_max_chance": { "$ref": "#/$defs/share" },
        "clock_tolerance_secs": { "$ref": "#/$defs/secs" },
        "clock_weight": { "$ref": "#/$defs/share" }
      }
    },
    "agentness": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "robots_window_secs": { "$ref": "#/$defs/secs" },
        "robots_min_pages": { "$ref": "#/$defs/count" },
        "robots_min_share": { "$ref": "#/$defs/share" },
        "robots_min_repeats": { "$ref": "#/$defs/count" },
        "robots_max_chance": { "$ref": "#/$defs/share" },
        "rate_window_secs": { "type": "integer", "minimum": 1 },
        "rate_min_cap": { "$ref": "#/$defs/count" },
        "rate_min_saturated": { "$ref": "#/$defs/count" },
        "rate_press_secs": { "$ref": "#/$defs/secs" },
        "rate_permutations": { "type": "integer", "minimum": 1 },
        "rate_max_permutations": { "type": "integer", "minimum": 1 },
        "rate_max_chance": { "$ref": "#/$defs/share" }
      }
    },
    "cut_lengths": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "min_records": { "$ref": "#/$defs/count" },
        "tolerance": { "$ref": "#/$defs/count" },
        "rule": { "$ref": "#/$defs/rule_id" }
      }
    },
    "paths": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "preamble_len": { "$ref": "#/$defs/count" },
        "min_hosts": { "$ref": "#/$defs/count" },
        "min_paths": { "$ref": "#/$defs/count" },
        "min_jaccard": { "$ref": "#/$defs/share" },
        "crawler": { "$ref": "#/$defs/paths" },
        "recon": { "$ref": "#/$defs/paths" },
        "common": { "$ref": "#/$defs/paths" }
      }
    },
    "rule": {
      "type": "array",
      "items": { "$ref": "#/$defs/rule" }
    },
    "asn": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["asn", "org"],
        "properties": {
          "asn": { "type": "integer", "minimum": 0, "maximum": 4294967295 },
          "org": { "type": "string", "minLength": 1 }
        }
      }
    },
    "benchmark": { "type": "array", "items": { "$ref": "#/$defs/known" } },
    "stale": { "type": "array", "items": { "$ref": "#/$defs/known" } }
  },
  "$defs": {
    "secs": { "type": "integer", "minimum": 0 },
    "count": { "type": "integer", "minimum": 0 },
    "share": { "type": "number", "minimum": 0, "maximum": 1 },
    "ratio": { "type": "number", "minimum": 0, "description": "A likelihood ratio; 1 or less turns a soft-link kind off." },
    "paths": { "type": "array", "items": { "type": "string", "pattern": "^/" } },
    "rule_id": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]*\\.[a-z0-9_.]+$",
      "description": "A dotted rule id, <family>.<name>."
    },
    "rule_refs": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[a-z][a-z0-9_]*\\.([a-z0-9_.]+|\\*)$",
        "description": "A rule id, or `prefix.*` for every rule whose id starts with `prefix.`."
      }
    },
    "rule": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "kind", "target", "pattern"],
      "properties": {
        "id": { "$ref": "#/$defs/rule_id" },
        "kind": {
          "enum": ["relay", "carrier", "exploit", "agent_marker", "cache_buster", "self_id", "exfil", "header", "outcome", "anti_bot", "infra"]
        },
        "target": {
          "enum": ["host", "url", "param_name", "param_value", "header", "user_agent", "final_url"]
        },
        "pattern": { "type": "string", "minLength": 1 },
        "weight": { "type": "integer", "minimum": 0, "default": 1 },
        "significant": { "type": "boolean", "default": false },
        "link": { "type": "boolean", "default": false },
        "tactic": {
          "enum": ["T1", "T2", "T3", "T4", "T5", "T6", "T7"],
          "description": "T8 and T9 are cluster-level judgments and never a rule's tactic."
        },
        "artifact": { "type": "boolean", "default": false },
        "check": { "enum": ["repeated_token", "ua_platform_mismatch"] },
        "group": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]*$",
          "description": "Independence group (SC-FM 4.11): rules of one group count once per record, at the largest weight among those that fired. Reserved; an engine that does not implement groups rejects a file that sets it."
        },
        "description": { "type": "string" }
      },
      "allOf": [
        {
          "if": { "properties": { "check": { "const": "repeated_token" } }, "required": ["check"] },
          "then": { "properties": { "target": { "const": "param_value" } } }
        },
        {
          "if": { "properties": { "check": { "const": "ua_platform_mismatch" } }, "required": ["check"] },
          "then": { "properties": { "target": { "const": "header" } } }
        },
        {
          "anyOf": [
            { "required": ["tactic"] },
            { "properties": { "artifact": { "const": true } }, "required": ["artifact"] }
          ]
        }
      ]
    },
    "known": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "pattern"],
      "properties": {
        "id": { "type": "string", "pattern": "^[a-z0-9_]+$" },
        "target": { "enum": ["url", "user_agent"], "default": "url" },
        "pattern": { "type": "string", "minLength": 1 },
        "note": { "type": "string" }
      }
    }
  }
}
