Skip to content

feat(gate): route cost-regression blocking through the policy engine#189

Merged
veksen merged 1 commit into
mainfrom
feat-regression-policy-gate
Jul 22, 2026
Merged

feat(gate): route cost-regression blocking through the policy engine#189
veksen merged 1 commit into
mainfrom
feat-regression-policy-gate

Conversation

@veksen

@veksen veksen commented Jul 22, 2026

Copy link
Copy Markdown
Member

Goal

Cost-regression already blocks a PR, but through the analyzer's old inline path. A repo can set untested-data-access (#183) and schema-drift (#188) to warn/off per-repo; it couldn't do the same for regression. This routes the regression gate through the shared policy engine so it's overridable the same way.

Regression already fails today, so this adds no new blocking — only per-repo overridability.

What

Before: a beyond-threshold, untriaged cost regression always fails the check, with no way to soften it. After: the same regression still fails by default, but a repo can set the regression-beyond-threshold policy to warn (surfaced, non-blocking) or off (suppressed) in CI settings, exactly as it already can for the other two conditions.

regression-beyond-threshold is a finding in the shared taxonomy (concludes failure) and defaults to fail in DEFAULT_CONDITION_POLICIES, so a repo that configures nothing is unaffected.

How

  • src/gate/regression.ts (new) — a pure helper gateRegression(regressedCount, policyConfig) that builds the { condition: "regression-beyond-threshold", verdictClass: "finding" } verdict, runs it through resolveVerdict, and returns { conclusion } | null. Mirrors gateSchemaChange from feat(gate): block PRs with schema changes (gate the API's signal) #188. Detection is not its job; it only decides the check outcome from a count already computed upstream.
  • src/main.ts — the regression arm inside if (reportContext.comparison) now calls gateRegression and emits core.setFailed on failure, core.warning on neutral. The per-query message text (previews, cost delta, dashboard links) is unchanged. Regression and new-query now emit their own annotations instead of one joined setFailed, consistent with how the test-presence gate already calls setFailed independently just above.

regressed is already the untriaged, beyond-threshold set (compareRuns filters by acknowledgedQueryHashes and regressionThreshold), so the gate does not re-filter.

New-query gate left untouched, on purpose. gateEligibleNewQueries (a new query shipping a high-impact index) currently blocks via inline setFailed. But new-query defaults to warn in DEFAULT_CONDITION_POLICIES, so routing it through resolveVerdict would silently stop it blocking. That may be desirable, but it's a behavior change to sign off on separately. This PR is regression only.

Triage vs. policy — two override layers

regressed is untriaged already, so there are now two independent override layers:

  • Per-query triage (acknowledge/resolve/ignore) removes an individual regression from the set.
  • Per-repo regression-beyond-threshold policy applies to whatever survives triage: off suppresses the whole condition regardless of triage; warn surfaces all untriaged regressions without blocking; fail (default) blocks them as today.

Tests

  • src/gate/regression.test.ts — the pure helper: blocks by default, warnneutral, offnull, zero regressions → null.
  • Full suite green: npm test -- run → 321 passing (32 files). Typecheck clean; build succeeds. (The pre-existing site-api.test.ts red and the api-client.ts type errors noted in the hand-off were already resolved on main by the @query-doctor/core ^0.14.0 adoption.)

🤖 Generated with Claude Code

Cost-regression already blocks a PR, but through the analyzer's old inline
path, so a repo couldn't set it to warn/off the way it can for
untested-data-access (#183) and schema-drift (#188). Route the regression arm
through resolveVerdict/policyFor, matching those two conditions.

regression-beyond-threshold is a finding in the shared taxonomy and defaults
to fail, so a repo that sets nothing blocks exactly as before. warn caps it at
a surfaced, non-blocking neutral; off suppresses it.

The new-query gate is left on its inline setFailed: its default policy is warn,
so routing it through resolveVerdict would silently stop it blocking — a
behavior change that needs a separate human sign-off.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query Doctor Analysis

3 queries analyzed

0 regressed · 0 improved · 0 new · 0 removed

2 pre-existing issues

Using assumed statistics (10000000 rows/table). For better results, sync production stats.

More detail → get_ci_run({ runId: "019f8b70-00d7-7a0d-8744-52a307a36dee" }) · view run · docs

@veksen
veksen merged commit 4395e49 into main Jul 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant