jevcheck 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jevcheck-0.2.0/.github/workflows/ci.yml +59 -0
- jevcheck-0.2.0/LICENSE +21 -0
- jevcheck-0.2.0/MANIFEST.in +10 -0
- jevcheck-0.2.0/PKG-INFO +162 -0
- jevcheck-0.2.0/README.md +138 -0
- jevcheck-0.2.0/docs/adr-009-two-model-compare.md +64 -0
- jevcheck-0.2.0/docs/contract.md +125 -0
- jevcheck-0.2.0/docs/jev-api.md +119 -0
- jevcheck-0.2.0/docs/release-readiness-audit-v0.1-recheck.md +111 -0
- jevcheck-0.2.0/docs/release-readiness-audit-v0.1.md +117 -0
- jevcheck-0.2.0/fixtures/replay-baseline.json +54 -0
- jevcheck-0.2.0/fixtures/replay-breaking.json +53 -0
- jevcheck-0.2.0/fixtures/replay-unchanged.json +54 -0
- jevcheck-0.2.0/fixtures/support-triage.json +97 -0
- jevcheck-0.2.0/fixtures/support-triage.jsonl +2 -0
- jevcheck-0.2.0/pyproject.toml +46 -0
- jevcheck-0.2.0/setup.cfg +4 -0
- jevcheck-0.2.0/src/jevcheck/__init__.py +57 -0
- jevcheck-0.2.0/src/jevcheck/__main__.py +3 -0
- jevcheck-0.2.0/src/jevcheck/answers.py +228 -0
- jevcheck-0.2.0/src/jevcheck/cli.py +356 -0
- jevcheck-0.2.0/src/jevcheck/client.py +78 -0
- jevcheck-0.2.0/src/jevcheck/compare.py +199 -0
- jevcheck-0.2.0/src/jevcheck/contract.py +314 -0
- jevcheck-0.2.0/src/jevcheck/eval.py +450 -0
- jevcheck-0.2.0/src/jevcheck/gate.py +61 -0
- jevcheck-0.2.0/src/jevcheck/pinning.py +101 -0
- jevcheck-0.2.0/src/jevcheck/py.typed +0 -0
- jevcheck-0.2.0/src/jevcheck/questions.py +100 -0
- jevcheck-0.2.0/src/jevcheck.egg-info/PKG-INFO +162 -0
- jevcheck-0.2.0/src/jevcheck.egg-info/SOURCES.txt +47 -0
- jevcheck-0.2.0/src/jevcheck.egg-info/dependency_links.txt +1 -0
- jevcheck-0.2.0/src/jevcheck.egg-info/entry_points.txt +2 -0
- jevcheck-0.2.0/src/jevcheck.egg-info/requires.txt +5 -0
- jevcheck-0.2.0/src/jevcheck.egg-info/top_level.txt +1 -0
- jevcheck-0.2.0/tests/__init__.py +0 -0
- jevcheck-0.2.0/tests/conftest.py +11 -0
- jevcheck-0.2.0/tests/helpers.py +34 -0
- jevcheck-0.2.0/tests/test_answers.py +175 -0
- jevcheck-0.2.0/tests/test_cli.py +614 -0
- jevcheck-0.2.0/tests/test_client.py +102 -0
- jevcheck-0.2.0/tests/test_compare.py +487 -0
- jevcheck-0.2.0/tests/test_contract.py +267 -0
- jevcheck-0.2.0/tests/test_eval.py +134 -0
- jevcheck-0.2.0/tests/test_eval_boundaries.py +194 -0
- jevcheck-0.2.0/tests/test_gate.py +35 -0
- jevcheck-0.2.0/tests/test_live.py +36 -0
- jevcheck-0.2.0/tests/test_pinning.py +112 -0
- jevcheck-0.2.0/tests/test_sdk_transport.py +296 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: pip install -e ".[dev]"
|
|
21
|
+
- name: Test
|
|
22
|
+
run: pytest
|
|
23
|
+
|
|
24
|
+
packaging:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
- name: Build wheel and sdist
|
|
32
|
+
run: |
|
|
33
|
+
pip install build
|
|
34
|
+
python -m build
|
|
35
|
+
- name: Installed-wheel CLI smoke
|
|
36
|
+
run: |
|
|
37
|
+
python -m venv "$RUNNER_TEMP/wheel"
|
|
38
|
+
"$RUNNER_TEMP/wheel/bin/pip" install dist/*.whl
|
|
39
|
+
"$RUNNER_TEMP/wheel/bin/jevcheck" eval fixtures/support-triage.json \
|
|
40
|
+
--candidate-model jev-1.14 \
|
|
41
|
+
--answers fixtures/replay-unchanged.json
|
|
42
|
+
"$RUNNER_TEMP/wheel/bin/jevcheck" compare fixtures/support-triage.json \
|
|
43
|
+
--from fixtures/replay-baseline.json \
|
|
44
|
+
--to jev-1.14 \
|
|
45
|
+
--answers fixtures/replay-unchanged.json
|
|
46
|
+
"$RUNNER_TEMP/wheel/bin/python" -c "import jevcheck,inspect; print(jevcheck.__version__)"
|
|
47
|
+
- name: sdist includes tests/fixtures and pytest collects
|
|
48
|
+
run: |
|
|
49
|
+
python -m venv "$RUNNER_TEMP/sdist"
|
|
50
|
+
"$RUNNER_TEMP/sdist/bin/pip" install dist/*.tar.gz pytest
|
|
51
|
+
mkdir -p "$RUNNER_TEMP/srcdist"
|
|
52
|
+
tar -xzf dist/*.tar.gz -C "$RUNNER_TEMP/srcdist"
|
|
53
|
+
srcdir=$(echo "$RUNNER_TEMP/srcdist"/jevcheck-*)
|
|
54
|
+
test -f "$srcdir/tests/helpers.py"
|
|
55
|
+
test -f "$srcdir/tests/conftest.py"
|
|
56
|
+
test -f "$srcdir/fixtures/support-triage.json"
|
|
57
|
+
test -f "$srcdir/docs/contract.md"
|
|
58
|
+
cd "$srcdir"
|
|
59
|
+
"$RUNNER_TEMP/sdist/bin/pytest"
|
jevcheck-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nithilan Kumaran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
jevcheck-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jevcheck
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: pytest for Jev — pin production behavioral contracts and eval candidate model upgrades
|
|
5
|
+
Author: Nithilan Kumaran
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sathariels/jevcheck
|
|
8
|
+
Project-URL: Documentation, https://github.com/sathariels/jevcheck
|
|
9
|
+
Keywords: jev,typesafe,eval,contract,model-upgrade
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: pydantic>=2
|
|
20
|
+
Requires-Dist: typesafe-sdk>=0.7.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# jevcheck
|
|
26
|
+
|
|
27
|
+
**pytest for Jev.** Pin what production is allowed to do, eval a candidate model, and fail the upgrade when answers flip or confidence drops.
|
|
28
|
+
|
|
29
|
+
Probabilities and model versions move. A raw `0.94` is not a release decision. jevcheck records a **production contract** (baseline model + fixtures + expected answers) and evals a candidate against that fixture.
|
|
30
|
+
|
|
31
|
+
**v0.1** `eval` is fixture-versus-candidate. **v0.2** adds two-model execution: `record` a baseline model's answers, then `compare` a candidate against that snapshot (or fetch both models live). See [`docs/adr-009-two-model-compare.md`](docs/adr-009-two-model-compare.md).
|
|
32
|
+
|
|
33
|
+
The repo’s `jev-1.13` / `jev-1.14` strings are **unverified example pin labels** used by fixtures. A documented TypeSafe version pin (2026-09-19 model list) is `jev-1.13.0`. Floating aliases `jev-latest` and `jev-preview` are rejected unless you pass `--allow-unpinned`. With that opt-in, a response whose `model` is the concrete resolved ID (for example `jev-1.13.0`) is accepted; the eval report prints that resolved model. Concrete pins still require exact identity.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
pin contract → ship on the pinned model → jevcheck eval → compatible or breaking
|
|
37
|
+
→ record → compare → compatible or breaking (v0.2)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Reports: **unchanged** / **confidence regressions** / **answer flips**, with exact diffs (`billing→general`, `0.94→0.71`) and a nonzero exit on failure.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e ".[dev]"
|
|
46
|
+
export TYPESAFE_API_KEY=... # live Jev only; never commit this
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Auth is `TYPESAFE_API_KEY` only. Unit tests mock the network.
|
|
50
|
+
|
|
51
|
+
## Pin → eval → upgrade
|
|
52
|
+
|
|
53
|
+
1. Write a contract (see [`docs/contract.md`](docs/contract.md)) against the model you ship.
|
|
54
|
+
2. Call Jev with that **explicit** model. `jev-latest` and `jev-preview` are rejected unless you opt in. Concrete pins require `response.model` to match exactly. An opted-in alias may resolve to a nonempty concrete (non-alias) response model, which is reported.
|
|
55
|
+
3. Before upgrading, eval the candidate (example fixture label — not a verified live ID):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Live call. Pin a catalog version such as jev-1.13.0 in production.
|
|
59
|
+
# The example below matches this repo's replay fixtures only.
|
|
60
|
+
jevcheck eval fixtures/support-triage.json --candidate-model jev-1.14
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Replay recorded answers (CI / no key):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
jevcheck eval fixtures/support-triage.json \
|
|
67
|
+
--candidate-model jev-1.14 \
|
|
68
|
+
--answers fixtures/replay-breaking.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
4. Compatible (exit 0) → change the pin. Breaking (exit 1) → read the diffs; do not upgrade.
|
|
72
|
+
|
|
73
|
+
## Upgrade flow (v0.2): record baseline → compare candidate
|
|
74
|
+
|
|
75
|
+
Two-model execution is a **v0.2** capability. `eval` above stays the v0.1 fixture path and is unchanged.
|
|
76
|
+
|
|
77
|
+
1. Record answers from the model you ship. The file is the same replay JSON `eval --answers` already accepts:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Live (needs TYPESAFE_API_KEY). Pin a catalog version such as jev-1.13.0 in production.
|
|
81
|
+
jevcheck record fixtures/support-triage.json --out baseline-answers.json
|
|
82
|
+
|
|
83
|
+
# CI / no key: copy a previously recorded snapshot through the same command.
|
|
84
|
+
jevcheck record fixtures/support-triage.json \
|
|
85
|
+
--answers fixtures/replay-baseline.json \
|
|
86
|
+
--out baseline-answers.json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`record` writes only after every answer kind matches the contract question (choice / noul / score). A mismatch is exit 2. Opted-in aliases print the resolved response model in the summary (`jev-preview → jev-1.13.0`).
|
|
90
|
+
|
|
91
|
+
2. Compare the candidate against that snapshot:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Live candidate against recorded baseline
|
|
95
|
+
jevcheck compare fixtures/support-triage.json \
|
|
96
|
+
--from baseline-answers.json \
|
|
97
|
+
--to jev-1.14
|
|
98
|
+
|
|
99
|
+
# CI / no key: recorded baseline + candidate replay
|
|
100
|
+
jevcheck compare fixtures/support-triage.json \
|
|
101
|
+
--from fixtures/replay-baseline.json \
|
|
102
|
+
--to jev-1.14 \
|
|
103
|
+
--answers fixtures/replay-breaking.json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
3. Or fetch both models in one step (live; needs a key):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
jevcheck compare fixtures/support-triage.json \
|
|
110
|
+
--from-model jev-1.13 \
|
|
111
|
+
--to jev-1.14
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`--from` and `--from-model` are mutually exclusive. Identity rules are the v0.1 pins: concrete names must match `response.model` exactly; `jev-latest` / `jev-preview` still need `--allow-unpinned`. Exit codes stay 0 compatible / 1 breaking / 2 usage-or-identity / 3 ops.
|
|
115
|
+
|
|
116
|
+
Verified System One fields: [`docs/jev-api.md`](docs/jev-api.md).
|
|
117
|
+
|
|
118
|
+
## Example
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from jevcheck import JevClient, evaluate, load_contract
|
|
122
|
+
|
|
123
|
+
contract = load_contract("fixtures/support-triage.json")
|
|
124
|
+
client = JevClient(model="jev-1.14") # example fixture label; pin a catalog version in production
|
|
125
|
+
|
|
126
|
+
report = evaluate(
|
|
127
|
+
contract,
|
|
128
|
+
lambda case: client.system_one(
|
|
129
|
+
state=case.state,
|
|
130
|
+
questions=case.questions,
|
|
131
|
+
model="jev-1.14",
|
|
132
|
+
),
|
|
133
|
+
candidate_model="jev-1.14",
|
|
134
|
+
)
|
|
135
|
+
print(report.summary())
|
|
136
|
+
if report.breaking:
|
|
137
|
+
raise SystemExit(1)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A `Gate` helper exists for auto / ask-human / reject thresholds. It is optional and is not the product.
|
|
141
|
+
|
|
142
|
+
## Develop
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pip install -e ".[dev]"
|
|
146
|
+
pytest
|
|
147
|
+
python -m jevcheck eval fixtures/support-triage.json \
|
|
148
|
+
--candidate-model jev-1.14 \
|
|
149
|
+
--answers fixtures/replay-unchanged.json
|
|
150
|
+
python -m jevcheck compare fixtures/support-triage.json \
|
|
151
|
+
--from fixtures/replay-baseline.json \
|
|
152
|
+
--to jev-1.14 \
|
|
153
|
+
--answers fixtures/replay-unchanged.json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## For agents / audits
|
|
157
|
+
|
|
158
|
+
v0.2 two-model lock: [`docs/adr-009-two-model-compare.md`](docs/adr-009-two-model-compare.md).
|
|
159
|
+
|
|
160
|
+
See [`docs/release-readiness-audit-v0.1.md`](docs/release-readiness-audit-v0.1.md). Recheck: [`docs/release-readiness-audit-v0.1-recheck.md`](docs/release-readiness-audit-v0.1-recheck.md).
|
|
161
|
+
|
|
162
|
+
MIT.
|
jevcheck-0.2.0/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# jevcheck
|
|
2
|
+
|
|
3
|
+
**pytest for Jev.** Pin what production is allowed to do, eval a candidate model, and fail the upgrade when answers flip or confidence drops.
|
|
4
|
+
|
|
5
|
+
Probabilities and model versions move. A raw `0.94` is not a release decision. jevcheck records a **production contract** (baseline model + fixtures + expected answers) and evals a candidate against that fixture.
|
|
6
|
+
|
|
7
|
+
**v0.1** `eval` is fixture-versus-candidate. **v0.2** adds two-model execution: `record` a baseline model's answers, then `compare` a candidate against that snapshot (or fetch both models live). See [`docs/adr-009-two-model-compare.md`](docs/adr-009-two-model-compare.md).
|
|
8
|
+
|
|
9
|
+
The repo’s `jev-1.13` / `jev-1.14` strings are **unverified example pin labels** used by fixtures. A documented TypeSafe version pin (2026-09-19 model list) is `jev-1.13.0`. Floating aliases `jev-latest` and `jev-preview` are rejected unless you pass `--allow-unpinned`. With that opt-in, a response whose `model` is the concrete resolved ID (for example `jev-1.13.0`) is accepted; the eval report prints that resolved model. Concrete pins still require exact identity.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pin contract → ship on the pinned model → jevcheck eval → compatible or breaking
|
|
13
|
+
→ record → compare → compatible or breaking (v0.2)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Reports: **unchanged** / **confidence regressions** / **answer flips**, with exact diffs (`billing→general`, `0.94→0.71`) and a nonzero exit on failure.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install -e ".[dev]"
|
|
22
|
+
export TYPESAFE_API_KEY=... # live Jev only; never commit this
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Auth is `TYPESAFE_API_KEY` only. Unit tests mock the network.
|
|
26
|
+
|
|
27
|
+
## Pin → eval → upgrade
|
|
28
|
+
|
|
29
|
+
1. Write a contract (see [`docs/contract.md`](docs/contract.md)) against the model you ship.
|
|
30
|
+
2. Call Jev with that **explicit** model. `jev-latest` and `jev-preview` are rejected unless you opt in. Concrete pins require `response.model` to match exactly. An opted-in alias may resolve to a nonempty concrete (non-alias) response model, which is reported.
|
|
31
|
+
3. Before upgrading, eval the candidate (example fixture label — not a verified live ID):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Live call. Pin a catalog version such as jev-1.13.0 in production.
|
|
35
|
+
# The example below matches this repo's replay fixtures only.
|
|
36
|
+
jevcheck eval fixtures/support-triage.json --candidate-model jev-1.14
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Replay recorded answers (CI / no key):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
jevcheck eval fixtures/support-triage.json \
|
|
43
|
+
--candidate-model jev-1.14 \
|
|
44
|
+
--answers fixtures/replay-breaking.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
4. Compatible (exit 0) → change the pin. Breaking (exit 1) → read the diffs; do not upgrade.
|
|
48
|
+
|
|
49
|
+
## Upgrade flow (v0.2): record baseline → compare candidate
|
|
50
|
+
|
|
51
|
+
Two-model execution is a **v0.2** capability. `eval` above stays the v0.1 fixture path and is unchanged.
|
|
52
|
+
|
|
53
|
+
1. Record answers from the model you ship. The file is the same replay JSON `eval --answers` already accepts:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Live (needs TYPESAFE_API_KEY). Pin a catalog version such as jev-1.13.0 in production.
|
|
57
|
+
jevcheck record fixtures/support-triage.json --out baseline-answers.json
|
|
58
|
+
|
|
59
|
+
# CI / no key: copy a previously recorded snapshot through the same command.
|
|
60
|
+
jevcheck record fixtures/support-triage.json \
|
|
61
|
+
--answers fixtures/replay-baseline.json \
|
|
62
|
+
--out baseline-answers.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`record` writes only after every answer kind matches the contract question (choice / noul / score). A mismatch is exit 2. Opted-in aliases print the resolved response model in the summary (`jev-preview → jev-1.13.0`).
|
|
66
|
+
|
|
67
|
+
2. Compare the candidate against that snapshot:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Live candidate against recorded baseline
|
|
71
|
+
jevcheck compare fixtures/support-triage.json \
|
|
72
|
+
--from baseline-answers.json \
|
|
73
|
+
--to jev-1.14
|
|
74
|
+
|
|
75
|
+
# CI / no key: recorded baseline + candidate replay
|
|
76
|
+
jevcheck compare fixtures/support-triage.json \
|
|
77
|
+
--from fixtures/replay-baseline.json \
|
|
78
|
+
--to jev-1.14 \
|
|
79
|
+
--answers fixtures/replay-breaking.json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
3. Or fetch both models in one step (live; needs a key):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
jevcheck compare fixtures/support-triage.json \
|
|
86
|
+
--from-model jev-1.13 \
|
|
87
|
+
--to jev-1.14
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`--from` and `--from-model` are mutually exclusive. Identity rules are the v0.1 pins: concrete names must match `response.model` exactly; `jev-latest` / `jev-preview` still need `--allow-unpinned`. Exit codes stay 0 compatible / 1 breaking / 2 usage-or-identity / 3 ops.
|
|
91
|
+
|
|
92
|
+
Verified System One fields: [`docs/jev-api.md`](docs/jev-api.md).
|
|
93
|
+
|
|
94
|
+
## Example
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from jevcheck import JevClient, evaluate, load_contract
|
|
98
|
+
|
|
99
|
+
contract = load_contract("fixtures/support-triage.json")
|
|
100
|
+
client = JevClient(model="jev-1.14") # example fixture label; pin a catalog version in production
|
|
101
|
+
|
|
102
|
+
report = evaluate(
|
|
103
|
+
contract,
|
|
104
|
+
lambda case: client.system_one(
|
|
105
|
+
state=case.state,
|
|
106
|
+
questions=case.questions,
|
|
107
|
+
model="jev-1.14",
|
|
108
|
+
),
|
|
109
|
+
candidate_model="jev-1.14",
|
|
110
|
+
)
|
|
111
|
+
print(report.summary())
|
|
112
|
+
if report.breaking:
|
|
113
|
+
raise SystemExit(1)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A `Gate` helper exists for auto / ask-human / reject thresholds. It is optional and is not the product.
|
|
117
|
+
|
|
118
|
+
## Develop
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pip install -e ".[dev]"
|
|
122
|
+
pytest
|
|
123
|
+
python -m jevcheck eval fixtures/support-triage.json \
|
|
124
|
+
--candidate-model jev-1.14 \
|
|
125
|
+
--answers fixtures/replay-unchanged.json
|
|
126
|
+
python -m jevcheck compare fixtures/support-triage.json \
|
|
127
|
+
--from fixtures/replay-baseline.json \
|
|
128
|
+
--to jev-1.14 \
|
|
129
|
+
--answers fixtures/replay-unchanged.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## For agents / audits
|
|
133
|
+
|
|
134
|
+
v0.2 two-model lock: [`docs/adr-009-two-model-compare.md`](docs/adr-009-two-model-compare.md).
|
|
135
|
+
|
|
136
|
+
See [`docs/release-readiness-audit-v0.1.md`](docs/release-readiness-audit-v0.1.md). Recheck: [`docs/release-readiness-audit-v0.1-recheck.md`](docs/release-readiness-audit-v0.1-recheck.md).
|
|
137
|
+
|
|
138
|
+
MIT.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ADR 009 — Two-model / baseline comparison (v0.2 slice)
|
|
2
|
+
|
|
3
|
+
Status: accepted for the v0.2 slice. Packaging version is `0.2.0`. The owner tags and publishes to PyPI after merge; this PR does not create a GitHub release. This document is the lock for `record` / `compare`.
|
|
4
|
+
|
|
5
|
+
v0.1 `eval` remains fixture-versus-candidate. This slice adds a second, explicit path so upgrade safety can mean **baseline answers versus candidate answers**, not only authored `expect` versus candidate.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
| Command | Meaning |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `record` | Given a v0.1 contract and a baseline model, fetch System One answers for every case and write a replay JSON. The file is the same shape as `eval --answers` (`dump_replay` / `load_replay`). |
|
|
12
|
+
| `compare` | Evaluate a candidate against those recorded baseline answers, **or** fetch both models live. |
|
|
13
|
+
| Baseline recording | The act of calling `record` (or an equivalent in-process `record_answers`). It does **not** rewrite the contract schema. Fixture `expect` stays the v0.1 authored contract. |
|
|
14
|
+
|
|
15
|
+
Chosen CLI (one UX, documented here):
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
jevcheck record CONTRACT --out FILE [--baseline-model MODEL] [--answers REPLAY]
|
|
19
|
+
jevcheck compare CONTRACT --from FILE --to MODEL [--answers CANDIDATE_REPLAY]
|
|
20
|
+
jevcheck compare CONTRACT --from-model MODEL --to MODEL [--answers CANDIDATE_REPLAY]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `--from FILE` is a recorded baseline replay. `--from-model MODEL` is a live baseline fetch. Exactly one is required.
|
|
24
|
+
- `--to` is the candidate pin (same role as `eval --candidate-model`).
|
|
25
|
+
- `--answers` on `record` / `compare` is a mocked replay so CI and tests need no key. On `compare` it is the **candidate** replay.
|
|
26
|
+
- `--baseline-model` still overrides contract / JSONL metadata. With `--from-model`, that live pin is the baseline identity.
|
|
27
|
+
- `eval CONTRACT --candidate-model … [--answers]` is unchanged.
|
|
28
|
+
|
|
29
|
+
## How compare decides
|
|
30
|
+
|
|
31
|
+
Compare does not invent a second scoring engine. It builds a derived contract: same cases, questions, defaults, and floors/tolerances; each `expect` field is filled from the baseline answer (`choice` / `noul`+`noul_true` / `score`, plus `baseline_confidence` from the [mapped scalar](jev-api.md#confidence-mapping-used-by-jevcheck)). Then it runs existing `evaluate()` on the candidate.
|
|
32
|
+
|
|
33
|
+
`record` and `compare` reject a wrong-kind answer (choice↔ChoiceAnswer, noul↔NoulAnswer, score↔ScoreAnswer) as a usage error (exit 2) rather than writing a snapshot `compare` cannot load. Missing baseline cases or missing expected fields are usage errors, not silent flips. Candidate missing answers stay v0.1 **answer flips**.
|
|
34
|
+
|
|
35
|
+
Compare does **not** invent a default `confidence_tolerance`. Drop detection uses the contract’s resolved defaults, same as v0.1.
|
|
36
|
+
|
|
37
|
+
## Identity and pinning
|
|
38
|
+
|
|
39
|
+
Reuse v0.1 `require_pinned` / `require_response_identity` for every requested model and every response `model`:
|
|
40
|
+
|
|
41
|
+
- Concrete pins: exact string equality. `jev-1.13` is not `jev-1.13.0`.
|
|
42
|
+
- Floating aliases (`jev-latest`, `jev-preview`, or any name containing `latest` / `preview`) stay rejected unless `--allow-unpinned`.
|
|
43
|
+
- With that opt-in, a nonempty concrete (non-alias) response model is accepted and reported. Record and compare summaries print `alias → resolved` (for example `jev-preview → jev-1.13.0`). jevcheck never rewrites a name.
|
|
44
|
+
|
|
45
|
+
`record` identity-checks the baseline. `compare --from` identity-checks each recorded baseline response against the contract / `--baseline-model` pin. `compare --from-model` identity-checks the live baseline. The candidate is identity-checked as in `eval`.
|
|
46
|
+
|
|
47
|
+
## Exit codes (unchanged)
|
|
48
|
+
|
|
49
|
+
| Code | Meaning |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| 0 | Compatible (`compare`) or successful write (`record`) |
|
|
52
|
+
| 1 | Behavioral contract failure (flips / confidence regressions) |
|
|
53
|
+
| 2 | Usage / input / identity / missing answers / unpinned without opt-in |
|
|
54
|
+
| 3 | Operational (SDK HTTP / invalid API body) |
|
|
55
|
+
|
|
56
|
+
Secret redaction on error text is unchanged.
|
|
57
|
+
|
|
58
|
+
## Non-goals
|
|
59
|
+
|
|
60
|
+
- Not a pytest plugin.
|
|
61
|
+
- `Gate` is unchanged and is still not the product.
|
|
62
|
+
- Contract JSON schema stays v0.1 (`version: "0.1"`). Record writes a replay file; it does not emit a new contract format.
|
|
63
|
+
- No TypeSafe / System One fields beyond [docs/jev-api.md](jev-api.md).
|
|
64
|
+
- No `0.2.0` git tag in this PR.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# jevcheck contract format (v0.1)
|
|
2
|
+
|
|
3
|
+
A **contract** is the production baseline: pinned model, fixtures, and expected answers. `jevcheck eval` compares a candidate model against that contract and reports unchanged cases, confidence regressions, and answer flips.
|
|
4
|
+
|
|
5
|
+
This format is owner-locked for v0.1. Ask before changing it.
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
- **JSON** (recommended): one object with metadata + `cases`.
|
|
10
|
+
- **JSONL**: one case object per line. Blank lines ignored. Optional first-line metadata object with `"_meta": true`.
|
|
11
|
+
|
|
12
|
+
## Contract object
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"version": "0.1",
|
|
17
|
+
"name": "support-triage",
|
|
18
|
+
"baseline_model": "jev-1.13",
|
|
19
|
+
"allow_unpinned": false,
|
|
20
|
+
"defaults": {
|
|
21
|
+
"min_confidence": 0.8,
|
|
22
|
+
"confidence_tolerance": 0.1,
|
|
23
|
+
"noul_true_threshold": 0.5,
|
|
24
|
+
"noul_tolerance": 0.1,
|
|
25
|
+
"score_tolerance": 0.5
|
|
26
|
+
},
|
|
27
|
+
"cases": []
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Field | Required | Meaning |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `version` | yes | Must be `"0.1"` |
|
|
34
|
+
| `name` | no | Shown in the CLI summary |
|
|
35
|
+
| `baseline_model` | yes for JSON | Pinned production model this contract was recorded against. Empty `""` is rejected. Floating aliases (`jev-latest`, `jev-preview`, or any name containing `latest`/`preview`) are rejected unless `allow_unpinned` / `--allow-unpinned`. jevcheck does not rewrite aliases: `jev-1.13` is not `jev-1.13.0`. |
|
|
36
|
+
| `allow_unpinned` | no | If `true`, floating `jev-latest` / `jev-preview` names are allowed (default `false`) |
|
|
37
|
+
| `defaults` | no | Applied when a case field omits the same key |
|
|
38
|
+
| `cases` | yes for JSON | Fixture list |
|
|
39
|
+
|
|
40
|
+
JSONL without a `_meta` line still needs `--baseline-model` on the CLI (or a per-eval override).
|
|
41
|
+
|
|
42
|
+
## Case object
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"id": "ticket-001",
|
|
47
|
+
"state": "I was charged twice. Please fix this ASAP.",
|
|
48
|
+
"questions": {
|
|
49
|
+
"intent": {
|
|
50
|
+
"type": "choice",
|
|
51
|
+
"instructions": "What is this ticket about?",
|
|
52
|
+
"criteria": {"billing": null, "general": null}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"expect": {
|
|
56
|
+
"intent": {
|
|
57
|
+
"choice": "billing",
|
|
58
|
+
"min_confidence": 0.85,
|
|
59
|
+
"baseline_confidence": 0.94
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`state` and `questions` match the verified System One request: see `docs/jev-api.md`. Question objects use `type` of `noul`, `choice`, or `score` and only those official fields.
|
|
66
|
+
|
|
67
|
+
Every key in `expect` must exist in `questions`. Extra unknown keys are rejected. An empty field expect (`{}`) is rejected. Kind-specific keys on the wrong question type (for example `score` on a choice question) are rejected. Shared confidence keys may appear on any field. This is an applicability check; the fixture key set is unchanged.
|
|
68
|
+
|
|
69
|
+
## Field expectations
|
|
70
|
+
|
|
71
|
+
All keys optional except that a field must state at least one *effective* constraint (a kind-specific answer key, `min_noul`, `min_confidence`, or `baseline_confidence` **together with** a resolved `confidence_tolerance` or `min_confidence` floor). A baseline-confidence-only expect with no applicable floor or tolerance after defaults is rejected (exit 2). Do not assume a default `confidence_tolerance`. Tolerance-only objects are rejected.
|
|
72
|
+
|
|
73
|
+
| Key | Applies to | Meaning |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `choice` | choice | Expected winning label. Different label → **answer flip** |
|
|
76
|
+
| `noul_true` | noul | Expected yes (`true`) or no (`false`) vs `noul_true_threshold`. Opposite polarity → **answer flip** |
|
|
77
|
+
| `noul` | noul | Expected `noul` float. Used with `noul_tolerance` / `min_noul` |
|
|
78
|
+
| `min_noul` | noul | Floor on the `noul` float (yes-probability) |
|
|
79
|
+
| `noul_tolerance` | noul | Allowed absolute drift from `noul` (inclusive; exact 0.1 drop against 0.1 passes, with a 1e-9 epsilon so binary float subtraction of decimal tenths does not false-fail) |
|
|
80
|
+
| `score` | score | Expected expected-score. Nearest integer level change → **answer flip** |
|
|
81
|
+
| `score_tolerance` | score | **Level-flip suppression**, not an absolute max distance. If `\|actual - expected\|` is within this inclusive bound (same 1e-9 epsilon as confidence/noul; so 1.4 vs 1.6 at 0.2 is within), a nearest-level change is not a flip. When both values already share a nearest level (Python `round`, ties toward even: `1.5→2`, `2.5→2`), a larger float gap still counts as unchanged. Example: expected 1.8, actual 2.2, tolerance 0.1 → unchanged (both level 2). |
|
|
82
|
+
| `min_confidence` | all | Floor on the [mapped scalar](jev-api.md#confidence-mapping-used-by-jevcheck) |
|
|
83
|
+
| `baseline_confidence` | all | Previously recorded scalar (e.g. `0.94`) |
|
|
84
|
+
| `confidence_tolerance` | all | Allowed drop from `baseline_confidence` (inclusive, same 1e-9 epsilon as noul drift) |
|
|
85
|
+
|
|
86
|
+
Noul has no API `confidence`. The mapped scalar is `noul` itself.
|
|
87
|
+
|
|
88
|
+
## Outcomes (per field, worst wins the case)
|
|
89
|
+
|
|
90
|
+
1. **answer flip** — categorical change: choice label, noul yes/no, or score nearest level (unless `score_tolerance` suppresses that flip); or the answer is missing / wrong type.
|
|
91
|
+
2. **confidence regression** — same answer, but the mapped scalar is below `min_confidence` / `min_noul`, or dropped more than `confidence_tolerance` from `baseline_confidence`, or noul drifted more than `noul_tolerance` from expected `noul`. Inclusive boundaries: a drop of exactly 0.1 against tolerance 0.1 is not a regression.
|
|
92
|
+
3. **unchanged** — answer matches and scalars stay within the pinned floors/tolerances.
|
|
93
|
+
|
|
94
|
+
Choice and score answers must include a nonempty probability map of finite values `>= 0` whose sum is `1 ± 1e-6`. Missing, null, empty, negative, NaN, or badly normalized maps are rejected (exit 2). The adapter does not invent an empty map. Score answers also require a nonempty `legend` (SDK-shaped responses always include it). Score `legend` / `probabilities` coming from typesafe-sdk 0.7.0 may use integer keys; jevcheck stringifies those keys.
|
|
95
|
+
|
|
96
|
+
When `--candidate-model` is a **concrete pin**, every response `model` must equal that candidate exactly. A null or missing model is an error (never the string `"None"`). Opted-in floating aliases (`jev-latest`, `jev-preview`, or names containing those tokens) under `--allow-unpinned` accept a nonempty concrete response `model` that is not itself a floating alias; the eval report prints the resolved response model. Without `--allow-unpinned`, floating aliases stay rejected.
|
|
97
|
+
|
|
98
|
+
Repo examples such as `jev-1.13` / `jev-1.14` are **unverified example pin labels** for fixtures, not a claim that those IDs exist on the live TypeSafe catalog. A documented version pin on the 2026-09-19 model list is `jev-1.13.0`.
|
|
99
|
+
|
|
100
|
+
A higher candidate confidence is not a regression.
|
|
101
|
+
|
|
102
|
+
## Replay file (`--answers`)
|
|
103
|
+
|
|
104
|
+
For mocked evals and tests, a JSON object keyed by case `id`:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"ticket-001": {
|
|
109
|
+
"model": "jev-1.14",
|
|
110
|
+
"usage": {"input_tokens": 10, "output_tokens": 4},
|
|
111
|
+
"answers": {
|
|
112
|
+
"intent": {
|
|
113
|
+
"type": "choice",
|
|
114
|
+
"choice": "general",
|
|
115
|
+
"confidence": 0.71,
|
|
116
|
+
"probabilities": {"billing": 0.29, "general": 0.71}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Answer objects use only verified System One fields.
|
|
124
|
+
|
|
125
|
+
`jevcheck record` (v0.2) writes this same file. `jevcheck compare --from` reads it as the baseline snapshot. See [`adr-009-two-model-compare.md`](adr-009-two-model-compare.md). `eval` remains fixture-versus-candidate.
|