{
  "schemaVersion": 1,
  "updatedAt": "2026-10-15",
  "contracts": [
    {
      "harness": "claude-code",
      "variant": "current",
      "default": true,
      "decisions": ["allow", "ask", "deny"],
      "policy": {
        "support": "native",
        "subjects": ["tool", "command", "path", "network", "MCP"],
        "matching": "tool rules with tool-specific specifiers",
        "precedence": "deny, then ask, then allow; first match within the winning class"
      },
      "commandDeny": {
        "support": "native",
        "mechanism": "policy",
        "repoPath": ".claude/settings.json",
        "format": "json",
        "example": {
          "permissions": {
            "deny": ["Bash(git push:*)"]
          }
        },
        "matching": "shell-operator-aware Bash command matching with wrapper stripping and leading environment-assignment handling",
        "precedence": "deny, then ask, then allow; a matching deny survives every permission mode and cannot be overridden by a hook allow",
        "caveats": [
          "Environment runners and nested interpreters such as sh -c can hide the inner command; deny those runners too or use OS containment for a hard boundary."
        ]
      },
      "containment": {
        "support": "native",
        "dimensions": ["filesystem", "network"],
        "independentFromPolicy": true
      },
      "approval": {
        "support": "native",
        "persistence": ["once", "session", "project-command"],
        "modes": ["default", "acceptEdits", "plan", "auto", "dontAsk", "bypassPermissions"]
      },
      "skillGrant": {
        "support": "native",
        "field": "allowed-tools",
        "semantics": "turn-scoped pre-approval while the skill is active; not a tool-availability restriction"
      },
      "hookInteraction": {
        "support": "native",
        "events": ["PreToolUse", "PermissionRequest", "PermissionDenied"],
        "canDecide": ["allow", "ask", "deny"],
        "notes": [
          "A hook allow does not override matching ask or deny rules; a blocking hook takes precedence over allow rules."
        ]
      },
      "configuration": [
        {
          "id": "managed",
          "scope": "managed",
          "path": "managed settings",
          "order": 1
        },
        {
          "id": "command-line",
          "scope": "session",
          "path": "CLI flags",
          "order": 2
        },
        {
          "id": "local",
          "scope": "project-local",
          "path": ".claude/settings.local.json",
          "order": 3
        },
        {
          "id": "project",
          "scope": "project",
          "path": ".claude/settings.json",
          "order": 4
        },
        {
          "id": "user",
          "scope": "user",
          "path": "~/.claude/settings.json",
          "order": 5
        }
      ],
      "orderStatus": "documented-policy-precedence",
      "references": {
        "policy": "https://code.claude.com/docs/en/permissions#manage-permissions",
        "containment": "https://code.claude.com/docs/en/permissions#how-permissions-interact-with-sandboxing",
        "approval": "https://code.claude.com/docs/en/permissions#permission-modes",
        "skillGrant": "https://code.claude.com/docs/en/permissions#tool-specific-permission-rules",
        "hookInteraction": "https://code.claude.com/docs/en/permissions#extend-permissions-with-hooks"
      },
      "evidence": ["claude-permissions-docs"]
    },
    {
      "harness": "codex",
      "variant": "current",
      "default": true,
      "decisions": ["allow", "ask", "deny"],
      "policy": {
        "support": "native",
        "subjects": [
          "sandbox escalation",
          "command rules",
          "MCP elicitation",
          "permission request",
          "skill approval"
        ],
        "matching": "approval_policy plus command rules"
      },
      "commandDeny": {
        "support": "native",
        "mechanism": "execpolicy",
        "repoPath": ".codex/rules/deny-push.rules",
        "format": "starlark",
        "example": "prefix_rule(\n    pattern = [\"git\", \"push\"],\n    decision = \"forbidden\",\n    justification = \"Pushing is a human decision\",\n    match = [\"git push\", [\"git\", \"push\", \"origin\", \"main\"]],\n)",
        "matching": "ordered argv-token prefix matching; .rules files are auto-discovered below the rules directory of each configuration layer",
        "precedence": "forbidden is stricter than prompt and allow and cannot be approved interactively",
        "caveats": [
          "Command rules belong in .codex/rules/*.rules, not config.toml.",
          "--ignore-rules skips user and project rule files; use a managed layer or OS containment when the invoking user must not be able to bypass the rule.",
          "Wrappers and nested interpreters require separate coverage unless Codex's nested-command parser recognizes the invocation."
        ]
      },
      "containment": {
        "support": "native",
        "dimensions": ["filesystem", "network"],
        "modes": ["read-only", "workspace-write", "danger-full-access"],
        "independentFromPolicy": true
      },
      "approval": {
        "support": "native",
        "modes": ["untrusted", "on-request", "never"],
        "notes": ["approval_policy and sandbox_mode are separate inputs"]
      },
      "skillGrant": {
        "support": "unsupported",
        "notes": [
          "Codex does not interpret SKILL.md allowed-tools as a skill-scoped permission grant."
        ]
      },
      "hookInteraction": {
        "support": "native",
        "events": ["PreToolUse", "PermissionRequest"],
        "canDecide": ["allow", "deny"],
        "notes": [
          "PreToolUse can only deny; PermissionRequest can allow or deny, and any deny wins.",
          "Hook-definition trust and tool approval are separate controls."
        ]
      },
      "configuration": [
        {
          "id": "user",
          "scope": "user",
          "path": "~/.codex/config.toml"
        },
        {
          "id": "project",
          "scope": "project",
          "path": ".codex/config.toml"
        },
        {
          "id": "command-line",
          "scope": "session",
          "path": "CLI flags and -c overrides"
        }
      ],
      "orderStatus": "see-configuration-reference",
      "references": {
        "policy": "https://learn.chatgpt.com/docs/config-file/config-reference#configtoml",
        "containment": "https://learn.chatgpt.com/docs/config-file/config-reference#configtoml",
        "approval": "https://learn.chatgpt.com/docs/config-file/config-reference#configtoml",
        "skillGrant": "https://learn.chatgpt.com/docs/build-skills#create-a-skill",
        "hookInteraction": "https://learn.chatgpt.com/docs/hooks#permissionrequest"
      },
      "evidence": [
        "codex-config-docs",
        "codex-skills-docs",
        "codex-hooks-docs",
        "codex-execpolicy-source"
      ]
    },
    {
      "harness": "cursor-agent",
      "variant": "cli-current",
      "default": true,
      "decisions": ["allow", "ask", "deny"],
      "policy": {
        "support": "native",
        "subjects": ["shell", "read", "write", "web fetch", "MCP"],
        "matching": "permission tokens with patterns"
      },
      "commandDeny": {
        "support": "native",
        "mechanism": "policy",
        "repoPath": ".cursor/cli.json",
        "format": "json",
        "example": {
          "permissions": {
            "deny": ["Shell(git:push*)"]
          }
        },
        "matching": "Shell(commandBase) with an optional command:args glob; exact argument-string behavior is not fully documented",
        "precedence": "deny takes precedence over allow and survives --force/--yolo",
        "confidence": "qualified",
        "caveats": [
          "Shell(git) is the unambiguous declarative form but blocks every git command.",
          "For exact git-push matching, use a beforeShellExecution hook that returns permission deny or exits 2; other non-zero hook exits fail open."
        ]
      },
      "containment": {
        "support": "unknown"
      },
      "approval": {
        "support": "unknown"
      },
      "skillGrant": {
        "support": "unknown"
      },
      "hookInteraction": {
        "support": "native",
        "events": ["preToolUse", "beforeShellExecution", "beforeMCPExecution", "beforeReadFile"],
        "canDecide": ["allow", "deny", "ask"],
        "notes": [
          "Hook permission decisions are a separate surface from CLI permission files.",
          "ask is not enforced for preToolUse."
        ]
      },
      "configuration": [
        {
          "id": "global-cli",
          "scope": "user",
          "path": "~/.cursor/cli-config.json",
          "order": 2
        },
        {
          "id": "project-cli",
          "scope": "project",
          "path": "<project>/.cursor/cli.json",
          "order": 1
        }
      ],
      "orderStatus": "project-over-global",
      "references": {
        "policy": "https://cursor.com/docs/cli/reference/permissions#configuration",
        "hookInteraction": "https://cursor.com/docs/hooks#beforeshellexecution-beforemcpexecution"
      },
      "evidence": ["cursor-permissions-docs", "cursor-hooks-docs"]
    },
    {
      "harness": "opencode",
      "variant": "v1-stable",
      "default": false,
      "decisions": ["allow", "ask", "deny"],
      "policy": {
        "support": "native",
        "subjects": ["tool", "command", "path", "external directory", "skill", "subagent"],
        "matching": "ordered wildcard rules",
        "precedence": "last matching rule wins; per-agent rules can override global rules"
      },
      "commandDeny": {
        "support": "native",
        "mechanism": "policy",
        "repoPath": "opencode.json",
        "format": "json",
        "example": {
          "$schema": "https://opencode.ai/config.json",
          "permission": {
            "bash": {
              "*": "ask",
              "git push": "deny",
              "git push *": "deny"
            }
          }
        },
        "matching": "simple glob matching against parsed commands; a literal space before * means bare git push needs its own rule",
        "precedence": "last matching rule wins; agent-level permissions override global configuration",
        "caveats": [
          "Keep broad defaults before specific denies; a later broad allow can re-allow the command."
        ]
      },
      "containment": {
        "support": "unknown",
        "notes": [
          "external_directory is permission policy; it is not asserted here as an OS sandbox"
        ]
      },
      "approval": {
        "support": "native",
        "persistence": ["once", "session"],
        "modes": ["normal", "auto"]
      },
      "skillGrant": {
        "support": "unsupported",
        "notes": [
          "Skill access is controlled externally by the skill permission; SKILL.md does not grant tools."
        ]
      },
      "hookInteraction": {
        "support": "native",
        "events": ["tool.execute.before"],
        "canDecide": ["deny"],
        "notes": [
          "Throwing from tool.execute.before blocks a tool call.",
          "permission.ask is declared in the 1.x plugin API but not called in 1.19.0, and the permission.asked and permission.replied events can only observe.",
          "OpenCode 2.x adds a permission.evaluate hook that can allow, deny or ask."
        ]
      },
      "configuration": [
        {
          "id": "global",
          "scope": "user",
          "path": "~/.config/opencode/opencode.json"
        },
        {
          "id": "project",
          "scope": "project",
          "path": "opencode.json"
        },
        {
          "id": "agent",
          "scope": "agent",
          "path": "agent permission configuration"
        }
      ],
      "orderStatus": "configuration-precedence-then-global-before-agent",
      "references": {
        "policy": "https://opencode.ai/docs/permissions/#actions",
        "approval": "https://opencode.ai/docs/permissions/#auto-mode",
        "skillGrant": "https://opencode.ai/docs/permissions/#available-permissions",
        "hookInteraction": "https://opencode.ai/docs/plugins/#events"
      },
      "evidence": ["opencode-permissions-docs", "opencode-plugins-docs"]
    },
    {
      "harness": "pi",
      "variant": "current",
      "default": true,
      "decisions": ["unsupported"],
      "policy": {
        "support": "unsupported",
        "notes": [
          "Stock pi has no built-in tool permission model; extensions run with the user's authority."
        ]
      },
      "commandDeny": {
        "support": "adapter",
        "mechanism": "extension",
        "repoPath": ".pi/extensions/block-git-push.ts",
        "format": "typescript",
        "example": "import type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { isToolCallEventType } from \"@earendil-works/pi-coding-agent\";\n\nexport default function (pi: ExtensionAPI) {\n  pi.on(\"tool_call\", async (event) => {\n    if (isToolCallEventType(\"bash\", event) && /(^|[;&|]\\s*)git\\s+push\\b/.test(event.input.command)) {\n      return { block: true, reason: \"git push is blocked by policy\" };\n    }\n  });\n}",
        "matching": "extension-defined string matching over bash tool input",
        "precedence": "no native policy precedence; extension ordering and later input mutation matter",
        "caveats": [
          "Stock pi has no declarative deny rule or built-in sandbox.",
          "The extension owns shell parsing and must explicitly cover wrappers, interpreters, aliases, and scripts.",
          "Project extensions load only after project trust."
        ]
      },
      "containment": {
        "support": "unsupported",
        "notes": ["No built-in execution sandbox is claimed."]
      },
      "approval": {
        "support": "unsupported",
        "notes": ["An extension must implement its own approval UI and policy."]
      },
      "skillGrant": {
        "support": "unsupported"
      },
      "hookInteraction": {
        "support": "adapter",
        "events": ["tool_call", "tool_result"],
        "notes": [
          "An extension can implement a permission gate, but it must emit its own evidence."
        ]
      },
      "configuration": [],
      "orderStatus": "not-applicable",
      "references": {
        "policy": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/security.md#choose-how-to-run-pi",
        "hookInteraction": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/extensions/types.ts#L1661"
      },
      "evidence": ["pi-extensions-docs"]
    },
    {
      "harness": "opencode",
      "variant": "2.x",
      "default": true,
      "observedVersion": "2.0.29",
      "verifiedAt": "2026-10-15",
      "decisions": ["allow", "ask", "deny"],
      "orderStatus": "last-matching-configured-rule; configured-deny-before-saved-approval-and-hook",
      "policy": {
        "support": "native",
        "subjects": ["tool", "command", "path", "external directory", "skill", "subagent", "MCP"],
        "matching": "whole-value wildcards over action and resource",
        "precedence": "last configured match wins; agent rules append after global rules; configured deny survives saved approvals and permission hooks"
      },
      "commandDeny": {
        "support": "native",
        "mechanism": "policy",
        "repoPath": "opencode.jsonc",
        "format": "jsonc",
        "example": {
          "$schema": "https://opencode.ai/config.json",
          "permissions": [
            {
              "action": "shell",
              "resource": "*",
              "effect": "ask"
            },
            {
              "action": "shell",
              "resource": "git push *",
              "effect": "deny"
            }
          ]
        },
        "matching": "shell resources ending in space-star also match the command without arguments",
        "precedence": "last configured match wins; configured deny is evaluated before saved approvals and hooks",
        "caveats": [
          "Shell directory inference is best effort and shell runs with host authority.",
          "A later configured broad allow can override an earlier configured deny; saved approvals cannot."
        ]
      },
      "containment": {
        "support": "unknown",
        "notes": ["external_directory is permission policy, not proof of OS containment."]
      },
      "approval": {
        "support": "native",
        "persistence": ["once", "project"],
        "notes": [
          "Allow always stores durable project-scoped proposed patterns; reject declines all pending requests in the session."
        ]
      },
      "skillGrant": {
        "support": "unsupported",
        "notes": [
          "The skill permission controls loading; skill frontmatter does not grant tool access."
        ]
      },
      "hookInteraction": {
        "support": "native",
        "events": ["permission.evaluate"],
        "canDecide": ["allow", "ask", "deny"],
        "notes": [
          "The hook runs after configured-deny evaluation, so it cannot override a configured deny."
        ]
      },
      "configuration": [
        {
          "id": "global",
          "scope": "user",
          "path": "~/.config/opencode/opencode.json(c)"
        },
        {
          "id": "project",
          "scope": "project",
          "path": "opencode.json(c) and .opencode/opencode.json(c)"
        },
        {
          "id": "agent",
          "scope": "agent",
          "path": "agents.<id>.permissions"
        }
      ],
      "references": {
        "policy": "https://github.com/anomalyco/opencode/blob/042d0832f5f3a73eaa039a58a7d565776f6d4290/packages/core/src/permission.ts#L90-L100",
        "approval": "https://github.com/anomalyco/opencode/blob/042d0832f5f3a73eaa039a58a7d565776f6d4290/services/www/src/docs/content/permissions.mdx#L8-L270",
        "skillGrant": "https://github.com/anomalyco/opencode/blob/042d0832f5f3a73eaa039a58a7d565776f6d4290/packages/core/src/config/plugin/skill-file.ts#L9-L63",
        "hookInteraction": "https://github.com/anomalyco/opencode/blob/042d0832f5f3a73eaa039a58a7d565776f6d4290/packages/core/src/permission.ts#L164-L195"
      },
      "evidence": [
        "refresh-opencode-2.0.29-permission",
        "refresh-opencode-2.0.29-permission-flow",
        "refresh-opencode-2.0.29-permission-docs",
        "refresh-opencode-2.0.29-skill-file"
      ]
    }
  ]
}
