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
58 changes: 58 additions & 0 deletions apps/loopover-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,63 @@
},
"decidedCount": {
"type": "number"
},
"guaranteed": {
"type": "object",
"properties": {
"close": {
"type": "object",
"nullable": true,
"properties": {
"alpha": {
"type": "number"
},
"lambda": {
"type": "number"
},
"coveragePct": {
"type": "number"
},
"n": {
"type": "number"
}
},
"required": [
"alpha",
"lambda",
"coveragePct",
"n"
]
},
"merge": {
"type": "object",
"nullable": true,
"properties": {
"alpha": {
"type": "number"
},
"lambda": {
"type": "number"
},
"coveragePct": {
"type": "number"
},
"n": {
"type": "number"
}
},
"required": [
"alpha",
"lambda",
"coveragePct",
"n"
]
}
},
"required": [
"close",
"merge"
]
}
},
"required": [
Expand All @@ -642,6 +699,7 @@
"closePrecisionCiPct",
"coveragePct",
"decidedCount",
"guaranteed",
"instanceCount",
"windowDays",
"gamingFlagsCaught"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
closePrecisionCiPct?: { lo: number; hi: number } | null;
coveragePct?: number | null;
decidedCount?: number;
guaranteed?: { close: { alpha: number; lambda: number; coveragePct: number; n: number } | null; merge: { alpha: number; lambda: number; coveragePct: number; n: number } | null };

Check failure on line 42 in apps/loopover-ui/src/components/site/proof-of-power-stats-model.ts

View workflow job for this annotation

GitHub Actions / validate-code

Replace `·close:·{·alpha:·number;·lambda:·number;·coveragePct:·number;·n:·number·}·|·null;·merge:·{·alpha:·number;·lambda:·number;·coveragePct:·number;·n:·number·}·|·null` with `⏎······close:·{·alpha:·number;·lambda:·number;·coveragePct:·number;·n:·number·}·|·null;⏎······merge:·{·alpha:·number;·lambda:·number;·coveragePct:·number;·n:·number·}·|·null;⏎···`
instanceCount: number;
windowDays: number;
gamingFlagsCaught: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export function ProofOfPowerStats({ className }: { className?: string }) {
fleetEligible
? // #8829: a bare accuracy scalar at unstated coverage is gameable (holding more raises it), so
// the tile names the coverage it was earned at whenever the backend supplies it.
`merge/close calls confirmed by outcome${data.fleetAccuracy.coveragePct != null ? ` · at ${data.fleetAccuracy.coveragePct}% coverage` : ""} · ${intFmt.format(data.fleetAccuracy.instanceCount)} self-hosted instance${data.fleetAccuracy.instanceCount === 1 ? "" : "s"}${data.fleetAccuracy.gamingFlagsCaught > 0 ? ` · ${intFmt.format(data.fleetAccuracy.gamingFlagsCaught)} gaming pattern${data.fleetAccuracy.gamingFlagsCaught === 1 ? "" : "s"} flagged` : ""}`
`merge/close calls confirmed by outcome${data.fleetAccuracy.coveragePct != null ? ` · at ${data.fleetAccuracy.coveragePct}% coverage` : ""}${data.fleetAccuracy.guaranteed?.close ? ` · closes ≥${Math.round((1 - data.fleetAccuracy.guaranteed.close.alpha) * 1000) / 10}% guaranteed at ${data.fleetAccuracy.guaranteed.close.coveragePct}% coverage` : ""} · ${intFmt.format(data.fleetAccuracy.instanceCount)} self-hosted instance${data.fleetAccuracy.instanceCount === 1 ? "" : "s"}${data.fleetAccuracy.gamingFlagsCaught > 0 ? ` · ${intFmt.format(data.fleetAccuracy.gamingFlagsCaught)} gaming pattern${data.fleetAccuracy.gamingFlagsCaught === 1 ? "" : "s"} flagged` : ""}`
: totals.reversed > 0
? `${intFmt.format(totals.reversed)} human-reversed`
: "reversal-grounded"
Expand Down
7 changes: 7 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,13 @@ declare global {
LOOPOVER_REVIEW_SELFTUNE?: string;
/** #8830: weekly stratified human-audit sampling of gate decisions (default OFF). */
LOOPOVER_DECISION_AUDIT?: string;
/** #8835: daily distribution-free risk-control recalibration over the audit labels (default OFF). */
LOOPOVER_RISK_CONTROL?: string;
/** #8835/#8849: per-arm error budgets + calibration confidence — instance-level instrument parameters
* (clamped; defaults 0.015 / 0.005 / 0.05). */
LOOPOVER_RISK_CONTROL_CLOSE_ALPHA?: string;
LOOPOVER_RISK_CONTROL_MERGE_ALPHA?: string;
LOOPOVER_RISK_CONTROL_DELTA?: string;
/** Experimental `gittensor` plugin (the `experimental:` manifest block, first key): the operator-level
* kill-switch for loopover's original subnet mining-registry/scoring integration, now opt-in rather than
* a core dependency. ANDed with the per-repo `.loopover.yml experimental.gittensor` opt-in -- neither
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { isPrReconciliationEnabled, resolvePrReconciliationManifestOverride } fr
import { isActiveReviewReconciliationEnabled, resolveActiveReviewReconciliationManifestOverride } from "./review/active-review-reconciliation";
import { isRagEnabled } from "./review/rag-wire";
import { isDecisionAuditEnabled } from "./review/decision-audit";
import { isRiskControlEnabled } from "./review/risk-control-wire";
import { isSelfTuneEnabled } from "./review/selftune-wire";
import { isSatisfactionFloorAutotuneEnabled } from "./services/satisfaction-floor-loosening-run";
import {
Expand Down Expand Up @@ -298,6 +299,12 @@ async function enqueueScheduledJobs(env: Env, controller: ScheduledController):
if (isHourly && scheduledAt.getUTCDay() === 2 && hour === 8 && selfHostedReviews && isDecisionAuditEnabled(env)) {
jobs.push({ type: "decision-audit-sample", requestedBy: "schedule" });
}
// Risk-control recalibration (#8835, flag LOOPOVER_RISK_CONTROL): daily fixed-sequence calibration of the
// per-arm act/hold thresholds over the adjudicated labels. 07:00 UTC — its own slot. Enqueued ONLY when
// the flag is ON — flag-OFF (default) this job is never created and the tick is byte-identical.
if (isHourly && hour === 7 && selfHostedReviews && isRiskControlEnabled(env)) {
jobs.push({ type: "risk-control-recalibrate", requestedBy: "schedule" });
}
// Prune expired log/snapshot rows once a day (03:00 UTC) per the conservative RETENTION_POLICY.
if (isHourly && hour === 3) {
jobs.push({ type: "prune-retention", requestedBy: "schedule" });
Expand Down
1 change: 1 addition & 0 deletions src/openapi/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export const PublicStatsSchema = z
closePrecisionCiPct: z.object({ lo: z.number(), hi: z.number() }).nullable(),
coveragePct: z.number().nullable(),
decidedCount: z.number(),
guaranteed: z.object({ close: z.object({ alpha: z.number(), lambda: z.number(), coveragePct: z.number(), n: z.number() }).nullable(), merge: z.object({ alpha: z.number(), lambda: z.number(), coveragePct: z.number(), n: z.number() }).nullable() }),
instanceCount: z.number(),
windowDays: z.number(),
gamingFlagsCaught: z.number(),
Expand Down
27 changes: 27 additions & 0 deletions src/orb/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,33 @@ export async function handleOrbIngest(body: string, db: D1Database): Promise<Orb
}
}

// #8835: the instance's live risk-control calibrations. TRUST GATE: stored only when the sender is a
// REGISTERED instance — a published accuracy guarantee is the strongest claim on the homepage, and open
// ingest must not let a stranger plant one (same anchor computeFleetAnalytics uses). Bounded: two known
// arms, value stored verbatim as JSON for public-stats to render.
const riskControl = (payload as { risk_control?: unknown }).risk_control;
if (riskControl !== undefined && riskControl !== null && typeof riskControl === "object" && !Array.isArray(riskControl)) {
try {
const registeredRow = await db.prepare("SELECT registered FROM orb_instances WHERE instance_id = ?").bind(instance_id).first<{ registered: number }>();
if (registeredRow?.registered === 1) {
for (const arm of ["close", "merge"]) {
const value = (riskControl as Record<string, unknown>)[arm];
if (value !== undefined && value !== null && typeof value === "object") {
await db
.prepare(`INSERT OR REPLACE INTO system_flags (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)`)
.bind(`riskcontrol:fleet:${arm}`, JSON.stringify(value).slice(0, 2000))
.run();
} else {
// The sender no longer publishes this arm — retract the fleet copy too (stale guarantees lie).
await db.prepare(`DELETE FROM system_flags WHERE key = ?`).bind(`riskcontrol:fleet:${arm}`).run();
}
}
}
} catch {
// best-effort — a calibration hiccup must never fail the outcome batch
}
}

// #8820: day-bucketed reuse counters (optional field; older builds omit it). Every row is
// whitelist-validated (strict YYYY-MM-DD day, clamped non-negative counts) and upserted on
// (instance_id, day) — the sender re-exports a rolling window each tick, so REPLACE keeps the freshest
Expand Down
25 changes: 17 additions & 8 deletions src/queue/gate-checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,12 @@ export function gateCheckPolicy(
guardrailHit: boolean;
guardrailMatches?: ReturnType<typeof guardrailPathMatches> | undefined;
},
// #8176: the backtest-gated GLOBAL default-override for the AI close-confidence floor, resolved by the
// env-bearing caller (getAiReviewCloseConfidenceOverride — flag-gated + bounds-validated). It only fills
// the DEFAULT: an explicit per-repo `gate.aiReview.closeConfidence` setting always wins below.
aiReviewCloseConfidenceOverride?: number | null,
// #8176/#8849: the AUTOMATIC default-override for the AI close-confidence floor, resolved by the
// env-bearing caller (resolveAutomaticCloseConfidence — a live calibrated λ̂ outranks the backtest-gated
// knob loosening; both are flag-gated + bounds-validated). It only fills the DEFAULT: an explicit per-repo
// `gate.aiReview.closeConfidence` setting always wins below — operator config-as-code outranks every
// automatic writer. Accepts the provenance-carrying shape or (legacy callers/tests) a bare number.
aiReviewCloseConfidenceOverride?: { value: number; calibrated: boolean } | number | null,
) {
// `settings` is already the EFFECTIVE config (`.loopover.yml` > DB > defaults), resolved upstream by
// resolveRepositorySettings, so the blocker modes here reflect the repo's config file directly.
Expand All @@ -84,10 +86,17 @@ export function gateCheckPolicy(
qualityGateMode: settings.qualityGateMode,
qualityGateMinScore: settings.qualityGateMinScore ?? null,
aiReviewGateMode: settings.aiReviewMode,
// Calibrated AI close-confidence floor (#7) — config-as-code via `.loopover.yml gate.aiReview.closeConfidence`,
// resolved into settings upstream. When the repo has no explicit setting, the #8176 backtest-gated
// global override (if any) becomes the default; `null` ⇒ advisory.ts applies the 0.93 shipped default.
aiReviewCloseConfidence: settings.aiReviewCloseConfidence ?? aiReviewCloseConfidenceOverride ?? null,
// AI close-confidence floor (#7) — config-as-code via `.loopover.yml gate.aiReview.closeConfidence`,
// resolved into settings upstream. When the repo has no explicit setting, the automatic override chain
// (#8849: calibrated λ̂, else the #8176 backtest loosening) becomes the default; `null` ⇒ advisory.ts
// applies the 0.93 shipped default.
aiReviewCloseConfidence:
settings.aiReviewCloseConfidence ??
(typeof aiReviewCloseConfidenceOverride === "number" ? aiReviewCloseConfidenceOverride : (aiReviewCloseConfidenceOverride?.value ?? null)),
// #8849: provenance for the low-confidence hold copy — true ONLY when the floor in force actually came
// from a live calibration (an explicit repo setting suppresses it).
aiReviewCloseConfidenceCalibrated:
settings.aiReviewCloseConfidence == null && typeof aiReviewCloseConfidenceOverride === "object" && aiReviewCloseConfidenceOverride !== null && aiReviewCloseConfidenceOverride.calibrated,
// Sub-floor AI-judgment disposition (#4603) — DB-backed (dashboard-settable) + `.loopover.yml
// gate.aiReview.lowConfidenceDisposition` override, resolved into settings upstream. `null`/undefined ⇒
// advisory.ts applies the "hold_for_review" default.
Expand Down
8 changes: 8 additions & 0 deletions src/queue/job-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { syncBrokeredInstalledRepos } from "../orb/installed-repos-sync";
import { incr } from "../selfhost/metrics";
import { generateSignalSnapshots } from "./signal-snapshot";
import { isDecisionAuditEnabled, runDecisionAuditSample } from "../review/decision-audit";
import { isRiskControlEnabled, runRiskControlRecalibration } from "../review/risk-control-wire";
import { runRetentionPrune } from "./retention";
// The 15 handlers below have no reason to move -- each is only reachable via this dispatcher (or, for
// mapWithConcurrency, ALSO used by other still-in-processors.ts code), so they stay put and are exported
Expand Down Expand Up @@ -244,6 +245,13 @@ export async function processJob(env: Env, message: JobMessage): Promise<void> {
console.log(JSON.stringify({ event: "decision_audit_sampled", inserted }));
return;
}
case "risk-control-recalibrate": {
// #8835: same stale-queued-job posture as its sampling sibling above.
if (!isRiskControlEnabled(env)) return;
const summary = await runRiskControlRecalibration(env);
console.log(JSON.stringify({ event: "risk_control_recalibrated", ...summary }));
return;
}
case "generate-weekly-value-report":
await generateWeeklyValueReport(env, {
variant: message.variant ?? "operator",
Expand Down
7 changes: 4 additions & 3 deletions src/queue/processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ import {
} from "../review/outcomes-wire";
import { AI_JUDGMENT_BLOCKER_CODES } from "../rules/advisory";
import { REVIEW_PROMPT_VERSION, REVIEW_SYSTEM_PROMPT } from "../services/ai-review";
import { resolveAutomaticCloseConfidence } from "../review/risk-control-wire";
import { maybeApplyCloseAuditHoldout } from "../review/close-audit-holdout";
import { buildDecisionRecord, contentDigest, loadDecisionRecordCollapsible, persistDecisionRecord } from "../review/decision-record";
import { neutralHoldReasonCode, nativeGateActionFromConclusion, recordNativeGateDecision } from "../review/parity-wire";
Expand Down Expand Up @@ -1548,7 +1549,7 @@ export async function sweepRepoRegate(
// unchanged.
// #8176: the global close-confidence default-override, resolved once for the sweep (same value the main
// webhook path threads; an explicit per-repo setting still wins inside gateCheckPolicy).
const sweepCloseConfidenceOverride = await getAiReviewCloseConfidenceOverride(env, repoFullName);
const sweepCloseConfidenceOverride = await resolveAutomaticCloseConfidence(env, repoFullName, await getAiReviewCloseConfidenceOverride(env, repoFullName));
for (const [index, pr] of candidates.entries()) {
const others = openPullRequests.filter(
(other) => other.number !== pr.number,
Expand Down Expand Up @@ -12124,7 +12125,7 @@ async function maybeProcessResolveCommand(env: Env, deliveryId: string, payload:
if (!findingRef.ok) { await recordAuditEvent(env, { eventType: "github_app.finding_resolved_skipped", actor: req.actor, targetKey, outcome: "completed", detail: findingRef.reason, metadata: { deliveryId, repoFullName: req.repoFullName, reason: findingRef.reason } }); await recordGithubProductUsage(env, "finding_resolved_skipped", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "skipped", metadata: { reason: findingRef.reason } }); return true; }
const { advisory } = await buildAuthorizedPrActionAdvisory(env, req.repoFullName, pr, settings);
await appendPublishedAiReviewFindingsForResolve(env, req.repoFullName, pr, settings.aiReviewMode, advisory);
const gate = evaluateGateCheck(advisory, gateCheckPolicy(settings, null, undefined, pr.slopRisk ?? null, undefined, undefined, await getAiReviewCloseConfidenceOverride(env, req.repoFullName)));
const gate = evaluateGateCheck(advisory, gateCheckPolicy(settings, null, undefined, pr.slopRisk ?? null, undefined, undefined, await resolveAutomaticCloseConfidence(env, req.repoFullName, await getAiReviewCloseConfidenceOverride(env, req.repoFullName))));
const selection = selectWarningsForResolve(gate.warnings, findingRef);
if (selection.reason === "finding_not_found") { await recordAuditEvent(env, { eventType: "github_app.finding_resolved_skipped", actor: req.actor, targetKey, outcome: "completed", detail: selection.reason, metadata: { deliveryId, repoFullName: req.repoFullName, reason: selection.reason } }); await recordGithubProductUsage(env, "finding_resolved_skipped", { actor: req.actor, repoFullName: req.repoFullName, targetKey, outcome: "skipped", metadata: { reason: selection.reason } }); return true; }
const mode = resolveAgentActionMode({ globalPaused: isGlobalAgentPause(env) || (await isGlobalAgentFrozen(env)), agentPaused: settings.agentPaused, agentDryRun: settings.agentDryRun });
Expand Down Expand Up @@ -12355,7 +12356,7 @@ async function maybeProcessExplainCommand(env: Env, deliveryId: string, payload:
}
const { advisory } = await buildAuthorizedPrActionAdvisory(env, req.repoFullName, pr, settings);
await appendPublishedAiReviewFindingsForResolve(env, req.repoFullName, pr, settings.aiReviewMode, advisory);
const gate = evaluateGateCheck(advisory, gateCheckPolicy(settings, null, undefined, pr.slopRisk ?? null, undefined, undefined, await getAiReviewCloseConfidenceOverride(env, req.repoFullName)));
const gate = evaluateGateCheck(advisory, gateCheckPolicy(settings, null, undefined, pr.slopRisk ?? null, undefined, undefined, await resolveAutomaticCloseConfidence(env, req.repoFullName, await getAiReviewCloseConfidenceOverride(env, req.repoFullName))));
const selection = selectWarningsForResolve(gate.warnings, findingRef);
if (selection.reason === "finding_not_found") {
const notFound = sanitizePublicComment([AGENT_COMMAND_COMMENT_MARKER, "", "> [!NOTE]", `> **No review finding \`${findingRef.findingCode}\` on this PR**`, "> That id is not among this PR's current review findings — re-run `@loopover explain <finding-id>` with an id from the review summary.", "", "---", loopoverFooter(env)].join("\n"));
Expand Down
Loading
Loading