From 94fc282c50d2e3799de53753bfe2740e7fa4e90a Mon Sep 17 00:00:00 2001 From: cleanjunc <277128341+cleanjunc@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:47:40 +0000 Subject: [PATCH] test(engine): make harness-submission-trigger coverage visible to Codecov MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `packages/loopover-engine/src/miner/harness-submission-trigger.ts` (#2337) is the final gate before a real call site may build an `open_pr` local-write spec from a passing `HandoffPacket`: `evaluateHarnessSubmissionTrigger` checks a session-level circuit breaker FIRST (N consecutive gate-blocks pauses the run, never un-tripped by a later candidate), then delegates to the separately-tested `shouldSubmit`. It is fully exercised by the engine package's own `node --test` suite, but that runner is not part of the root vitest run Codecov reads `codecov/patch` from, so it reports as ~0% covered despite being genuinely tested (same blind spot as #6250). Add a root-level vitest twin importing `evaluateHarnessSubmissionTrigger` and `DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS` via the engine barrel and mirroring every scenario the package suite covers: the circuit-breaker boundary (below/at/above the ceiling, with the tripped path never consulting shouldSubmit), the `?? DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS` fallback (both arms), and the delegation outcomes (clean allow, kill-switch, predicted-gate failure, slop-over-threshold, observe mode). Fixtures built exactly as the package suite does; 100% line + branch of the source locally. Test-only: no change to any file under `packages/loopover-engine/src/**` or `packages/loopover-engine/test/**`. Also extend the engine README's Codecov-mirror note — the doc touch keeps this a full-coverage CI run so the sharded coverage blobs reach the merge step (a root-`test/**`-only diff is otherwise treated as a scoped, artifact-free run). Closes #8346 --- packages/loopover-engine/README.md | 5 +- test/unit/harness-submission-trigger.test.ts | 155 +++++++++++++++++++ 2 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 test/unit/harness-submission-trigger.test.ts diff --git a/packages/loopover-engine/README.md b/packages/loopover-engine/README.md index d40819039..08b2ef7ad 100644 --- a/packages/loopover-engine/README.md +++ b/packages/loopover-engine/README.md @@ -36,8 +36,9 @@ flags, so it works on the whole declared `engines` range. Codecov (`codecov/patch`) only reads the root vitest suite, so modules that need a Codecov-visible mirror also have a root test under `test/unit/` (e.g. `test/unit/reviewer-consensus-calibration.test.ts` for `src/reviewer-consensus-calibration.ts` — #8349, or `test/unit/signal-tracking.test.ts` for -`src/calibration/signal-tracking.ts` — #8343). The package-local `node:test` suite remains the package's own -gate; the root mirror is what makes the same scenarios gradeable by Codecov. +`src/calibration/signal-tracking.ts` — #8343, or `test/unit/harness-submission-trigger.test.ts` for +`src/miner/harness-submission-trigger.ts` — #8346). The package-local `node:test` suite remains the package's +own gate; the root mirror is what makes the same scenarios gradeable by Codecov. ## `opportunity-ranker` diff --git a/test/unit/harness-submission-trigger.test.ts b/test/unit/harness-submission-trigger.test.ts new file mode 100644 index 000000000..1f8a869ea --- /dev/null +++ b/test/unit/harness-submission-trigger.test.ts @@ -0,0 +1,155 @@ +// Root-level vitest coverage twin for `packages/loopover-engine/src/miner/harness-submission-trigger.ts` (#8346). +// +// `evaluateHarnessSubmissionTrigger` (#2337) is the final gate before a real call site may build an `open_pr` +// local-write spec from a passing `HandoffPacket`: it checks a session-level circuit breaker FIRST, then +// delegates to the separately-tested `shouldSubmit`. It is live and fully exercised by the engine package's own +// `node --test` suite (`packages/loopover-engine/test/harness-submission-trigger.test.ts`), but that runner is +// not part of the root vitest run Codecov reads `codecov/patch` from, so it reports as ~0% covered despite +// being genuinely tested (same blind spot as #6250). This twin imports the primitive via the engine barrel and +// mirrors every scenario the package suite covers — matching the sibling pattern in +// `test/unit/calibration-dashboard.test.ts`. Fixtures are built the same way the package suite does; no source +// file is modified. +import { describe, expect, it } from "vitest"; +import { + DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS, + evaluateHarnessSubmissionTrigger, +} from "../../packages/loopover-engine/src/index"; +import type { + HandoffPacket, + HarnessSubmissionTriggerCandidate, + PredictedGateVerdict, + SelfReviewSlopAssessment, + SelfReviewVerdict, +} from "../../packages/loopover-engine/src/index"; + +function passingVerdictFields(): PredictedGateVerdict { + return { + predicted: true, + basis: "public_config", + pack: "oss-anti-slop", + conclusion: "success", + title: "t", + summary: "s", + readinessScore: 92, + confirmedContributor: undefined, + blockers: [], + warnings: [], + funnel: null, + note: "", + }; +} + +function failingVerdictFields(): PredictedGateVerdict { + return { + ...passingVerdictFields(), + conclusion: "failure", + blockers: [{ code: "duplicate_pr_risk", title: "t", detail: "d" }], + }; +} + +function slop(band: SelfReviewSlopAssessment["band"]): SelfReviewSlopAssessment { + return { slopRisk: 0, band, findings: [] }; +} + +function selfReviewVerdict(overrides: Partial = {}): SelfReviewVerdict { + return { + predictedGateVerdict: passingVerdictFields(), + slopAssessment: slop("clean"), + changedPaths: ["src/upload.ts"], + passesPredictedGate: true, + ...overrides, + }; +} + +function handoffPacket(verdictOverrides: Partial = {}): HandoffPacket { + return { + worktreePath: "/tmp/attempt-1", + diffSummary: "added retry logic", + selfReviewVerdict: selfReviewVerdict(verdictOverrides), + attemptLogReference: "attempt-1", + }; +} + +function baseCandidate(overrides: Partial = {}): HarnessSubmissionTriggerCandidate { + return { + killSwitchScope: "none", + handoffPacket: handoffPacket(), + slopThreshold: "low", + mode: "enforce", + consecutiveGateBlocks: 0, + ...overrides, + }; +} + +describe("barrel: the harness submission trigger is re-exported from the engine entrypoint (#2337)", () => { + it("exposes the trigger function and the default ceiling constant", () => { + expect(typeof evaluateHarnessSubmissionTrigger).toBe("function"); + expect(typeof DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS).toBe("number"); + }); +}); + +describe("evaluateHarnessSubmissionTrigger — circuit breaker", () => { + it("N consecutive blocks trips it, refusing even an otherwise-clean handoff, without consulting shouldSubmit (boundary: exactly max)", () => { + const decision = evaluateHarnessSubmissionTrigger(baseCandidate({ consecutiveGateBlocks: 3, maxConsecutiveGateBlocks: 3 })); + expect(decision.allow).toBe(false); + expect(decision.circuitBreakerTripped).toBe(true); + expect(decision.reasons).toEqual(["circuit_breaker_tripped_after_consecutive_blocks:3>=3"]); + }); + + it("below the ceiling still proceeds to consult shouldSubmit (>= not-taken arm)", () => { + const decision = evaluateHarnessSubmissionTrigger(baseCandidate({ consecutiveGateBlocks: 2, maxConsecutiveGateBlocks: 3 })); + expect(decision.allow).toBe(true); + expect(decision.circuitBreakerTripped).toBe(false); + }); + + it("the default ceiling applies via the `?? DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS` fallback when the override is omitted (both arms)", () => { + const justUnder = evaluateHarnessSubmissionTrigger( + baseCandidate({ consecutiveGateBlocks: DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS - 1 }), + ); + expect(justUnder.allow).toBe(true); + + const atDefault = evaluateHarnessSubmissionTrigger( + baseCandidate({ consecutiveGateBlocks: DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS }), + ); + expect(atDefault.allow).toBe(false); + expect(atDefault.circuitBreakerTripped).toBe(true); + }); +}); + +describe("evaluateHarnessSubmissionTrigger — delegation to shouldSubmit (below the breaker)", () => { + it("a passing handoff with the breaker well clear allows, forwarding shouldSubmit's empty reasons", () => { + const decision = evaluateHarnessSubmissionTrigger(baseCandidate()); + expect(decision).toEqual({ allow: true, reasons: [], circuitBreakerTripped: false }); + }); + + it("the kill-switch is forwarded to shouldSubmit's own check, blocking an otherwise-clean handoff", () => { + const decision = evaluateHarnessSubmissionTrigger(baseCandidate({ killSwitchScope: "global" })); + expect(decision.allow).toBe(false); + expect(decision.circuitBreakerTripped).toBe(false); + expect(decision.reasons).toEqual(["global_kill_switch_active"]); + }); + + it("a handoff whose verdict fails predicted-gate is blocked by shouldSubmit, not the circuit breaker", () => { + const decision = evaluateHarnessSubmissionTrigger( + baseCandidate({ handoffPacket: handoffPacket({ predictedGateVerdict: failingVerdictFields(), passesPredictedGate: false }) }), + ); + expect(decision.allow).toBe(false); + expect(decision.circuitBreakerTripped).toBe(false); + expect(decision.reasons.some((r) => r.startsWith("predicted_gate_not_passing"))).toBe(true); + }); + + it("a handoff whose slop assessment exceeds the configured threshold is blocked by shouldSubmit", () => { + const decision = evaluateHarnessSubmissionTrigger( + baseCandidate({ handoffPacket: handoffPacket({ slopAssessment: slop("high") }), slopThreshold: "low" }), + ); + expect(decision.allow).toBe(false); + expect(decision.reasons).toEqual(["slop_band_exceeds_threshold:high>low"]); + }); + + it("observe mode forces allow: false even for an otherwise-clean handoff, below the circuit breaker", () => { + const decision = evaluateHarnessSubmissionTrigger(baseCandidate({ mode: "observe" })); + expect(decision.allow).toBe(false); + expect(decision.circuitBreakerTripped).toBe(false); + expect(decision.reasons).toEqual(["observe_mode_active:would_have_allowed"]); + }); +});