Files
gstack/docs/eval-models.md
T
Garry Tanandcapy-ai[bot] c71d764ba4 v1.91.115.0 feat: field list 2 wave A: autoplan round cap and public export, eval-plan diagnostics and model rules, --for review-voice (#3234)
* fix(capy): the install trailer says behind=<n> and its fix, so a stale install shows in every relayed trailer (field list 2 item 13, #2378)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(capy): a lease records its holder's start identity, so a pid reused after a wake is reclaimed; a skipped session-start upgrade ends fail behind, never ok (GBRA-105, GBRA-106)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(lease): stale needs proof (unreadable or malformed identities keep the lease), ps identity is timezone-independent, and reclaim never deletes a concurrent renewal

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(autoplan): enforced round cap and spend budget, deferred close, public export without machine paths, opt-in durable checkpoints (field list 2 W5-W7)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(lease): one directory lock for publish, prune and reclaim; reclaim rereads the record judged stale, so a renewal is never deleted or hidden

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(lease): the directory lock names its holder; broken only when the holder is dead or its pid reused, unlock removes only its own lock, touch exits 3 under a live holder

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(lease): breakers serialize on .lock.break and reread the lock before removing it; gstack-skill-start reports a lease the held lock kept out

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(lease): name the double-crash limit of breaking a dead lock

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(capy): --for review-voice install profile, no-Drive bootstrap command, reviewer prompt trailer (field list 2 W11)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
(cherry picked from commit 89bf556c11)

* test: pin review-voice row, helpers row and the no-Drive fallback in existing install tests

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
(cherry picked from commit 7f6c70459a)

* test: the DRIVER template names the reviewer trailer relay (pregate lane)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
(cherry picked from commit 2545d9a6d2)

* test(context-budget): capy skill eager ceiling for the review-voice subagent paragraph (W11; ratchet protocol, capture value)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(eval-plan): prereg diagnostics dry run (W8) and .gstack/eval-models.json lint (W9), WIP

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
(cherry picked from commit b509096218)

* feat(eval-plan): W8/W9 tests, docs/eval-models.md, troubleshooting anchors, /eval-plan steps

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
(cherry picked from commit 9335e45de2)

* test(eval-plan): eval-plan/SKILL.md names only defined codes and accepted flags

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
(cherry picked from commit a7a974d268)

* test(review-voice): the launch line points at the verdict's bun-only directory (#3233's launch contract)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(third-party-actions): the Aside drive-offer detector matches the product name, not the English idiom (paid run 38988927989 tpa-absent-linux false positive)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): export refuses artifact paths outside the run before writing; a deferred close keeps earlier undecided findings; reopening CEO grants Eng its rerun (review of #3234)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(autoplan-budget): bind the reservation expectation to lib/autoplan-budget's default estimate (test-of-test ratchet)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): the docs observer accepts the spawned-prefixed gstack-skill-start --help read

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(windows): curate capy-review-voice out of the Windows subset (POSIX launcher contract)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: changelog entry for field list 2 wave A

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: stamp v1.91.115.0

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-17 02:40:45 +00:00

90 lines
4.3 KiB
Markdown

# Eval model rules: `.gstack/eval-models.json`
A repository that has rules about which models its evals may use (for example
"Fable in smoke tests only", "no gpt-5.4-mini", "the newest model of each
family") writes them as data in `.gstack/eval-models.json`. `gstack-eval-plan`
checks every arm against them: `arms`, `dry-run`, `pilot start`, and every
call row of `price-gate`. gstack never reads these rules from prose in
`CLAUDE.md` or any other document. Plan: field list 2, W9
(`docs/designs/field-list-2-2026-10-16/PLAN.md`).
## The file
```json
{
"schema_version": 1,
"allow": ["claude-*", "gpt-*"],
"deny": ["gpt-5.4-mini"],
"scopes": { "full": { "deny": ["claude-fable-*"] } },
"families": {
"claude-opus": ["claude-opus-5-5", "claude-opus-5", "claude-opus-4-8"],
"claude-fable": ["claude-fable-5-1", "claude-fable-5"],
"gpt-sol": ["gpt-6.1-sol", "gpt-6-sol"]
}
}
```
| Key | Required | Meaning |
|---|---|---|
| `schema_version` | yes | `1` |
| `allow` | yes | Model ids or globs an arm may use. An id that no entry matches fails, so a misspelled or new model is never admitted by default. Use `["*"]` to allow everything not denied. |
| `deny` | no | Ids or globs no arm may use. Checked first. |
| `scopes` | no | Per eval scope (`smoke` or `full`), an extra `allow` and/or `deny` list. "Fable in smoke only" is `scopes.full.deny: ["claude-fable-*"]`. |
| `families` | no | Family name to literal ids, **newest first**. With this key present, an arm must be the newest id of its family (`EVAL_MODEL_OLD_GENERATION` otherwise), and an arm in no family fails `EVAL_MODEL_GENERATION_UNRESOLVED` instead of passing unchecked. |
Globs use `*` (any run of characters) and `?` (one character); every other
character is literal. A rule matches the id as written, the id without a
provider prefix (`openai/gpt-5.4-mini` matches `gpt-5.4-mini`), and a Claude id
without its date suffix (`claude-opus-5-5-20261001` matches `claude-opus-5-5`).
An unknown key, an unknown scope, a glob inside `families`, or an id listed in
two families makes the whole file invalid (`EVAL_MODELS_INVALID`), and then no
arm passes.
The rules apply whatever gstack's own model catalog (`lib/model-catalog.ts`)
holds: an uncatalogued model is allowed or denied by the file alone. The
catalog only checks that `families` is current. If a family lists the
catalog's current frontier or smart model after another id, the file is stale
and invalid.
## Order of checks
For each arm at its scope, the first rule that refuses decides:
1. `deny` → `EVAL_MODEL_DENIED`
2. `scopes.<scope>.deny` → `EVAL_MODEL_DENIED`
3. no `allow` entry matches → `EVAL_MODEL_DENIED` (unknown ids fail closed)
4. `scopes.<scope>.allow` is set and does not match → `EVAL_MODEL_DENIED`
5. `families` is set: in no family → `EVAL_MODEL_GENERATION_UNRESOLVED`; not the family's first id → `EVAL_MODEL_OLD_GENERATION`
## Scope
`price-gate` checks each call row at that row's `scope` (`GSTACK_EVAL_CALLS v1`,
`docs/eval-calls.md`). `arms`, `dry-run` and `pilot start` take
`--scope smoke|full`, which defaults to `full`. So a smoke-only model fails
unless the command says it is a smoke run.
## Which copy is read
By default the reviewed copy is read: `git show origin/<base>:.gstack/eval-models.json`
(`--base`, default origin/HEAD's branch). A branch cannot loosen the rules it
is checked against. When the working-tree copy differs, an
`EVAL_MODELS_NOTE:` line says so. `--models-from head` lints with the working-tree copy
instead. Use it for the PR that adds or changes the file; the source label
says `head (unreviewed)`. When neither copy exists, nothing is checked and
the line says `EVAL_MODELS: none ...`. When the base cannot be resolved but a
working-tree copy exists, the command refuses (`EVAL_MODELS_INVALID`) rather
than skipping the rules.
## Output
```
EVAL_MODEL: claude-fable-5-1 scope=full denied (scopes.full.deny claude-fable-*) where=dry-run --arms
EVAL_MODEL: claude-opus-5-5 scope=full ok (allow claude-*; newest in families.claude-opus) where=dry-run --arms
EVAL_MODELS: fail refused=1/2 source=origin/main@0123456789ab:.gstack/eval-models.json
```
Each refusal also prints a result line on stderr ending in its code. `dry-run`
records the check as `checks.model_rules` in `dry-run.json` and fails
`DRY_RUN_RESULT`. `pilot start` reserves nothing. `price-gate` writes no
receipt.