Files
gstack/docs/eval-models.md
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

4.3 KiB

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

{
  "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.