Skip to content

config-as-code: sweep-watchdog staleness threshold is hardcoded, unlike its own enabled flag #6594

Description

@JSONbored

Context

src/review/sweep-watchdog.ts implements the fleet-wide sweep-liveness watchdog (flag-gated by LOOPOVER_SWEEP_WATCHDOG): it detects when the scheduled regate sweep has stopped advancing a repo's last_regated_at marker and re-enqueues a targeted regate job.

The watchdog's enabled/disabled state is already full config-as-code: isSweepWatchdogEnabled() (src/review/sweep-watchdog.ts:30-36) reads a top-level sweepWatchdog: block from .loopover.yml (SweepWatchdogManifestOverride, parsed by parseSweepWatchdogConfig / typed as FocusManifestSweepWatchdogConfig in packages/loopover-engine/src/focus-manifest.ts) before falling back to the LOOPOVER_SWEEP_WATCHDOG env var.

However, the staleness threshold itself is a bare, unconfigurable constant:

// src/review/sweep-watchdog.ts:80
export const SWEEP_STALENESS_THRESHOLD_MS = 45 * 60 * 1000;

// src/review/sweep-watchdog.ts:82-87
export function isSweepStale(input: { openPullRequestCount: number; lastRegatedAt: string | null; nowMs: number }): boolean {
  if (input.openPullRequestCount === 0) return false;
  const lastMs = input.lastRegatedAt ? Date.parse(input.lastRegatedAt) : NaN;
  if (!Number.isFinite(lastMs)) return true;
  return input.nowMs - lastMs > SWEEP_STALENESS_THRESHOLD_MS;
}

isSweepStale has exactly one call site (src/review/sweep-watchdog.ts:161) and there is no code path anywhere that lets an operator change the 45-minute window — a self-hoster whose sweep cadence or repo count makes 45 minutes too tight (false-positive nudges) or too loose (slower incident detection) has no way to tune it short of editing this constant and redeploying. The existing sweepWatchdog: manifest block (top-level in .loopover.yml, same shape as ops:/publicStats:) is the natural, already-established place for this: it currently parses only { enabled: boolean } (see parseSweepWatchdogConfig in packages/loopover-engine/src/focus-manifest.ts), with no threshold field at all.

Requirements

  1. Add an optional staleAfterMinutes (or equivalently named) numeric field to the sweepWatchdog: top-level manifest block, parsed alongside the existing enabled field in parseSweepWatchdogConfig (packages/loopover-engine/src/focus-manifest.ts).
  2. SweepWatchdogManifestOverride (src/review/sweep-watchdog.ts) and FocusManifestSweepWatchdogConfig (packages/loopover-engine/src/focus-manifest.ts) must both carry the new field through their existing present/enabled shape (e.g. { present: boolean; enabled: boolean; staleAfterMinutes: number | null }), matching the sparse-override pattern already used elsewhere in this file (pickOverlayNullable).
  3. isSweepStale (src/review/sweep-watchdog.ts:82) must accept an optional threshold override parameter (milliseconds) that, when provided, replaces SWEEP_STALENESS_THRESHOLD_MS in its comparison. SWEEP_STALENESS_THRESHOLD_MS remains the exported default used when no override is configured — do not remove it.
  4. The manifest-configured staleAfterMinutes (converted to milliseconds) must be threaded from the config resolution point through to the isSweepStale call at src/review/sweep-watchdog.ts:161.
  5. Invalid input (non-numeric, zero, or negative staleAfterMinutes) must be rejected by the parser with a warning pushed to the existing warnings array (mirroring how normalizeOptionalBoolean reports a bad enabled value in the same function), falling back to the default SWEEP_STALENESS_THRESHOLD_MS — never producing a zero/negative/NaN threshold at runtime.
  6. When staleAfterMinutes is absent or the manifest block itself is absent (present: false), behavior must be byte-identical to today (45-minute default) — this is an additive, backward-compatible field, not a behavior change for repos that don't set it.

Deliverables

  • staleAfterMinutes field added to FocusManifestSweepWatchdogConfig and its parser/serializer (parseSweepWatchdogConfig, sweepWatchdogConfigToJson) in packages/loopover-engine/src/focus-manifest.ts.
  • SweepWatchdogManifestOverride type and its consumption in src/review/sweep-watchdog.ts updated to carry the new field.
  • isSweepStale accepts an optional threshold-override parameter; default behavior unchanged when omitted.
  • The .loopover.yml bundled fallback (src/config/loopover-repo-focus-manifest.ts) and the repo-root .loopover.yml doc comments updated to mention the new field where the sweepWatchdog: block is documented (or where it would be documented if not already present).

Test Coverage Requirements

This repo's Codecov patch gate is 99%+, hard, for src/**/packages/** — all touched files (packages/loopover-engine/src/focus-manifest.ts, src/review/sweep-watchdog.ts) fall under that gate. Add:

  • Parser tests: valid staleAfterMinutes, absent field (defaults to null/no-override), invalid values (string, zero, negative, NaN) each produce the documented warning and fall back safely.
  • isSweepStale tests: explicit override threshold changes the stale/not-stale boundary; omitted override preserves today's 45-minute (SWEEP_STALENESS_THRESHOLD_MS) behavior exactly (reuse the existing boundary-condition test style already in test/unit/sweep-watchdog.test.ts).
  • Round-trip test: sweepWatchdogConfigToJsonparseSweepWatchdogConfig preserves staleAfterMinutes (matching the existing round-trip coverage pattern for enabled).

Expected Outcome

A self-hoster can set sweepWatchdog: { enabled: true, staleAfterMinutes: 90 } in .loopover.yml to widen (or narrow) the sweep-liveness staleness window without editing code, exactly as they can already toggle enabled. Omitting staleAfterMinutes continues to use today's fixed 45-minute threshold with no behavior change.

Links & Resources

  • src/review/sweep-watchdog.ts (the module; SWEEP_STALENESS_THRESHOLD_MS at line 80, isSweepStale at line 82, call site at line 161)
  • packages/loopover-engine/src/focus-manifest.ts (FocusManifestSweepWatchdogConfig type, parseSweepWatchdogConfig, sweepWatchdogConfigToJson)
  • src/config/loopover-repo-focus-manifest.ts (bundled .loopover.yml fallback / documentation pattern for top-level fleet-wide config blocks like ops:/publicStats:)
  • test/unit/sweep-watchdog.test.ts (existing test file to extend)

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:featureGittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions