Skip to content

Commit 44c2661

Browse files
authored
Merge branch 'main' into docs/discovery-plane-operator-guide-finalize
2 parents 1d8e8b1 + e9adc75 commit 44c2661

50 files changed

Lines changed: 994 additions & 45 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,7 +1028,21 @@ jobs:
10281028
# ~2,900-file suite is 0% covered, polluting Codecov's project trend on every scoped PR. Real
10291029
# coverage for files actually exercised is unaffected either way.
10301030
SCOPE_ARGS+=(--changed=origin/main --coverage.all=false)
1031-
npm run test:coverage -- --maxWorkers=4 --shard=${{ matrix.shard }}/3 --reporter=default --reporter=blob --reporter=junit --outputFile.blob=blob-report/report-${{ matrix.shard }}.blob --outputFile.junit=reports/junit/vitest.xml "${EXCLUDE_ARGS[@]}" "${SCOPE_ARGS[@]}"
1031+
# A scoped PR touching zero files any test imports (a pure docs/comment-only diff under a
1032+
# scoped path, e.g. packages/loopover-miner/docs/**) is a real, valid outcome here -- vitest
1033+
# itself prints "No test files found, exiting with code 0" and exits clean, writing no
1034+
# coverage/lcov.info at all (nothing was instrumented). Confirmed live: PR #8165, a pure
1035+
# packages/loopover-miner/docs + apps/loopover-ui/content/docs mdx change, matched zero test
1036+
# files and failed "Verify coverage report exists" even though nothing was actually broken.
1037+
# Capture vitest's own stdout (the same "No test files found" string this file's own comment
1038+
# above already documents relying on) rather than re-deriving the zero-files case from
1039+
# scratch, and record it as a step output so "Verify coverage report exists" below can tell
1040+
# this apart from a real crash that should still fail loudly.
1041+
set -o pipefail
1042+
npm run test:coverage -- --maxWorkers=4 --shard=${{ matrix.shard }}/3 --reporter=default --reporter=blob --reporter=junit --outputFile.blob=blob-report/report-${{ matrix.shard }}.blob --outputFile.junit=reports/junit/vitest.xml "${EXCLUDE_ARGS[@]}" "${SCOPE_ARGS[@]}" 2>&1 | tee vitest-scoped-output.log
1043+
if grep -q "No test files found" vitest-scoped-output.log; then
1044+
echo "no_tests_matched=true" >> "$GITHUB_OUTPUT"
1045+
fi
10321046
else
10331047
# Duration-aware sharding (#ci-duration-aware-sharding), full-suite case only: vitest's own
10341048
# --shard splits by file COUNT alone, no duration awareness -- confirmed via real per-shard CI
@@ -1060,7 +1074,12 @@ jobs:
10601074
echo "Coverage itself is gated by Codecov on changed lines (codecov/patch), computed from all shards' merged lcov."
10611075
echo "Reproduce locally with: 'npm run test:coverage' (unsharded, runs the whole suite)."
10621076
- name: Verify coverage report exists
1063-
if: ${{ success() }}
1077+
# Skipped when the scoped-selection branch above matched zero test files (steps.coverage.outputs.
1078+
# no_tests_matched) -- a legitimately test-free diff (e.g. docs-only under a scoped path) writes no
1079+
# coverage/lcov.info at all, and that is not a failure. A real failure (tests ran and either failed
1080+
# or the coverage step crashed before writing output) still fails this job via `success()` below,
1081+
# same as before.
1082+
if: ${{ success() && steps.coverage.outputs.no_tests_matched != 'true' }}
10641083
run: |
10651084
if [ ! -s coverage/lcov.info ]; then
10661085
echo "::error title=Coverage::coverage/lcov.info is missing or empty"

.loopover.yml.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,16 @@ gate:
329329
# value.
330330
contentLaneDeliverable: off
331331

332+
# Backtest-regression gate (#8105). Governs what a REGRESSED verdict from the
333+
# pre-merge backtest (threshold #8138 / logic #8139) does: advisory (default)
334+
# renders the comparison in the review comment but never blocks; block
335+
# escalates it into a backtest_regression hard blocker; off silences the
336+
# backtest advisory entirely. Flip to block only once the persisted track
337+
# record (scripts/backtest-track-record.ts, #8140) supports it.
338+
# off | advisory | block. Default: advisory. DB-backed (dashboard-settable
339+
# too); this overrides the stored value.
340+
backtestRegression: advisory
341+
332342
# Gate-check dry-run. When true, the posted check conclusion remains the real
333343
# non-enforcing verdict while comments/check text may also show the would-be
334344
# stricter verdict for AI-review blocker mode. It does not disable downstream

apps/loopover-ui/content/docs/backtest-calibration.mdx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ threshold or rewrites detection logic gets scored against the real recorded hist
1515
targets, the same raw inputs — instead of being eyeballed.
1616

1717
<Callout variant="note">
18-
Everything on this page is advisory-only today. A backtest verdict — even a regression — never
19-
blocks a merge. Whether a REGRESSED verdict should ever gate merges is a tracked, deliberately
20-
separate decision that waits for real production track-record data.
18+
Everything on this page is advisory-only by default. A REGRESSED verdict blocks a merge only if a
19+
repo explicitly opts in via `backtestRegressionGateMode: block` (`.loopover.yml
20+
gate.backtestRegression`) — the shipped default is `advisory`, and the flip is meant to wait for
21+
real production track-record data.
2122
</Callout>
2223

2324
## The corpus

apps/loopover-ui/content/docs/tuning.mdx

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,12 @@ forward and wins whenever both are set for the same field.
259259
`advisory` renders the assessment in the review comment without blocking;
260260
`block` additionally lets a confidence-floor-passing "unaddressed" verdict
261261
become a blocker.
262+
- `gate.backtestRegression` — governs what a REGRESSED verdict from the
263+
[pre-merge backtest](/docs/backtest-calibration) does. Default `advisory`:
264+
the comparison renders in the review comment but never blocks. `block`
265+
escalates a REGRESSED verdict into a hard blocker — flip it only once the
266+
persisted track record supports gating. `off` silences the backtest advisory
267+
entirely.
262268
- `gate.contentLaneDeliverable` — only meaningful for a repo with a registry
263269
content-lane spec configured (`contentLane`); a no-op otherwise. Fully
264270
deterministic (a text/path match, no AI call): when the PR's primary linked
@@ -381,36 +387,42 @@ testExpectations:
381387
linkedIssuePolicy: preferred
382388
383389
# Gate policy — checkMode is set explicitly (not the legacy, ambiguous "enabled" alias)
390+
384391
gate:
385-
checkMode: visible
386-
pack: gittensor
387-
duplicates: block
388-
linkedIssue: advisory
389-
readiness:
390-
mode: advisory
391-
minScore: 70
392-
slop:
393-
mode: block
394-
minScore: 60
395-
aiAdvisory: true
396-
mergeReadiness: advisory
397-
manifestPolicy: block
398-
aiReview:
399-
mode: advisory
400-
byok: true
401-
provider: anthropic
402-
model: claude-3-5-sonnet-latest
392+
checkMode: visible
393+
pack: gittensor
394+
duplicates: block
395+
linkedIssue: advisory
396+
readiness:
397+
mode: advisory
398+
minScore: 70
399+
slop:
400+
mode: block
401+
minScore: 60
402+
aiAdvisory: true
403+
mergeReadiness: advisory
404+
manifestPolicy: block
405+
aiReview:
406+
mode: advisory
407+
byok: true
408+
provider: anthropic
409+
model: claude-3-5-sonnet-latest
403410
404411
# Generic settings overrides -- commentMode/checkRunMode/checkRunDetailLevel/badgeEnabled are
412+
405413
# config-as-code only (no DB column or dashboard toggle); this file is their sole source.
414+
406415
settings:
407-
commentMode: detected_contributors_only
408-
checkRunMode: enabled
409-
checkRunDetailLevel: standard
410-
badgeEnabled: true
411-
# Optional path holds. Omitted or [] means no path guardrails.
412-
# hardGuardrailGlobs:
413-
# - "src/selfhost/**"`}
416+
commentMode: detected_contributors_only
417+
checkRunMode: enabled
418+
checkRunDetailLevel: standard
419+
badgeEnabled: true
420+
421+
# Optional path holds. Omitted or [] means no path guardrails.
422+
423+
# hardGuardrailGlobs:
424+
425+
# - "src/selfhost/\*\*"`}
414426

415427
/>
416428

apps/loopover-ui/public/openapi.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9761,6 +9761,14 @@
97619761
"advisory",
97629762
"block"
97639763
]
9764+
},
9765+
"backtestRegressionGateMode": {
9766+
"type": "string",
9767+
"enum": [
9768+
"off",
9769+
"advisory",
9770+
"block"
9771+
]
97649772
}
97659773
},
97669774
"required": [
@@ -9782,6 +9790,7 @@
97829790
"selfAuthoredLinkedIssueGateMode",
97839791
"linkedIssueSatisfactionGateMode",
97849792
"contentLaneDeliverableGateMode",
9793+
"backtestRegressionGateMode",
97859794
"slopAiAdvisory",
97869795
"aiReviewMode",
97879796
"aiReviewByok",
@@ -10492,6 +10501,14 @@
1049210501
"advisory",
1049310502
"block"
1049410503
]
10504+
},
10505+
"backtestRegressionGateMode": {
10506+
"type": "string",
10507+
"enum": [
10508+
"off",
10509+
"advisory",
10510+
"block"
10511+
]
1049510512
}
1049610513
},
1049710514
"required": [
@@ -10513,6 +10530,7 @@
1051310530
"selfAuthoredLinkedIssueGateMode",
1051410531
"linkedIssueSatisfactionGateMode",
1051510532
"contentLaneDeliverableGateMode",
10533+
"backtestRegressionGateMode",
1051610534
"autoLabelEnabled",
1051710535
"typeLabelsEnabled",
1051810536
"gittensorLabel",

config/examples/loopover.full.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,16 @@ gate:
343343
# value.
344344
contentLaneDeliverable: off
345345

346+
# Backtest-regression gate (#8105). Governs what a REGRESSED verdict from the
347+
# pre-merge backtest (threshold #8138 / logic #8139) does: advisory (default)
348+
# renders the comparison in the review comment but never blocks; block
349+
# escalates it into a backtest_regression hard blocker; off silences the
350+
# backtest advisory entirely. Flip to block only once the persisted track
351+
# record (scripts/backtest-track-record.ts, #8140) supports it.
352+
# off | advisory | block. Default: advisory. DB-backed (dashboard-settable
353+
# too); this overrides the stored value.
354+
backtestRegression: advisory
355+
346356
# Gate-check dry-run. When true, the posted check conclusion remains the real
347357
# non-enforcing verdict while comments/check text may also show the would-be
348358
# stricter verdict for AI-review blocker mode. It does not disable downstream
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Backtest-regression gate (#8105, epic #8082): advisory by default -- byte-identical behavior for every
2+
-- existing row (the shipped #8138/#8142 comment-only advisory). block escalates a REGRESSED pre-merge
3+
-- backtest verdict into a backtest_regression hard blocker; off silences the backtest advisory entirely.
4+
-- The flip to block is deliberately a config change made only once #8140's persisted track record supports
5+
-- it -- see #8105's own do-not-gate-before-data boundary.
6+
ALTER TABLE repository_settings ADD COLUMN backtest_regression_gate_mode TEXT NOT NULL DEFAULT 'advisory';

packages/loopover-engine/src/advisory/gate-advisory.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ export type GateCheckPolicy = {
9999
* on this mode; see runContentLaneDeliverableCheckForAdvisory, src/queue/processors.ts on the host side),
100100
* so this branch only matters once a repo has explicitly opted into `advisory`/`block`. */
101101
contentLaneDeliverableGateMode?: GateRuleMode | undefined;
102+
/** Backtest-regression gate (#8105): `block` turns a `backtest_regression` finding into a hard blocker.
103+
* Default `advisory` — the finding only exists in block mode (the host resolver pushes it there), so
104+
* this branch is defense-in-depth, mirroring content_lane_deliverable_missing's above. */
105+
backtestRegressionGateMode?: GateRuleMode | undefined;
102106
/** CLA / license-compatibility gate (#2564). When `block`, a `cla_consent_missing` finding — raised when
103107
* neither configured detection method (a consent phrase in the PR body, or a named CLA-bot check-run
104108
* conclusion) confirms consent — becomes a hard blocker. `off` (default) = no finding at all; `advisory` =
@@ -636,6 +640,9 @@ function isConfiguredGateBlocker(finding: AdvisoryFinding, policy: GateCheckPoli
636640
// opts in with `block`. Fully deterministic (no AI judgment involved), so it is exempt from the
637641
// close-precision circuit breaker on the host side.
638642
if (code === "content_lane_deliverable_missing") return gatePolicyBlocks(policy.contentLaneDeliverableGateMode, "off");
643+
// Backtest-regression gate (#8105): blocks only under an explicit opt-in; default advisory (the shipped
644+
// pre-#8105 behavior). The finding itself only exists in block mode -- see the host-side resolver.
645+
if (code === "backtest_regression") return gatePolicyBlocks(policy.backtestRegressionGateMode, "advisory");
639646
// Lockfile-tamper-risk gate (#2563): blocks only when the maintainer opts in with `block`. Defaults to `off`
640647
// (the finding is never even produced — see maybeAddLockfileTamperFinding's mode gate in queue/processors.ts),
641648
// so this branch only matters once a repo has explicitly turned the scan on.

packages/loopover-engine/src/focus-manifest.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ export type FocusManifestGateConfig = {
165165
* `linkedIssueSatisfaction` immediately above in shape, but is a purely structural check, not an AI
166166
* opinion, so it carries none of that feature's AI-budget/confidence-floor machinery. */
167167
contentLaneDeliverable: GateRuleMode | null;
168+
/** `gate.backtestRegression` (#8105, epic #8082): off|advisory|block, advisory by default — governs what a
169+
* REGRESSED pre-merge backtest verdict (#8138 threshold / #8139 logic) does. `advisory` renders the
170+
* comparison but never blocks (the shipped pre-#8105 behavior); `block` escalates it into a
171+
* `backtest_regression` hard blocker; `off` silences the backtest advisory entirely. DB-backed
172+
* (dashboard-settable too); this overrides the stored value like every other `gate:` field. */
173+
backtestRegression: GateRuleMode | null;
168174
dryRun: boolean | null;
169175
/** `gate.premergeContentRecheck` (#2550): for a PR touching `migrations/**`, re-verify against a live,
170176
* freshly-fetched tip of the base branch — unioned with this PR's own new migration filenames — for a
@@ -1303,6 +1309,7 @@ const EMPTY_GATE_CONFIG: FocusManifestGateConfig = {
13031309
selfAuthoredLinkedIssue: null,
13041310
linkedIssueSatisfaction: null,
13051311
contentLaneDeliverable: null,
1312+
backtestRegression: null,
13061313
dryRun: null,
13071314
premergeContentRecheck: null,
13081315
requireFreshRebaseWindowMinutes: null,
@@ -1788,6 +1795,7 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu
17881795
selfAuthoredLinkedIssue: normalizeOptionalGateMode(record.selfAuthoredLinkedIssue, "gate.selfAuthoredLinkedIssue", warnings),
17891796
linkedIssueSatisfaction: normalizeOptionalGateMode(record.linkedIssueSatisfaction, "gate.linkedIssueSatisfaction", warnings),
17901797
contentLaneDeliverable: normalizeOptionalGateMode(record.contentLaneDeliverable, "gate.contentLaneDeliverable", warnings),
1798+
backtestRegression: normalizeOptionalGateMode(record.backtestRegression, "gate.backtestRegression", warnings),
17911799
dryRun: normalizeOptionalBoolean(record.dryRun, "gate.dryRun", warnings),
17921800
premergeContentRecheck: normalizeOptionalBoolean(record.premergeContentRecheck, "gate.premergeContentRecheck", warnings),
17931801
requireFreshRebaseWindowMinutes: normalizeOptionalPositiveInteger(record.requireFreshRebaseWindow, "gate.requireFreshRebaseWindow", warnings),
@@ -1845,6 +1853,7 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu
18451853
gate.selfAuthoredLinkedIssue !== null ||
18461854
gate.linkedIssueSatisfaction !== null ||
18471855
gate.contentLaneDeliverable !== null ||
1856+
gate.backtestRegression !== null ||
18481857
gate.dryRun !== null ||
18491858
gate.premergeContentRecheck !== null ||
18501859
gate.requireFreshRebaseWindowMinutes !== null ||

scripts/check-docs-drift.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ export const GATE_MODE_MANIFEST: GateModeManifestRow[] = [
240240
{ field: "selfAuthoredLinkedIssueGateMode", aliases: ["selfAuthoredLinkedIssueGateMode", "gate.selfAuthoredLinkedIssue"], pages: ["how-reviews-work.mdx", "tuning.mdx", "github-app.mdx"] },
241241
{ field: "linkedIssueSatisfactionGateMode", aliases: ["linkedIssueSatisfactionGateMode", "gate.linkedIssueSatisfaction"], pages: ["how-reviews-work.mdx", "tuning.mdx", "github-app.mdx"] },
242242
{ field: "contentLaneDeliverableGateMode", aliases: ["contentLaneDeliverableGateMode", "gate.contentLaneDeliverable"], pages: ["how-reviews-work.mdx", "tuning.mdx", "github-app.mdx"] },
243+
{ field: "backtestRegressionGateMode", aliases: ["backtestRegressionGateMode", "gate.backtestRegression"], pages: ["backtest-calibration.mdx", "tuning.mdx"] },
243244
{ field: "moderationGateMode", aliases: ["moderationGateMode", "settings.moderationGateMode"], pages: ["how-reviews-work.mdx", "tuning.mdx", "github-app.mdx"] },
244245
];
245246

0 commit comments

Comments
 (0)