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
12 changes: 12 additions & 0 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ import { isRagEnabled } from "../review/rag-wire";
import { getPublicStats, isPublicStatsEnabled, resolvePublicStatsManifestOverride } from "../review/public-stats";
import { loadPublicAccuracyTrend } from "../services/public-accuracy-trend";
import { loadCalibrationTrend } from "../services/rule-calibration-trend";
import { isSatisfactionFloorAutotuneEnabled, runSatisfactionFloorLoosening } from "../services/satisfaction-floor-loosening-run";
import { loadPublicReuseRateTrend } from "../services/public-reuse-rate-trend";
import { loadPublicReviewVolumeTrend } from "../services/public-review-volume-trend";
import { buildMaintainerQualityDashboard, isMaintainerQualityDataStale } from "../services/maintainer-quality-dashboard";
Expand Down Expand Up @@ -4813,6 +4814,17 @@ export function createApp() {
// Aggregate counts and rule ids only — no PR content, no raw context.
app.get("/v1/internal/calibration-trend", async (c) => c.json(await loadCalibrationTrend(c.env)));

// #8121 (approved narrow start): manually trigger one backtest-gated loosening evaluation of the
// linked-issue satisfaction confidence floor. 404 when the autotune flag is off (the endpoint doesn't
// exist on a deploy that hasn't opted in, mirroring the rag-index route's flag-gate). Bearer-gated by the
// /v1/internal/* middleware (INTERNAL_JOB_TOKEN). Applying is idempotent per candidate step: repeat calls
// re-evaluate from the CURRENT (possibly already-loosened) floor and step at most one candidate at a time.
app.post("/v1/internal/calibration/loosen-satisfaction-floor", async (c) => {
if (!isSatisfactionFloorAutotuneEnabled(c.env)) return c.json({ error: "not_found" }, 404);
const result = await runSatisfactionFloorLoosening(c.env);
return c.json(result);
});

app.post("/v1/internal/jobs/refresh-registry", async (c) => {
const message: JobMessage = { type: "refresh-registry", requestedBy: "api" };
await c.env.JOBS.send(message);
Expand Down
2 changes: 2 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ declare global {
* fetch (cloud, or a self-host without the dir, is byte-identical to before). */
LOOPOVER_REPO_CONFIG_DIR?: string;
LOOPOVER_AUTO_FILE_DRIFT_ISSUES?: string;
// #8121: backtest-gated satisfaction-floor autotune go-live switch (wrangler var, "false" by default).
SATISFACTION_FLOOR_AUTOTUNE_ENABLED?: string;
LOOPOVER_DRIFT_ISSUE_REPO?: string;
LOOPOVER_DRIFT_ISSUE_TOKEN?: string;
/** Comma-separated GitHub logins assigned to filed upstream-drift issues (default: the loopover
Expand Down
17 changes: 14 additions & 3 deletions src/services/linked-issue-satisfaction-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// bounded, public-safe {status, rationale} (or null). The caller (src/queue/processors.ts) decides.
import type { LinkedIssueSatisfactionResult } from "./linked-issue-satisfaction";
import { SATISFACTION_SYSTEM_PROMPT, buildLinkedIssueSatisfactionPrompt, buildLinkedIssueSatisfactionResult } from "./linked-issue-satisfaction";
import { getSatisfactionFloorOverride } from "./satisfaction-floor-loosening-run";
import { countByokAiEventsForRepoSince, recordAiUsageEvent, sumAiEstimatedNeuronsSince } from "../db/repositories";
import {
type AiReviewActualUsage,
Expand Down Expand Up @@ -43,6 +44,10 @@ export type LinkedIssueSatisfactionRunInput = {
/** Optional BYOK: when present, the maintainer's frontier model writes the assessment (billed to their
* account, counted against the shared per-repo/day BYOK cap) instead of the free/default reviewer. */
providerKey?: AiReviewProviderKey | null | undefined;
/** #8121: optional explicit confidence floor. Absent ⇒ the run resolves the live backtest-gated override
* itself (getSatisfactionFloorOverride; null when the autotune flag is off), falling back to the pure
* module's shipped constant — so every caller gets the loosened floor with zero threading. */
confidenceFloor?: number | undefined;
};

export type LinkedIssueSatisfactionRunResult =
Expand Down Expand Up @@ -72,6 +77,7 @@ async function runWorkersSatisfactionOpinion(
system: string,
user: string,
maxTokens: number,
confidenceFloor?: number,
): Promise<WorkersSatisfactionOpinionResult> {
const ai = env.AI as unknown as AiRunner | undefined;
if (!ai || typeof ai.run !== "function") return { result: null };
Expand All @@ -86,7 +92,7 @@ async function runWorkersSatisfactionOpinion(
extra,
);
const text = coerceAiText(raw);
const result = buildLinkedIssueSatisfactionResult(issueText, text);
const result = buildLinkedIssueSatisfactionResult(issueText, text, confidenceFloor);
if (result) return { result, usage: coerceAiUsage(raw), rawText: text };
} catch (error) {
if (isRateLimitError(error)) break;
Expand All @@ -109,6 +115,11 @@ export async function runLoopOverLinkedIssueSatisfaction(env: Env, input: Linked
// nothing to assess, so short-circuit before spending any budget or making a model call.
if (!(input.issueText ?? "").trim()) return { status: "ok", result: null, estimatedNeurons: 0 };

// #8121: resolve the live backtest-gated floor override HERE (single resolution point for every caller)
// unless the caller supplied an explicit floor. Null (flag off / no valid override) keeps the pure
// module's shipped constant via the parameter default -- byte-identical to pre-#8121 behavior.
const confidenceFloor = input.confidenceFloor ?? (await getSatisfactionFloorOverride(env)) ?? undefined;

const maxTokens = clampNumber(Number(env.AI_MAX_OUTPUT_TOKENS || 256), 256, 1024);
const user = buildLinkedIssueSatisfactionPrompt({
issueText: input.issueText,
Expand Down Expand Up @@ -146,11 +157,11 @@ export async function runLoopOverLinkedIssueSatisfaction(env: Env, input: Linked
let rawModelText: string | undefined;
if (input.providerKey) {
const { text, usage: byokUsage } = await callAiProvider(input.providerKey, SATISFACTION_SYSTEM_PROMPT, user, maxTokens);
result = text ? buildLinkedIssueSatisfactionResult(input.issueText, text) : null;
result = text ? buildLinkedIssueSatisfactionResult(input.issueText, text, confidenceFloor) : null;
usage = byokUsage;
rawModelText = text || undefined;
} else {
({ result, usage, rawText: rawModelText } = await runWorkersSatisfactionOpinion(env, input.issueText, SATISFACTION_SYSTEM_PROMPT, user, maxTokens));
({ result, usage, rawText: rawModelText } = await runWorkersSatisfactionOpinion(env, input.issueText, SATISFACTION_SYSTEM_PROMPT, user, maxTokens, confidenceFloor));
}
await record(env, input, "ok", estimatedNeurons, result ? `advisory finding (${result.status})` : "no usable output", { status: result?.status ?? null, surfaced: Boolean(result), byok: Boolean(input.providerKey) }, usage);
return { status: "ok", result, estimatedNeurons, ...(rawModelText ? { rawModelText } : {}) };
Expand Down
12 changes: 9 additions & 3 deletions src/services/linked-issue-satisfaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ export function buildLinkedIssueSatisfactionPrompt(input: LinkedIssueSatisfactio
/** Parse the model's raw JSON text response into a {@link LinkedIssueSatisfactionResult}, or null when the
* output is unusable (no JSON object, invalid status, or the confidence floor rejects an "unaddressed" call).
* PURE — never throws (a malformed blob that matches the brace regex but fails JSON.parse is caught). */
export function parseLinkedIssueSatisfactionOpinion(text: string): LinkedIssueSatisfactionResult | null {
export function parseLinkedIssueSatisfactionOpinion(
text: string,
// #8121: the floor is overridable ONLY downward and only via the backtest-gated loosening loop -- callers
// without an override pass nothing and get the shipped constant, byte-identical to before.
confidenceFloor: number = LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR,
): LinkedIssueSatisfactionResult | null {
const match = text
.replace(/^```(?:json)?\s*/i, "")
.replace(/```$/i, "")
Expand All @@ -129,7 +134,7 @@ export function parseLinkedIssueSatisfactionOpinion(text: string): LinkedIssueSa
const confidence = parseConfidence(obj.confidence);
// Fail-safe floor (#2172): a low-confidence "unaddressed" is never published as unaddressed — the caller
// gets no finding at all rather than a shaky "you didn't fix this" call. addressed/partial are unaffected.
if (obj.status === "unaddressed" && confidence < LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR) return null;
if (obj.status === "unaddressed" && confidence < confidenceFloor) return null;
if (!rationale) return null;
return { status: obj.status, rationale, confidence };
}
Expand All @@ -143,10 +148,11 @@ export function parseLinkedIssueSatisfactionOpinion(text: string): LinkedIssueSa
export function buildLinkedIssueSatisfactionResult(
issueText: string | null | undefined,
modelResponseText: string,
confidenceFloor: number = LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR,
): LinkedIssueSatisfactionResult | null {
if (!(issueText ?? "").trim()) return null;
try {
const opinion = parseLinkedIssueSatisfactionOpinion(modelResponseText);
const opinion = parseLinkedIssueSatisfactionOpinion(modelResponseText, confidenceFloor);
if (!opinion) return null;
const safeRationale = toPublicSafe(opinion.rationale);
if (!safeRationale) return null;
Expand Down
103 changes: 103 additions & 0 deletions src/services/satisfaction-floor-loosening-run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// IO orchestration for the backtest-gated satisfaction-floor loosening (#8121 narrow start) — the
// "separate, I/O-touching slice" satisfaction-floor-loosening.ts leaves to its caller, mirroring
// threshold-backtest-run.ts's identical split. Three responsibilities:
// 1. read the live floor override (system_flags, migration 0054 — the same operational-flag table the
// auto-tune circuit breakers use, so no new storage surface);
// 2. evaluate a loosening against the rule's real recorded history (SignalStore → corpus → pure core);
// 3. apply an approved proposal: write the override + a calibration audit event.
//
// The ENTIRE apply path is flag-gated on env.SATISFACTION_FLOOR_AUTOTUNE_ENABLED (wrangler var, unset/false
// by default), so a deploy without the flag is behavior-identical — #8121's Boundaries demand no autonomous
// config change without the explicit opt-in. Direction is enforced here AGAIN (proposed < current, ≥ hard
// minimum) on top of the evaluator's own guarantee: the write path must be independently incapable of
// tightening-disguised-as-loosening or of sailing past the safety minimum, whatever its input claims.
import { buildBacktestCorpus } from "@loopover/engine";
import { createSignalStore } from "../review/signal-tracking-wire";
import { recordAuditEvent } from "../db/repositories";
import {
evaluateSatisfactionFloorLoosening,
SATISFACTION_FLOOR_HARD_MINIMUM,
SATISFACTION_FLOOR_RULE_ID,
type SatisfactionFloorLooseningProposal,
} from "./satisfaction-floor-loosening";
import { LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR } from "./linked-issue-satisfaction";

export const SATISFACTION_FLOOR_OVERRIDE_FLAG_KEY = "satisfaction_floor_override";
export const SATISFACTION_FLOOR_LOOSENING_EVENT_TYPE = "calibration.satisfaction_floor_loosened";
const CORPUS_LOOKBACK_MS = 90 * 24 * 60 * 60 * 1000; // mirrors threshold-backtest-run's 90-day window

/** Truthy-string env flag, matching the repo's flag convention (mirrors outcomes-wire's flagTruthy). */
export function isSatisfactionFloorAutotuneEnabled(env: Env): boolean {
const value = (env.SATISFACTION_FLOOR_AUTOTUNE_ENABLED ?? "").trim().toLowerCase();
return value === "1" || value === "true" || value === "on" || value === "yes";
}

/**
* Read the live floor override. Returns null (caller uses the shipped default) when: the autotune flag is
* off (an operator turning the feature off instantly restores the shipped floor, no cleanup required), no
* override row exists, or the stored value fails validation — an override may only ever sit BELOW the
* shipped floor and AT/ABOVE the hard minimum, so a corrupted/hand-edited row can never tighten the floor
* or loosen it past safety. Fail-safe null on any DB error (the shipped default is always the fallback).
*/
export async function getSatisfactionFloorOverride(env: Env): Promise<number | null> {
if (!isSatisfactionFloorAutotuneEnabled(env)) return null;
try {
const row = await env.DB.prepare("SELECT value FROM system_flags WHERE key = ?")
.bind(SATISFACTION_FLOOR_OVERRIDE_FLAG_KEY)
.first<{ value: string }>();
if (!row) return null;
const parsed = Number(row.value);
if (!Number.isFinite(parsed) || parsed >= LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR || parsed < SATISFACTION_FLOOR_HARD_MINIMUM) {
return null;
}
return parsed;
} catch {
return null;
}
}

export type SatisfactionFloorLooseningRunResult =
| { applied: false; reason: "flag_off" | "no_proposal" | "already_applied" }
| { applied: true; proposal: SatisfactionFloorLooseningProposal };

/**
* Evaluate and (when justified) apply a backtest-gated loosening of the satisfaction floor. The current
* floor is the live override when one exists (so repeated runs evaluate from where the system actually is,
* stepping at most one candidate per run, and can never oscillate upward). Persists the new override plus a
* `calibration.satisfaction_floor_loosened` audit event carrying both split comparisons — the same
* structured evidence trail every other calibration write in epic #8082 leaves. Audit write is best-effort;
* the override write is NOT (an unrecorded floor change would be worse than no change, so a failed flag
* write aborts by throwing to the caller — the internal route surfaces it as a 500).
*/
export async function runSatisfactionFloorLoosening(env: Env, nowMs: number = Date.now()): Promise<SatisfactionFloorLooseningRunResult> {
if (!isSatisfactionFloorAutotuneEnabled(env)) return { applied: false, reason: "flag_off" };

const currentFloor = (await getSatisfactionFloorOverride(env)) ?? LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR;
if (currentFloor <= SATISFACTION_FLOOR_HARD_MINIMUM) return { applied: false, reason: "already_applied" };

const { fired, overrides } = await createSignalStore(env).queryRuleHistory(SATISFACTION_FLOOR_RULE_ID, nowMs - CORPUS_LOOKBACK_MS);
const cases = buildBacktestCorpus(SATISFACTION_FLOOR_RULE_ID, fired, overrides);
const proposal = evaluateSatisfactionFloorLoosening(cases, currentFloor);
if (!proposal) return { applied: false, reason: "no_proposal" };
// Defense in depth: the write path independently refuses anything that isn't a strict, bounded loosening.
if (proposal.proposedFloor >= currentFloor || proposal.proposedFloor < SATISFACTION_FLOOR_HARD_MINIMUM) {
return { applied: false, reason: "no_proposal" };
}

await env.DB.prepare(
"INSERT INTO system_flags (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at",
)
.bind(SATISFACTION_FLOOR_OVERRIDE_FLAG_KEY, String(proposal.proposedFloor))
.run();

await recordAuditEvent(env, {
eventType: SATISFACTION_FLOOR_LOOSENING_EVENT_TYPE,
actor: "loopover",
targetKey: SATISFACTION_FLOOR_RULE_ID,
outcome: "completed",
detail: `satisfaction confidence floor loosened ${proposal.currentFloor} -> ${proposal.proposedFloor} (backtest-gated, visible improved + held-out non-regressed)`,
metadata: { proposal },
}).catch(() => undefined);

return { applied: true, proposal };
}
Loading