{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "RulePackDef",
  "description": "A declarative rule pack loaded from a standalone JSON or JSONC file listed\nin the `rulePacks` config key.\n\nRule packs are pure data: loading a pack never executes project code. They\nencode project-specific policy (banned calls, banned imports, and\ncatalogue-backed banned effects) evaluated over fallow's static extraction\ndata, reporting as `policy-violation`\nfindings.\n\n```jsonc\n{\n  \"$schema\": \"https://raw.githubusercontent.com/fallow-rs/fallow/main/rule-pack-schema.json\",\n  \"version\": 1,\n  \"name\": \"team-policy\",\n  \"description\": \"House rules for the platform team\",\n  \"rules\": [\n    {\n      \"id\": \"no-child-process\",\n      \"kind\": \"banned-call\",\n      \"callees\": [\"child_process.*\"],\n      \"message\": \"Use the sandboxed runner instead.\",\n      \"severity\": \"error\"\n    },\n    {\n      \"id\": \"no-network\",\n      \"kind\": \"banned-effect\",\n      \"effects\": [\"network\"],\n      \"message\": \"Keep this package side-effect free.\"\n    },\n    {\n      \"id\": \"no-moment\",\n      \"kind\": \"banned-import\",\n      \"specifiers\": [\"moment\"],\n      \"message\": \"Use date-fns.\"\n    }\n  ]\n}\n```",
  "type": "object",
  "properties": {
    "version": {
      "description": "Pack format version. Must be `1`; the field exists so future rule\nkinds can be added without breaking older fallow builds silently.",
      "type": "integer",
      "format": "uint32",
      "minimum": 0
    },
    "name": {
      "description": "Pack name, unique across all loaded packs. Must use only ASCII\nletters, digits, `.`, `_`, and `-` so `\"<pack>/<id>\"` is unambiguous in\noutput, baselines, and scoped suppression comments.",
      "type": "string"
    },
    "description": {
      "description": "Optional human description of the pack's intent.",
      "type": [
        "string",
        "null"
      ]
    },
    "rules": {
      "description": "The policy rules this pack enforces. Must be non-empty: an empty pack\nwould silently enforce nothing.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/RulePackRule"
      }
    }
  },
  "additionalProperties": false,
  "required": [
    "version",
    "name",
    "rules"
  ],
  "$defs": {
    "RulePackRule": {
      "description": "One declarative policy rule inside a rule pack.\n\n`callees` applies only to `banned-call` rules; `specifiers` and\n`ignoreTypeOnly` apply only to `banned-import` rules; `effects` applies\nonly to `banned-effect` rules; `exports` applies only to `banned-export`\nrules. `allowedFiles` and `proofKinds` apply only to `gdp-proof-producer`\nrules. `zones` can scope any rule kind to files classified into one of the\nnamed boundary zones. Setting a field on the wrong kind is a load error\n(fail loud, never silently ignore policy).",
      "type": "object",
      "properties": {
        "id": {
          "description": "Rule id, unique within the pack. Must use only ASCII letters, digits,\n`.`, `_`, and `-` so `\"<pack>/<id>\"` is unambiguous in output,\nbaselines, and scoped suppression comments.",
          "type": "string"
        },
        "kind": {
          "description": "Which check this rule performs.",
          "$ref": "#/$defs/RulePackRuleKind"
        },
        "callees": {
          "description": "Callee patterns to ban (`banned-call` only). Matching is segment-aware\nand import-resolved, identical to `boundaries.calls.forbidden`:\n`child_process.*` covers `import { exec } from \"node:child_process\"`,\nthe bare specifier, and namespace/default imports; `fetch` matches only\nthe global `fetch`; a leading `*.member` matches any object.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "specifiers": {
          "description": "Import specifiers to ban (`banned-import` only). Matched segment-aware\nagainst the RAW specifier: `moment` covers `moment` and\n`moment/locale/nl` but not `moment-timezone`. A trailing `/*` form,\nsuch as `@org/ui/*`, matches subpaths only (`@org/ui/internal`) and\nnot the package root (`@org/ui`). Aliased or rewritten specifiers\n(e.g. `npm:moment`) are not matched.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "effects": {
          "description": "Effect classes to ban (`banned-effect` only). Effects are derived from\n`security_matchers.toml` catalogue rows and matched against captured\ncall sites after import-resolution canonicalization.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/EffectKind"
          }
        },
        "exports": {
          "description": "Export names to ban (`banned-export` only). `\"default\"` matches the\ndefault export; any other entry matches an exported name exactly; a\nsingle trailing `*` makes it a prefix match (`internal*`). No other\nglob syntax is supported. Re-exports are out of scope for this rule.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "allowedFiles": {
          "description": "Project-root-relative globs allowed to call `@gdp-ts/core.defineProof`\n(`gdp-proof-producer` only). At least one pattern is required.\nA single `*` stays within one path segment; use `**` to match across directories.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "proofKinds": {
          "description": "Exact literal proof labels checked by a `gdp-proof-producer` rule.\nEmpty or absent checks every recognized factory call, including calls\nwith dynamic labels. A nonempty list checks only static string labels.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "ignoreTypeOnly": {
          "description": "When `true`, type-only imports (`import type ...` and type-only\nre-exports) are ignored by `banned-import`; type-only exports are\nignored by `banned-export`. Defaults to `false`: type-only sites are\nflagged too.",
          "type": "boolean"
        },
        "files": {
          "description": "Optional include globs (project-root-relative). Empty or absent means\nthe rule applies to every analyzed file.\nA single `*` stays within one path segment; use `**` to match across directories.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "exclude": {
          "description": "Optional exclude globs (project-root-relative), applied after `files`.\nA single `*` stays within one path segment; use `**` to match across directories.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "zones": {
          "description": "Optional boundary zones this rule applies to. Empty or absent means the\nrule applies regardless of zone; non-empty values require matching\nconfigured boundaries and combine with `files`/`exclude` as AND.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "message": {
          "description": "Author-provided message naming the sanctioned alternative. Rendered\nnext to each finding.",
          "type": [
            "string",
            "null"
          ]
        },
        "severity": {
          "description": "Per-rule severity overriding the `rules.\"policy-violation\"` master.\n`off` disables this rule. When the master itself is `off`, the whole\nevaluator is disabled and per-rule severity cannot resurrect it.",
          "anyOf": [
            {
              "$ref": "#/$defs/Severity"
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "additionalProperties": false,
      "required": [
        "id",
        "kind"
      ]
    },
    "RulePackRuleKind": {
      "description": "Which check a rule-pack rule performs.",
      "oneOf": [
        {
          "description": "Ban call sites whose callee path matches one of `callees`.",
          "type": "string",
          "const": "banned-call"
        },
        {
          "description": "Ban imports and re-exports whose raw specifier matches one of\n`specifiers`.",
          "type": "string",
          "const": "banned-import"
        },
        {
          "description": "Ban call sites whose catalogue-derived effect matches one of `effects`.",
          "type": "string",
          "const": "banned-effect"
        },
        {
          "description": "Ban exported names that match one of `exports`.",
          "type": "string",
          "const": "banned-export"
        },
        {
          "description": "Restrict gdp-ts proof factory calls to explicitly allowed modules.",
          "type": "string",
          "const": "gdp-proof-producer"
        }
      ]
    },
    "EffectKind": {
      "description": "Internal side-effect taxonomy derived from security catalogue rows.",
      "oneOf": [
        {
          "description": "No observable side effect.",
          "type": "string",
          "const": "pure"
        },
        {
          "description": "Structured-data read/query evaluation (XML external entities, XPath).",
          "type": "string",
          "const": "read"
        },
        {
          "description": "In-memory state mutation (mass assignment, prototype pollution).",
          "type": "string",
          "const": "write"
        },
        {
          "description": "Network I/O (HTTP clients, request-forgery sinks).",
          "type": "string",
          "const": "network"
        },
        {
          "description": "Filesystem access (path traversal, file permission changes).",
          "type": "string",
          "const": "storage"
        },
        {
          "description": "In-process code evaluation (`eval`, `Function`, string timers).",
          "type": "string",
          "const": "process"
        },
        {
          "description": "OS shell command execution.",
          "type": "string",
          "const": "shell"
        },
        {
          "description": "Cryptographic primitives (weak algorithm or key usage).",
          "type": "string",
          "const": "crypto"
        },
        {
          "description": "Random-number generation in security-sensitive contexts.",
          "type": "string",
          "const": "randomness"
        },
        {
          "description": "DOM injection (`innerHTML`-style XSS sinks).",
          "type": "string",
          "const": "dom"
        },
        {
          "description": "Database query execution (SQL/NoSQL injection sinks).",
          "type": "string",
          "const": "database"
        },
        {
          "description": "Callback invoked by a framework rather than a direct effect.",
          "type": "string",
          "const": "framework-callback"
        },
        {
          "description": "Effect the catalogue cannot classify.",
          "type": "string",
          "const": "unknown"
        }
      ]
    },
    "Severity": {
      "description": "Severity level for rules.\n\nControls whether an issue type sets a non-zero exit code (`error`), is\nreported without an effect on the exit code (`warn`), or is suppressed\nentirely (`off`). `--fail-on-issues` promotes `warn` to `error`. The\ndead-code count gate of the GitHub Action counts `warn` findings unless its\n`fail-on-issues` input is `false`.",
      "oneOf": [
        {
          "description": "Report and set a non-zero exit code.",
          "type": "string",
          "const": "error"
        },
        {
          "description": "Report without an effect on the exit code.",
          "type": "string",
          "const": "warn"
        },
        {
          "description": "Don't detect or report.",
          "type": "string",
          "const": "off"
        }
      ]
    }
  }
}
