Skip to content

Commit d89698d

Browse files
authored
feat(calibration): operator visibility for the satisfaction-floor loosening loop (#8161) (#8180)
The loop's applied loosenings only existed as audit rows an operator had to query by hand. Adds loadSatisfactionFloorStatus: flag state, shipped vs LIVE floor, the validated stored override (reported even while the flag is off -- an operator must see a lingering row that would take effect the moment the flag flips, while liveFloor only follows it when on), and the applied history projected newest-first from the calibration.satisfaction_floor_loosened events with both split verdicts and sample sizes. Corrupt/non-object rows degrade to a nulls entry rather than hiding that an apply happened; every read fails safe. Served at GET /v1/internal/calibration/satisfaction-floor -- deliberately NOT flag-gated (visibility must survive a flag flip), same INTERNAL_JOB_TOKEN gate, aggregate numbers/verdicts only (leak-guard pinned). Closes #8161.
1 parent a41206a commit d89698d

3 files changed

Lines changed: 245 additions & 1 deletion

File tree

src/api/routes.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ import { isRagEnabled } from "../review/rag-wire";
320320
import { getPublicStats, isPublicStatsEnabled, resolvePublicStatsManifestOverride } from "../review/public-stats";
321321
import { loadPublicAccuracyTrend } from "../services/public-accuracy-trend";
322322
import { loadCalibrationTrend } from "../services/rule-calibration-trend";
323-
import { isSatisfactionFloorAutotuneEnabled, runSatisfactionFloorLoosening } from "../services/satisfaction-floor-loosening-run";
323+
import { isSatisfactionFloorAutotuneEnabled, loadSatisfactionFloorStatus, runSatisfactionFloorLoosening } from "../services/satisfaction-floor-loosening-run";
324324
import { loadPublicReuseRateTrend } from "../services/public-reuse-rate-trend";
325325
import { loadPublicReviewVolumeTrend } from "../services/public-review-volume-trend";
326326
import { buildMaintainerQualityDashboard, isMaintainerQualityDataStale } from "../services/maintainer-quality-dashboard";
@@ -4826,6 +4826,12 @@ export function createApp() {
48264826
return c.json(result);
48274827
});
48284828

4829+
// Operator visibility for the loosening loop (#8161): flag state, shipped vs live floor, the stored
4830+
// override row, and the applied-loosening history with both split verdicts. Deliberately NOT flag-gated
4831+
// (unlike the trigger above): an operator must be able to see a lingering override row while the flag is
4832+
// off. Same INTERNAL_JOB_TOKEN gate via the /v1/internal/* middleware; aggregate numbers/verdicts only.
4833+
app.get("/v1/internal/calibration/satisfaction-floor", async (c) => c.json(await loadSatisfactionFloorStatus(c.env)));
4834+
48294835
app.post("/v1/internal/jobs/refresh-registry", async (c) => {
48304836
const message: JobMessage = { type: "refresh-registry", requestedBy: "api" };
48314837
await c.env.JOBS.send(message);

src/services/satisfaction-floor-loosening-run.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,104 @@ export async function loadSatisfactionFloorRecState(env: Env, nowMs: number = Da
172172

173173
return { flagEnabled, proposal, lastAppliedAt };
174174
}
175+
176+
// ── Operator visibility (#8161) ────────────────────────────────────────────────────────────────────────
177+
178+
export type SatisfactionFloorAppliedEntry = {
179+
at: string;
180+
currentFloor: number | null;
181+
proposedFloor: number | null;
182+
visibleCases: number | null;
183+
heldOutCases: number | null;
184+
visibleVerdict: string | null;
185+
heldOutVerdict: string | null;
186+
};
187+
188+
export type SatisfactionFloorStatus = {
189+
flagEnabled: boolean;
190+
shippedFloor: number;
191+
/** The floor the live assessment actually uses right now: the validated override when the flag is on,
192+
* else the shipped constant. */
193+
liveFloor: number;
194+
/** The RAW stored override row (validated), reported even when the flag is off — an operator looking at
195+
* this surface needs to see a lingering row that would take effect the moment the flag flips. Null when
196+
* no row exists or the stored value fails the loosening-only bounds. */
197+
storedOverride: number | null;
198+
applied: SatisfactionFloorAppliedEntry[];
199+
};
200+
201+
const SATISFACTION_FLOOR_STATUS_HISTORY_LIMIT = 25;
202+
203+
function numberOrNull(value: unknown): number | null {
204+
return typeof value === "number" && Number.isFinite(value) ? value : null;
205+
}
206+
207+
function verdictOrNull(value: unknown): string | null {
208+
const verdict = (value as { verdict?: unknown } | undefined)?.verdict;
209+
return typeof verdict === "string" ? verdict : null;
210+
}
211+
212+
/**
213+
* The operator status read (#8161): flag state, shipped vs live floor, the stored override row (validated,
214+
* shown regardless of flag state — see the type's own doc), and the applied-loosening history projected
215+
* from the calibration.satisfaction_floor_loosened audit events (#8121's evidence trail), newest first.
216+
* Aggregate numbers and verdicts only — no corpus content of any kind. Fail-safe: a read error degrades the
217+
* affected section (empty history / null override) rather than throwing the operator endpoint.
218+
*/
219+
export async function loadSatisfactionFloorStatus(env: Env): Promise<SatisfactionFloorStatus> {
220+
const flagEnabled = isSatisfactionFloorAutotuneEnabled(env);
221+
222+
let storedOverride: number | null = null;
223+
try {
224+
const row = await env.DB.prepare("SELECT value FROM system_flags WHERE key = ?")
225+
.bind(SATISFACTION_FLOOR_OVERRIDE_FLAG_KEY)
226+
.first<{ value: string }>();
227+
if (row) {
228+
const parsed = Number(row.value);
229+
if (Number.isFinite(parsed) && parsed < LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR && parsed >= SATISFACTION_FLOOR_HARD_MINIMUM) {
230+
storedOverride = parsed;
231+
}
232+
}
233+
} catch {
234+
storedOverride = null;
235+
}
236+
237+
const applied: SatisfactionFloorAppliedEntry[] = [];
238+
try {
239+
const rows = await env.DB.prepare(
240+
"SELECT created_at, metadata_json FROM audit_events WHERE event_type = ? ORDER BY created_at DESC LIMIT ?",
241+
)
242+
.bind(SATISFACTION_FLOOR_LOOSENING_EVENT_TYPE, SATISFACTION_FLOOR_STATUS_HISTORY_LIMIT)
243+
.all<{ created_at: string; metadata_json: string }>();
244+
/* v8 ignore next -- .all() over a live D1/TestD1 always yields a defined results array, never undefined;
245+
* the ?? [] guards a future driver-shape change, mirroring loadOrbDayRows' identical note. */
246+
for (const row of rows.results ?? []) {
247+
let proposal: Record<string, unknown> = {};
248+
try {
249+
const metadata = JSON.parse(row.metadata_json) as { proposal?: Record<string, unknown> };
250+
proposal = metadata.proposal && typeof metadata.proposal === "object" ? metadata.proposal : {};
251+
} catch {
252+
/* corrupt row -- keep the entry with nulls rather than hiding that an apply happened */
253+
}
254+
applied.push({
255+
at: row.created_at,
256+
currentFloor: numberOrNull(proposal.currentFloor),
257+
proposedFloor: numberOrNull(proposal.proposedFloor),
258+
visibleCases: numberOrNull(proposal.visibleCases),
259+
heldOutCases: numberOrNull(proposal.heldOutCases),
260+
visibleVerdict: verdictOrNull(proposal.visible),
261+
heldOutVerdict: verdictOrNull(proposal.heldOut),
262+
});
263+
}
264+
} catch {
265+
/* degrade to an empty history -- the endpoint must not throw on a read blip */
266+
}
267+
268+
return {
269+
flagEnabled,
270+
shippedFloor: LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR,
271+
liveFloor: flagEnabled && storedOverride !== null ? storedOverride : LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR,
272+
storedOverride,
273+
applied,
274+
};
275+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
loadSatisfactionFloorStatus,
4+
SATISFACTION_FLOOR_LOOSENING_EVENT_TYPE,
5+
SATISFACTION_FLOOR_OVERRIDE_FLAG_KEY,
6+
} from "../../src/services/satisfaction-floor-loosening-run";
7+
import { SATISFACTION_FLOOR_HARD_MINIMUM } from "../../src/services/satisfaction-floor-loosening";
8+
import { LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR } from "../../src/services/linked-issue-satisfaction";
9+
import { recordAuditEvent } from "../../src/db/repositories";
10+
import { createApp } from "../../src/api/routes";
11+
import { createTestEnv } from "../helpers/d1";
12+
13+
// Operator visibility for the loosening loop (#8161): the status read + its internal route. The loop's own
14+
// behavior lives in satisfaction-floor-loosening-run.test.ts — this file pins the reporting surface.
15+
16+
const enabledEnv = () => createTestEnv({ SATISFACTION_FLOOR_AUTOTUNE_ENABLED: "true" as never });
17+
18+
async function setOverrideRow(env: Env, value: string) {
19+
await env.DB.prepare("INSERT INTO system_flags (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = excluded.value")
20+
.bind(SATISFACTION_FLOOR_OVERRIDE_FLAG_KEY, value)
21+
.run();
22+
}
23+
24+
describe("loadSatisfactionFloorStatus (#8161)", () => {
25+
it("reports the shipped defaults on a fresh deployment: flag off, no override, live floor = shipped, empty history", async () => {
26+
const status = await loadSatisfactionFloorStatus(createTestEnv());
27+
expect(status).toEqual({
28+
flagEnabled: false,
29+
shippedFloor: LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR,
30+
liveFloor: LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR,
31+
storedOverride: null,
32+
applied: [],
33+
});
34+
});
35+
36+
it("shows a lingering override row even while the flag is OFF, but liveFloor only follows it when ON", async () => {
37+
const offEnv = createTestEnv();
38+
await setOverrideRow(offEnv, "0.4");
39+
const offStatus = await loadSatisfactionFloorStatus(offEnv);
40+
expect(offStatus.storedOverride).toBe(0.4);
41+
expect(offStatus.flagEnabled).toBe(false);
42+
expect(offStatus.liveFloor).toBe(LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR); // flag off ⇒ shipped floor rules
43+
44+
const onEnv = enabledEnv();
45+
await setOverrideRow(onEnv, "0.4");
46+
const onStatus = await loadSatisfactionFloorStatus(onEnv);
47+
expect(onStatus.liveFloor).toBe(0.4);
48+
});
49+
50+
it("rejects out-of-bounds or unparseable stored values from BOTH storedOverride and liveFloor", async () => {
51+
for (const bad of ["0.9", String(SATISFACTION_FLOOR_HARD_MINIMUM - 0.05), "junk", String(LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR)]) {
52+
const env = enabledEnv();
53+
await setOverrideRow(env, bad);
54+
const status = await loadSatisfactionFloorStatus(env);
55+
expect(status.storedOverride).toBeNull();
56+
expect(status.liveFloor).toBe(LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR);
57+
}
58+
});
59+
60+
it("projects the applied history newest-first with both split verdicts, and a corrupt row degrades to nulls instead of vanishing", async () => {
61+
const env = enabledEnv();
62+
await recordAuditEvent(env, {
63+
eventType: SATISFACTION_FLOOR_LOOSENING_EVENT_TYPE,
64+
actor: "loopover",
65+
targetKey: "linked_issue_scope_mismatch",
66+
outcome: "completed",
67+
metadata: {
68+
proposal: {
69+
currentFloor: 0.5,
70+
proposedFloor: 0.45,
71+
visibleCases: 24,
72+
heldOutCases: 7,
73+
visible: { verdict: "improved" },
74+
heldOut: { verdict: "unchanged" },
75+
},
76+
},
77+
createdAt: "2026-07-20T00:00:00.000Z",
78+
});
79+
await env.DB.prepare(
80+
"INSERT INTO audit_events (id, event_type, actor, target_key, outcome, detail, metadata_json, created_at) VALUES ('corrupt', ?, 'loopover', 'x', 'completed', '', 'not-json', '2026-07-21T00:00:00.000Z')",
81+
)
82+
.bind(SATISFACTION_FLOOR_LOOSENING_EVENT_TYPE)
83+
.run();
84+
85+
// A parseable row whose proposal is not an object degrades the same way as unparseable JSON.
86+
await env.DB.prepare(
87+
"INSERT INTO audit_events (id, event_type, actor, target_key, outcome, detail, metadata_json, created_at) VALUES ('nonobject', ?, 'loopover', 'x', 'completed', '', '{\"proposal\": 5}', '2026-07-22T00:00:00.000Z')",
88+
)
89+
.bind(SATISFACTION_FLOOR_LOOSENING_EVENT_TYPE)
90+
.run();
91+
92+
const status = await loadSatisfactionFloorStatus(env);
93+
expect(status.applied).toHaveLength(3);
94+
expect(status.applied[1]!.proposedFloor).toBeNull(); // the unparseable-JSON row
95+
expect(status.applied[0]).toEqual({
96+
at: "2026-07-22T00:00:00.000Z",
97+
currentFloor: null,
98+
proposedFloor: null,
99+
visibleCases: null,
100+
heldOutCases: null,
101+
visibleVerdict: null,
102+
heldOutVerdict: null,
103+
});
104+
expect(status.applied[2]).toEqual({
105+
at: "2026-07-20T00:00:00.000Z",
106+
currentFloor: 0.5,
107+
proposedFloor: 0.45,
108+
visibleCases: 24,
109+
heldOutCases: 7,
110+
visibleVerdict: "improved",
111+
heldOutVerdict: "unchanged",
112+
});
113+
});
114+
115+
it("fails safe to defaults + empty history on a DB error", async () => {
116+
const env = enabledEnv();
117+
env.DB = { prepare: () => { throw new Error("boom"); } } as never;
118+
const status = await loadSatisfactionFloorStatus(env);
119+
expect(status.storedOverride).toBeNull();
120+
expect(status.applied).toEqual([]);
121+
expect(status.liveFloor).toBe(LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR);
122+
});
123+
});
124+
125+
describe("GET /v1/internal/calibration/satisfaction-floor (#8161)", () => {
126+
it("401s without the internal token, and is NOT flag-gated: 200s with the flag off (visibility must survive a flag flip)", async () => {
127+
const app = createApp();
128+
const env = createTestEnv();
129+
expect((await app.request("/v1/internal/calibration/satisfaction-floor", {}, env)).status).toBe(401);
130+
const res = await app.request("/v1/internal/calibration/satisfaction-floor", { headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` } }, env);
131+
expect(res.status).toBe(200);
132+
const body = (await res.json()) as { flagEnabled: boolean; liveFloor: number; applied: unknown[] };
133+
expect(body.flagEnabled).toBe(false);
134+
expect(body.liveFloor).toBe(LINKED_ISSUE_SATISFACTION_CONFIDENCE_FLOOR);
135+
expect(JSON.stringify(body)).not.toMatch(/reward|payout|trust|wallet|hotkey|issueText|modelResponse/i);
136+
});
137+
});

0 commit comments

Comments
 (0)