Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/loopover-contract/src/api-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { z } from "zod";

import { checkBeforeStartSchema, slopRiskSchema, validateFocusManifestSchema, validateLinkedIssueSchema } from "./api-requests.js";
import { AGENT_ACTION_CLASSES, AUTONOMY_LEVELS } from "./enums.js";
import { MAX_CONTRIBUTOR_OPEN_ITEM_CAP, MAX_REVIEW_NAG_COOLDOWN_DAYS } from "./limits.js";
import { MAX_CONTRIBUTOR_OPEN_ITEM_CAP, MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES, MAX_REVIEW_NAG_COOLDOWN_DAYS } from "./limits.js";

export const FindingSchema = z
.object({
Expand Down Expand Up @@ -465,6 +465,8 @@ export const RepositorySettingsSchema = z
gateDryRun: z.boolean().optional(),
premergeContentRecheck: z.boolean().optional(),
requireFreshRebaseWindowMinutes: z.number().int().positive().nullable().optional(),
// #9738: non-negative, not positive -- 0 is the documented way to turn the window off.
priorityEligibilityWindowMinutes: z.number().int().min(0).max(MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES).nullable().optional(),
staleBaseAheadByThreshold: z.number().int().positive().nullable().optional(),
mergeReadinessGateMode: z.enum(["off", "advisory", "block"]),
manifestPolicyGateMode: z.enum(["off", "advisory", "block"]),
Expand Down
2 changes: 2 additions & 0 deletions packages/loopover-contract/src/limits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,5 @@ export const PUBLIC_SURFACE_SKIP_REASONS = [
export const MAX_CONTRIBUTOR_OPEN_ITEM_CAP = 100;
/** src/settings/agent-actions.ts -- keeps the review-nag lookback from overflowing Date arithmetic. */
export const MAX_REVIEW_NAG_COOLDOWN_DAYS = 365;
/** src/review/priority-eligibility-window.ts (#9738) -- one day, the longest an eligibility hold may last. */
export const MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES = 24 * 60;
46 changes: 46 additions & 0 deletions test/unit/contract-limits-pinned.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// The Worker-side bounds that @loopover/contract restates must equal their originals (#9773 follow-up).
//
// WHY THIS EXISTS. limits.ts says these entries are "pinned against their originals like every other entry
// here" -- but PREFLIGHT_LIMITS was the only group with a meta-test actually doing the pinning. The three
// single constants were restated on trust, which is the same thing as not being pinned.
//
// The failure this catches is quiet and one-sided: the contract package cannot import the Worker's `src/`
// (it is a zod-only leaf, which is the property every other surface depends on), so nothing at compile time
// relates the two copies. Raise a bound on the Worker side alone and the published schema keeps rejecting
// input the server would now accept; lower it alone and the schema accepts input the server then rejects or
// truncates. Either way the mismatch surfaces as a confusing client-side validation error rather than as a
// build failure.
//
// This is not a hypothetical. gen-contract-api-schemas.ts copies these schemas verbatim, so a copied schema
// referencing a constant that has NOT been restated here emits a file that does not compile -- the
// generator's own doc calls that "the loud failure this wants". #9738 added
// `.max(MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES)` to the settings schema without adding the constant, which
// left `main` failing `contract:api-schemas:check` for every PR: regenerating produced an uncompilable file,
// and not regenerating left the check red. The constant is now restated; this test is what keeps the VALUES
// together from here on, which the compile-time failure alone never did.
import { describe, expect, it } from "vitest";

import {
MAX_CONTRIBUTOR_OPEN_ITEM_CAP as CONTRACT_MAX_CONTRIBUTOR_OPEN_ITEM_CAP,
MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES as CONTRACT_MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES,
MAX_REVIEW_NAG_COOLDOWN_DAYS as CONTRACT_MAX_REVIEW_NAG_COOLDOWN_DAYS,
} from "../../packages/loopover-contract/src/limits";
import { MAX_REVIEW_NAG_COOLDOWN_DAYS } from "../../src/settings/agent-actions";
import { MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES } from "../../src/review/priority-eligibility-window";
import { MAX_CONTRIBUTOR_OPEN_ITEM_CAP } from "../../src/types";

describe("@loopover/contract restated Worker bounds stay pinned to their originals", () => {
// One case per constant rather than a table, so a failure names the specific bound that drifted and the
// file it has to be reconciled with.
it("MAX_CONTRIBUTOR_OPEN_ITEM_CAP matches src/types.ts", () => {
expect(CONTRACT_MAX_CONTRIBUTOR_OPEN_ITEM_CAP).toBe(MAX_CONTRIBUTOR_OPEN_ITEM_CAP);
});

it("MAX_REVIEW_NAG_COOLDOWN_DAYS matches src/settings/agent-actions.ts", () => {
expect(CONTRACT_MAX_REVIEW_NAG_COOLDOWN_DAYS).toBe(MAX_REVIEW_NAG_COOLDOWN_DAYS);
});

it("MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES matches src/review/priority-eligibility-window.ts", () => {
expect(CONTRACT_MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES).toBe(MAX_PRIORITY_ELIGIBILITY_WINDOW_MINUTES);
});
});