CANP-WP-0002 T04: eval schema, render checks and output criteria

`evals/` was a reserved path holding unvalidated blobs: section 12 named the
directory and gave an illustrative snippet, but nothing was specified, so no
tool could act on an eval file.

Every eval file must now declare a `schema`, and CPF defines exactly one —
`canned-prompts/eval-rubric/v0.1`. Unrecognized schemas stay legal and are
skipped rather than rejected, so the format gains something actionable without
becoming an evaluation language, which remains a non-goal.

The schema splits along the same seam as T01 and T03. Render checks
(`contains`, `not_contains`, `resolves_all`) assert properties of the rendered
prompt text, need no model, and are therefore run by the reference CLI. Output
criteria describe a good result and are declared but not run, because judging
them requires a model. That division is now the format's consistent answer to
"deterministic locally, or not".

An eval references a fixture already declared in the manifest's `examples`
rather than carrying its own copy, so an example that is also an eval fixture
stays honest — both break together.

An eval declares assessment and must not record outcomes. Results are run
evidence and live outside the immutable package, per INTENT.md and section 17.

Spec: 12 rewritten with 12.1, 18 (rules 17-18), 21 (`eval` verb).

Reference CLI: read_eval, validate_eval, load_example_values,
run_render_checks, cmd_eval; a failed render check exits non-zero.
Tests 42 -> 51.

examples/pqrst-estimate/evals/quality.yaml is a real eval with four render
checks and four output criteria, and it passes.

Fixes a latent bug reaching a fixture exposed: coerce_value assumed every
value was a command-line string, so a YAML fixture carrying a real type
(include_rationale: true) crashed on .lower(). Typed values are now validated
but not re-parsed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 388925@bnt-lap001
Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
This commit is contained in:
tegwick 2026-09-06 01:38:11 +02:00
parent cac6bc135f
commit c5f1640454
8 changed files with 444 additions and 18 deletions

View file

@ -678,20 +678,73 @@ Tools MAY render examples directly.
`evals` is a list of relative paths to evaluation specifications.
CPF v0.1 deliberately does not standardize a universal evaluation language. Eval files SHOULD therefore declare their own evaluator or schema.
CPF does not standardize a universal evaluation language, which remains a
non-goal. Every eval file MUST declare a `schema`, and a consumer MUST ignore a
schema it does not recognize rather than guessing at its meaning.
Example:
CPF defines exactly one schema, so that `evals/` holds something tools can act
on rather than opaque blobs. Other schemas remain legal and are simply not
interpreted here.
### 12.1 `canned-prompts/eval-rubric/v0.1`
```yaml
schema: canned-prompts/eval-rubric/v0.1
name: code-review-quality
criteria:
- identifies correctness risks
- distinguishes blocking from advisory findings
- avoids inventing repository facts
name: pqrst-estimate-quality
description: The estimate reads as a post-session audit, not a plan.
example: examples/basic.yaml
render:
- contains: "must sum to exactly 100%"
- not_contains: "{{"
- resolves_all: true
output:
criteria:
- Percentages sum to exactly 100%.
- Categories are not silently renamed or merged.
- Rationale cites evidence from the session summary.
```
A registry may associate externally collected run/eval evidence with `<id>@<version>` without mutating the package.
| Field | Required | Meaning |
|---|---:|---|
| `schema` | yes | MUST be `canned-prompts/eval-rubric/v0.1` |
| `name` | yes | Identifies this eval within the package |
| `description` | no | What the eval is for |
| `example` | no | Fixture to run against; MUST be a path declared in the manifest's `examples` |
| `render` | no | Deterministic checks on the **rendered prompt** |
| `output` | no | Model-judged criteria for the **result** |
An eval declaring neither `render` nor `output` asserts nothing and SHOULD be
rejected.
#### Render checks
Render checks assert properties of the rendered prompt text. They need no
model, so **any implementation that can render can run them.**
| Check | Passes when |
|---|---|
| `contains: <text>` | the rendered prompt contains that text |
| `not_contains: <text>` | it does not |
| `resolves_all: true` | every placeholder resolved to a value |
Each entry is a single-key mapping, so a check may appear more than once.
#### Output criteria
`output.criteria` is a list of statements about a good result. Judging them
requires running the prompt and assessing what comes back, which CPF does not
specify and most consumers cannot do. They are **declared, not run** — the
same division as `include` and `derive` in § 10.2, and for the same reason.
#### Results are not part of the package
An eval file declares what to assess; it MUST NOT record outcomes. Results are
run evidence, which lives outside the immutable package (§ 17) and may be
associated with `<id>@<version>` by a registry or evaluation system without
mutating it. A tool reporting results SHOULD identify the package version, the
eval `name`, and each check's outcome.
## 13. Provenance and lineage
@ -786,7 +839,12 @@ A v0.1 validator SHOULD verify at least:
15. every dependency referenced by a composition or derived default declares a
`version`, and that version is a semver literal, `any`, `newest`, or a
`>=` lower bound (§ 10.1);
16. inclusion does not form a cycle.
16. inclusion does not form a cycle;
17. every eval file parses and declares a `schema`;
18. an eval declaring `canned-prompts/eval-rubric/v0.1` has a `name`, asserts
something via `render` or `output`, uses only known render checks, and —
when it declares an `example` — names a path listed in the manifest's
`examples`.
A validator SHOULD additionally warn when a package intended for publication
declares an inline derivation prompt (§ 6.1).
@ -960,6 +1018,13 @@ The reference tool satisfies `include` defaults, because inclusion is
deterministic and needs no model, and reports `derive` defaults it cannot
satisfy.
```text
eval ID run an installed package's render checks
```
`eval` runs the deterministic render checks of every recognized eval file and
reports output criteria as declared but not run.
The reference tool never calls a model, so its `resolve` handles supplied
values and static defaults only and reports any input whose derived default it
cannot satisfy. `render` performs the same resolution and then substitutes;