EVALUATION_PROTOCOL
Jev Gate Protocol — Stage 1 mechanical checks (code) → Stage 2 one Jev request of row-level questions, counted by code (ambiguity rubric + three panel seats) → verdict bound to the spec hash → lock...
Verifies whether a Seed spec is of implementation-ready quality in two stages. Stage 1 is code. Stage 2 is one request to Jev (TypeSafe's judgment-only model). The authoring model never scores its own spec — it writes, Jev judges, the agent explains.
Stage 1 jev_judge.py check (M-01..M-10, deterministic)
│ PASS
Stage 2 jev_judge.py judge --set ambiguity,panel (row-level noul questions, one request, counted by code)
│ PASS
Gate PASS → stamp (model · date · spec_hash) → --lock | user confirmation → LOCKED
Any stage that is FAIL or UNVERIFIED stops the gate. UNVERIFIED is never a pass.
Stage 1: Mechanical Verification — jev_judge.py check <spec>#
Structural and format completeness, computed by the parser (no LLM reads the checklist).
| ID | Inspection Item | Pass Criteria |
|---|---|---|
| M-01 | Header parses | # Seed: {name} | vX.Y.Z | Status: {…} on the first lines |
| M-02 | Status | DRAFT or LOCKED |
| M-03 | Core problem | S1 has a non-empty statement (blockquote lines excluded) |
| M-04 | Immutable constraints | S2 table has ≥ 1 row; every row has a C-NN id and a non-empty rationale |
| M-05 | Domain entities | S3 has ≥ 1 ### Entity with Attributes + Relationships + Invariants |
| M-06 | Must boundaries |
S4 has ≥ 1
Must
row; every Must row has an
AC-NN
id, a criterion, and a Status in
PENDING/IN-PROGRESS/MET/FAILED/WAIVED
; no row has a Type outside Must/Must-Not/May
|
| M-07 | Must-Not boundaries | S4 has ≥ 1 Must-Not row with the same row rules |
| M-08 | Exposed assumptions | S5 has ≥ 3 rows |
| M-09 | Metadata |
Created
,
Author
,
Locked
,
Ambiguity Score
fields present
|
| M-10 | Version | SemVer vX.Y.Z |
Verdict#
- PASS: all 10 items PASS
-
FAIL: ≥ 1 item FAIL → the report lists each failed item with its
detail; the agent writes remediation -
UNVERIFIED: a section heading exists but its table cannot be parsed (
_unparseable) → blocks exactly like FAIL. Never silently score an unparseable table as passed — that is the mechanical fail-open (eval G1).
Per-item legend: ✅ PASS / ❌ FAIL / ⚠️ UNVERIFIED.
Stage 2: Jev Judgment — jev_judge.py judge --set ambiguity,panel --spec <spec>#
One request. Jev evaluates every question in parallel against the same state.spec, so a few dozen
row questions cost about the same as one (one Seed spec ≈ 20–40 questions, under one cent). Jev judges
one row at a time; the script counts. Question texts are the SoT in
skills/jev-gate/questions/{ambiguity,panel}.json; the numbers below mirror
config/thresholds.yaml.
2a. Ambiguity — row questions, counted into the rubric#
| Row question | Over | Flagged when |
|---|---|---|
vague::AC-xx / c_vague::C-xx |
every S4 row / every S2 row | p > 0.5 |
unclear_ref::AC-xx | every S4 row | p > 0.5 |
observable::AC-xx | Must / Must-Not rows | p ≤ 0.5 |
covered::{Entity} | every S3 entity | p ≤ 0.5 |
conflict::C-xx::AC-yy |
every S2 × Must/Must-Not pair (≤ 200) | p > 0.5 |
has_failure_case | once | p ≤ 0.5 |
Counts become rubric levels 0–4 (references/AMBIGUITY_RUBRIC.md), weighted 0.25 / 0.20 / 0.20 / 0.20 / 0.15:
ambiguity_score = Σ weight × (level / 4)
| Condition | Grade | Verdict |
|---|---|---|
| score ≤ 0.2 and no flagged Must/Must-Not row and no contradiction | CLEAR | PASS |
| score ≤ 0.3, or a flagged Must/Must-Not row, a contradiction, an uncovered entity, or no failure / invalid-input / limit case | WARNING | CONDITIONAL — fix the listed rows, re-judge (≤ verify.max_fix_rounds) |
| 0.3 – 0.5 | AMBIGUOUS | FAIL |
| > 0.5 | UNCLEAR | FAIL — rewrite |
| any row question unanswered or malformed, no Must/Must-Not rows, or an unparseable table | — | UNVERIFIED |
Rows answered between 0.25 and 0.75 are listed as uncertain_rows — a person reads them.
2b. Panel — three seats computed from atomic judgments#
No role play: a persona asked to "approve as an architect" could not tell a clear spec from a vague one (every calibration fixture failed). Each seat is computed in code from narrow questions:
| Seat | Reads | APPROVE when every value clears 0.75 | REJECT when |
|---|---|---|---|
| analyst | observable::* (from 2a) |
every Must/Must-Not states a trigger and an observable result | half or more do not |
| pm | s1_problem, in_scope::AC-xx (Must rows) |
S1 is a problem of a specific group, not a feature; every Must is in scope | S1 ≤ 0.25 and half or more of the Musts are out of scope |
| architect | entity_ready::{Entity}, conflict::* (from 2a) |
every entity can define a type; every pair ≤ 0.25 | any pair > 0.5 (a contradiction) |
Otherwise the seat is REQUEST_CHANGES — including a seat with nothing to judge (no Must rows, no entities): an empty list is not an approval. A seat input without an answer makes the set
UNVERIFIED —
an empty seat is never filled with APPROVE. The panel needs 2a in the same request (--set ambiguity,panel).
blocking = REQUEST_CHANGES + REJECT
| Condition | Verdict |
|---|---|
| blocking = 0 | PASS |
| blocking = 1 and no REJECT | CONDITIONAL |
| otherwise | FAIL |
This panel is a cross-backend judgment: Jev is not the model that wrote the spec, and the seat rules are code, so the verdict is reproducible for the same answers.
Fail-closed rules (all stages)#
| Situation | Result |
|---|---|
TYPESAFE_API_KEY unset | exit 3 · UNVERIFIED · cannot lock |
| HTTP 401 / 422 / other, or 429 / 529 after 3 retries | UNVERIFIED |
| any question without an answer, or with a malformed one (probability outside 0–1, a string or bool, an option not offered) | its set is UNVERIFIED |
| no set produced a verdict | UNVERIFIED |
| empty or duplicate row id / entity name | UNVERIFIED — fix the spec |
state over --max-state-chars (32,000) or more than 200 constraint × criterion pairs |
UNVERIFIED — not sent; split the spec by domain |
| answered by a model other than the calibrated one | warning in the report — re-run the calibration fixtures before trusting borderline verdicts |
--dry-run |
request printed, no verdict — never cite a dry run as a result |
| agent disagrees with Jev | the agent may add commentary and fix the spec; it may not change the verdict |
Integrated Verdict#
Worst result wins: UNVERIFIED > FAIL > CONDITIONAL > PASS.
Stage 1 PASS and 2a PASS and 2b PASS → ✅ Gate PASS (lockable)
Stage 1 PASS and (2a or 2b CONDITIONAL), no FAIL → CONDITIONAL (fix → re-run, ≤ 2 rounds)
any FAIL → FAIL
any UNVERIFIED → UNVERIFIED (not a gate outcome; fix the environment, re-run)
Stamp rule. Every verdict is bound to spec_version + spec_hash
(git blob sha of the file,
computed by jev_judge.py). If the file changes after a verdict, that verdict is stale: re-run both
stages — they are cheap (Stage 1 is local; Stage 2 is one sub-cent request). Reusing a stale PASS is
the null-as-pass form of plugins/cc-dev/rules/orchestration-graph.md
§3.2. A missing stamp on an
older report counts as a mismatch.
What Jev does not do — and who does it instead#
| Need | Jev | Agent |
|---|---|---|
| Verdict, probabilities, confidence | ✅ | ✗ must not override |
| Why a dimension scored level 2 | lists the flagged rows | explains them from the row text and AMBIGUITY_RUBRIC.md |
| Remediation list | ✗ | writes it from FAIL / CONDITIONAL items |
| Fixing the spec | ✗ | edits the spec, then re-runs the gate |
| Locking | ✗ | only after Gate PASS + --lock or user confirmation |
Result Report Format — docs/spec-evaluation-{slug}.md#
# Spec Evaluation: {project-name}
## Summary
- Seed Spec: {path} · {specVersion} · {spec_hash_short}
- Model: {model_reported} · Date: {YYYY-MM-DD} · Questions: {question_count} · Elapsed: {ms} ms
- **Final Verdict: {PASS | CONDITIONAL | FAIL | UNVERIFIED}**
- Caveats: {warnings, e.g. model differs from the calibrated one} · uncertain rows: {uncertain_rows}
## Stage 1: Mechanical — {PASS|FAIL}
| ID | Item | Result | Detail |
|----|------|--------|--------|
| M-01 | … | ✅ | |
## Stage 2a: Ambiguity — {score} ({grade}) → {verdict}
| Dimension | Level | Normalized | Weight | Rows |
|-----------|-------|-----------|--------|------|
| lexical_precision | 1 | 0.25 | 0.25 | AC-03, AC-05 |
Flagged Must/Must-Not rows: {flagged_must_rows}
## Stage 2b: Panel — blocking {n} → {verdict}
| Seat | Counted as | Basis |
|------|-----------|-------|
| analyst | REQUEST_CHANGES | AC-03 observable 0.10 |
## Remediation (written by the agent from the failed items — Jev supplies no prose)
- {row / seat}: {what to change, citing the Seed section and row id}
## Round table
| Round | Stamp | Stage 1 | 2a | 2b | Disposition |
|-------|-------|---------|----|----|-------------|
| 1 | v1.0.0 / 3ab91c | PASS | 0.10 WARNING (AC-03) | 1 | fixed S4 AC-03 wording |