enhance(#2229): add three-way claim disposition (admit/refute/abstain) to /gsd-explore research pass (#2543)

Closes #2229.

Each claim surfaced by /gsd-explore's research pass is dispositioned admit, refute, or abstain, with abstentions routed to a visible ledger instead of being smoothed into confident prose. Refute and abstain are separated by whether the disagreeing source is authoritative for that claim; a strong prior is never authoritative alone.

Two guards ride with it: conflict-abstention, and a tier floor that presents a would-be admit as an abstain when the researcher's resolved tier is the budget tier or cannot be determined.

To make that floor enforceable, resolve-model now emits the effective tier (--pick tier). It was already computed above the resolve_model_ids omit gate but was unreachable from a workflow, which left the floor inert on every non-Claude install - the model id is blank under omit and runtime-substituted where a tier map exists, and the profile defaults to balanced. The tier signal mirrors every resolution step that can change which tier runs, including the model_policy preset, and reports unknown rather than guessing. Output is additive; model, profile and effort are unchanged.

Two residuals are disclosed in the workflow rather than papered over: a raw-model-id model_overrides pin reports unknown and is floored (fails closed), and a model_profile_overrides entry repointing a tier at another tier's model can under-report (fails open, and predates this change).

Admin merge used only to satisfy the missing secondary reviewer on a single-maintainer PR. No CI failure and no conflict were bypassed: 38 checks green, remote runner 32255/32255 on both Node lanes.
This commit is contained in:
Rezolv
2026-08-09 23:05:41 -04:00
committed by GitHub
parent 693f12ad56
commit 2dbee3ebdd
12 changed files with 1079 additions and 25 deletions

View File

@@ -33,7 +33,7 @@ import planningScopeMod = require('./planning-scope.cjs');
const { SCOPE } = planningScopeMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import modelResolverMod = require('./model-resolver.cjs');
const { resolveModelInternal, resolveModelForTier, resolveProviderEscalation, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, resolveGranularityInternal, assertValidGranularityOverride } = modelResolverMod;
const { resolveModelInternal, resolveTierInternal, resolveModelForTier, resolveProviderEscalation, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, resolveGranularityInternal, assertValidGranularityOverride } = modelResolverMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import agentCommandRouterMod = require('./agent-command-router.cjs');
const { AGENT_FAILURE_CLASSES } = agentCommandRouterMod;
@@ -516,10 +516,20 @@ function cmdResolveModel(cwd: string, agentType: string | undefined, raw: boolea
const model = resolveModelInternal(cwd, agentType!);
const effort = resolveEffortInternal(cwd, agentType!);
const agentModels = (MODEL_PROFILES as Record<string, unknown>)[agentType!];
// Own-property guard: agentType is an unvalidated CLI positional, so a
// prototype-chain value ("toString", "constructor") would otherwise return
// an inherited truthy member from this plain object and misreport a
// genuinely unknown agent as known (unknown_agent dropped from the result).
const agentModelsMap = MODEL_PROFILES as Record<string, unknown>;
const agentModels = Object.hasOwn(agentModelsMap, agentType!) ? agentModelsMap[agentType!] : undefined;
// #2229: `tier` is additive — existing keys and their values are untouched, so
// every `--pick model` / `--pick profile` / `--raw` consumer is unaffected. It
// exists because the model id is deliberately blank under resolve_model_ids:"omit",
// which leaves a tier-sensitive guard with nothing to read.
const tier = resolveTierInternal(cwd, agentType!);
const result = agentModels
? { model, profile, effort }
: { model, profile, effort, unknown_agent: true };
? { model, profile, effort, tier }
: { model, profile, effort, tier, unknown_agent: true };
output(result, raw, model);
}
@@ -594,7 +604,12 @@ function cmdResolveExecution(cwd: string, agentType: string | undefined, raw: bo
const fastModeSupported = RUNTIMES_WITH_FAST_MODE.has(runtime);
const agentModels = (MODEL_PROFILES as Record<string, unknown>)[agentType!];
// Own-property guard: agentType is an unvalidated CLI positional, so a
// prototype-chain value ("toString", "constructor") would otherwise return
// an inherited truthy member from this plain object and misreport a
// genuinely unknown agent as known (unknown_agent dropped from the result).
const agentModelsMap = MODEL_PROFILES as Record<string, unknown>;
const agentModels = Object.hasOwn(agentModelsMap, agentType!) ? agentModelsMap[agentType!] : undefined;
const result: Record<string, unknown> = {
model,
profile,