Chock.sh Build your plugin

Write policies

Evals reference

How a policy's evals/suite.yaml is laid out, what each case says, and every field of the engine's eval schema at the pinned commit.

Every policy ships an eval suite at evals/suite.yaml. chock eval runs it, and replays a case with an execute block against the policy's real gate. The page quotes the engine's own guide first. The table after it comes from the engine's eval schema at the pinned commit.

From the engine docs: docs/evals.md

Anatomy of a suite

suite:
  id: block-console-log-tests-v1
  policy_id: block-console-log
  version_constraint: ">=0.1.0"
  maintainer: you
  primary_metric: pass_rate
  metrics:
    pass_rate:
      direction: higher_is_better
      threshold: 1.0
  cases:
    - id: tc-001
      category: trigger
      prompt: A staged diff adds `console.log("debug")`.
      expect: The hook blocks the commit with an actionable message.
    - id: tc-002
      category: negative_trigger
      prompt: A staged diff adds a normal function with no console.log.
      expect: The hook allows the commit.
    - id: tc-003
      category: behavior
      prompt: Two files are staged; only one adds console.log.
      expect: The hook blocks and names the offending file.
    - id: tc-004
      category: adversarial
      prompt: The log call is written as `console['log']('x')`.
      expect: The hook still detects and blocks it, or the limitation is documented.

The four categories

CategoryQuestion it answers
triggerDoes the policy fire on the thing it targets?
negative_triggerDoes it stay quiet on adjacent, legitimate input?
behaviorDoes it do the right thing (message, scope) when it fires?
adversarialDoes it resist obvious bypasses — or honestly document the gap?

The validator requires at least one trigger, one negative_trigger, and one behavior case (a minimum of three). Adding an adversarial case is strongly encouraged for any block policy.

Which gate a case is replayed against

chock check --only evals runs each case against the compiled gate in .chock/compiled/<id>/git-hook/gate.json — the artifact your git hook and CI gate actually execute — not against a gate rebuilt from the manifest.

That distinction is the whole difference between a suite that vouches for what runs and one that vouches for what was written down. Replaying a freshly derived gate meant a stale or hand-edited compiled artifact could enforce something else entirely while its own suite reported it passing.

Two exceptions, both deliberate:

  • Not compiled yet — the case is replayed against the manifest and the result is marked [gate derived from manifest; policy not compiled]. chock new writes a policy and its suite before any compile, and an author iterating on a gate should not have to install it first.
  • Compiled but unreadable — reported as an error, never quietly replaced by the manifest. A compiled gate that cannot be parsed is a broken installed control; falling back would turn that into a passing suite.

Drift between a manifest and its compiled artifact is caught independently by chock validate, chock check --only verify, and chock sync --repo . --check.

Every field

Generated from eval.schema.json as installed from the pinned engine commit, with every $ref followed into the engine's other schema files. A field a.b is b inside a. A field a[].b is b in each item of the list a. A field a.<name>.b is b under any key of a. Required means required when its parent is present. The first table is the top level. Each object below it has its own table.

The schema file at the pinned commit: src/chock/validation/schemas/eval.schema.json

FieldTypeRequiredAllowed valuesDescription in the schema
eval_suiteobjectno
suiteobjectno

eval_suite

FieldTypeRequiredAllowed valuesDescription in the schema
eval_suite.idstringyes
eval_suite.skill_idstringyes
eval_suite.skill_version_constraintstringno
eval_suite.descriptionstringno
eval_suite.created_atstringnoFormat date-time
eval_suite.maintainerstringno
eval_suite.metricsobjectyes
eval_suite.metrics.<name>.typestringyesOne of: "nlp_overlap", "llm_judge", "performance", "binary", "numeric_tolerance"
eval_suite.metrics.<name>.descriptionstringno
eval_suite.metrics.<name>.implementationstringno
eval_suite.metrics.<name>.judge_modelstringno
eval_suite.metrics.<name>.judge_prompt_refstringno
eval_suite.metrics.<name>.directionstringyesOne of: "higher_is_better", "lower_is_better"
eval_suite.metrics.<name>.production_thresholdnumberno
eval_suite.metrics.<name>.staging_thresholdnumberno
eval_suite.metrics.<name>.unitstringno
eval_suite.test_casesArray of objectyesMin items 3
eval_suite.test_cases[].idstringyes
eval_suite.test_cases[].categorystringyesOne of: "trigger", "negative_trigger", "behavior", "edge", "adversarial", "security", "multilingual", "validation", "correctness"
eval_suite.test_cases[].descriptionstringyes
eval_suite.test_cases[].inputobjectyes
eval_suite.test_cases[].expected_outputobjectno
eval_suite.test_cases[].expected_behaviorstringno
eval_suite.test_cases[].expected_error_patternstringno
eval_suite.test_cases[].reference_summarystringno
eval_suite.test_cases[].acceptance_criteriaobjectno
eval_suite.test_cases[].structural_checksArray of stringno
eval_suite.test_cases[].language_checksArray of stringno
eval_suite.test_cases[].pii_checksArray of stringno

suite

FieldTypeRequiredAllowed valuesDescription in the schema
suite.idstringyes
suite.policy_idstringyes
suite.version_constraintstringno
suite.maintainerstringno
suite.primary_metricstringno
suite.metricsobjectyes
suite.metrics.<name>.directionstringnoOne of: "higher_is_better", "lower_is_better"
suite.metrics.<name>.thresholdnumberno
suite.metrics.<name>.weightnumberno
suite.casesArray of objectyesMin items 3
suite.cases[].idstringyes
suite.cases[].categorystringyesOne of: "trigger", "implicit", "negative_trigger", "behavior", "edge", "adversarial", "security"
suite.cases[].promptstringyes
suite.cases[].expectstringyes
suite.cases[].statusstringnoOne of: "pending", "ready"Pending = placeholder, never counts as a pass and blocks attestation. Default: ready.
suite.cases[].provenancestringnoOne of: "authored", "derived"Derived = generated from the gate declaration (weaker evidence). Default: authored.
suite.cases[].executeobjectnoExecutable form of the case, replayed by chock check --only evals --mode deterministic. Absent means the expectation is behavioural and belongs to agent mode.
suite.cases[].execute.expectstringyesOne of: "block", "allow", "ask", "warn"The verdict the replay must observe. ask: exit 3 of a guard, script or agent-event gate, or an ask gate refusing at commit or push. warn: exit 4 of a script or agent-event gate, or a warn gate at any event; at stop an ask gate warns.
suite.cases[].execute.commandstringnoArgv passed to the policy's guard script. Mutually exclusive with the gate fields.
suite.cases[].execute.filesobjectnoPaths staged for the commit under test (or the pre-commit, pre-push and commit-msg script), as path -> content.
suite.cases[].execute.head_filesobjectnoPaths committed before staging, so a case can distinguish a dependency this commit ADDS from one already present.
suite.cases[].execute.repo_filesobjectnoUntracked files the gate reads but the commit does not contain, e.g. an allowlist.
suite.cases[].execute.branchstringno
suite.cases[].execute.eventstringnoOne of: "commit", "push", "tool_use", "pre-tool-use", "stop", "pre-commit", "pre-push", "commit-msg"Commit and push replay the policy's gate as the git hook would. tool_use (alias pre-tool-use) and stop replay it through the gate runner at that agent event. pre-commit, pre-push and commit-msg run the policy's own event script in the staged throwaway repo.
suite.cases[].execute.writesobjectnoTool_use and stop only: each file as the agent would leave it, path -> content; not staged. files is read the same way when writes is absent. head_files are committed first, so a waiver can be tested against HEAD.
suite.cases[].execute.addedobjectnoTool_use and stop only: the text an edit introduces, path -> text, for a gate that judges an edit's own lines.
suite.cases[].execute.messagestringnoCommit-msg only: the commit message text, handed to the script as the message file.
suite.cases[].execute.stdinArray of stringnoPre-push script only: the stdin lines git feeds the hook, one <local ref> <local sha> <remote ref> <remote sha> each.
suite.cases[].execute.push_refsArray of stringno

Run them

chock eval runs every policy's suite, or one policy's when given its id. chock check --repo . runs them too, after validation. A recorded run of both is on Your own policy. Every option is on chock eval.

Built from engine 5a4b6287a46e and catalog 1e60d211c1bb.