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
| Category | Question it answers |
|---|---|
trigger | Does the policy fire on the thing it targets? |
negative_trigger | Does it stay quiet on adjacent, legitimate input? |
behavior | Does it do the right thing (message, scope) when it fires? |
| adversarial | Does 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 newwrites 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
| Field | Type | Required | Allowed values | Description in the schema |
|---|---|---|---|---|
eval_suite | object | no | ||
suite | object | no |
eval_suite
| Field | Type | Required | Allowed values | Description in the schema |
|---|---|---|---|---|
eval_suite.id | string | yes | ||
eval_suite.skill_id | string | yes | ||
eval_suite.skill_version_constraint | string | no | ||
eval_suite.description | string | no | ||
eval_suite.created_at | string | no | Format date-time | |
eval_suite.maintainer | string | no | ||
eval_suite.metrics | object | yes | ||
eval_suite.metrics.<name>.type | string | yes | One of: "nlp_overlap", "llm_judge", "performance", "binary", "numeric_tolerance" | |
eval_suite.metrics.<name>.description | string | no | ||
eval_suite.metrics.<name>.implementation | string | no | ||
eval_suite.metrics.<name>.judge_model | string | no | ||
eval_suite.metrics.<name>.judge_prompt_ref | string | no | ||
eval_suite.metrics.<name>.direction | string | yes | One of: "higher_is_better", "lower_is_better" | |
eval_suite.metrics.<name>.production_threshold | number | no | ||
eval_suite.metrics.<name>.staging_threshold | number | no | ||
eval_suite.metrics.<name>.unit | string | no | ||
eval_suite.test_cases | Array of object | yes | Min items 3 | |
eval_suite.test_cases[].id | string | yes | ||
eval_suite.test_cases[].category | string | yes | One of: "trigger", "negative_trigger", "behavior", "edge", "adversarial", "security", "multilingual", "validation", "correctness" | |
eval_suite.test_cases[].description | string | yes | ||
eval_suite.test_cases[].input | object | yes | ||
eval_suite.test_cases[].expected_output | object | no | ||
eval_suite.test_cases[].expected_behavior | string | no | ||
eval_suite.test_cases[].expected_error_pattern | string | no | ||
eval_suite.test_cases[].reference_summary | string | no | ||
eval_suite.test_cases[].acceptance_criteria | object | no | ||
eval_suite.test_cases[].structural_checks | Array of string | no | ||
eval_suite.test_cases[].language_checks | Array of string | no | ||
eval_suite.test_cases[].pii_checks | Array of string | no |
suite
| Field | Type | Required | Allowed values | Description in the schema |
|---|---|---|---|---|
suite.id | string | yes | ||
suite.policy_id | string | yes | ||
suite.version_constraint | string | no | ||
suite.maintainer | string | no | ||
suite.primary_metric | string | no | ||
suite.metrics | object | yes | ||
suite.metrics.<name>.direction | string | no | One of: "higher_is_better", "lower_is_better" | |
suite.metrics.<name>.threshold | number | no | ||
suite.metrics.<name>.weight | number | no | ||
suite.cases | Array of object | yes | Min items 3 | |
suite.cases[].id | string | yes | ||
suite.cases[].category | string | yes | One of: "trigger", "implicit", "negative_trigger", "behavior", "edge", "adversarial", "security" | |
suite.cases[].prompt | string | yes | ||
suite.cases[].expect | string | yes | ||
suite.cases[].status | string | no | One of: "pending", "ready" | Pending = placeholder, never counts as a pass and blocks attestation. Default: ready. |
suite.cases[].provenance | string | no | One of: "authored", "derived" | Derived = generated from the gate declaration (weaker evidence). Default: authored. |
suite.cases[].execute | object | no | Executable 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.expect | string | yes | One 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.command | string | no | Argv passed to the policy's guard script. Mutually exclusive with the gate fields. | |
suite.cases[].execute.files | object | no | Paths staged for the commit under test (or the pre-commit, pre-push and commit-msg script), as path -> content. | |
suite.cases[].execute.head_files | object | no | Paths committed before staging, so a case can distinguish a dependency this commit ADDS from one already present. | |
suite.cases[].execute.repo_files | object | no | Untracked files the gate reads but the commit does not contain, e.g. an allowlist. | |
suite.cases[].execute.branch | string | no | ||
suite.cases[].execute.event | string | no | One 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.writes | object | no | Tool_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.added | object | no | Tool_use and stop only: the text an edit introduces, path -> text, for a gate that judges an edit's own lines. | |
suite.cases[].execute.message | string | no | Commit-msg only: the commit message text, handed to the script as the message file. | |
suite.cases[].execute.stdin | Array of string | no | Pre-push script only: the stdin lines git feeds the hook, one <local ref> <local sha> <remote ref> <remote sha> each. | |
suite.cases[].execute.push_refs | Array of string | no |
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.