@@ -936,7 +936,7 @@ describe("compaction governor", () => {
936936 });
937937});
938938
939- describe("provider-aware idle recompress (CL-8745 )", () => {
939+ describe("cache expiry never folds (CL-8914 )", () => {
940940 const MINUTE_MS = 60_000;
941941
942942 function ttlInferenceDone(
@@ -984,142 +984,48 @@ describe("provider-aware idle recompress (CL-8745)", () => {
984984 } as ReactorInboundEvent;
985985 }
986986
987- test("fires past the provider TTL while under threshold, meter-only on empty", () => {
987+ test("idle pings past any provider cache window return null", () => {
988+ // The governor holds no TTL table: an unarmed idle re-entry never
989+ // produces a compact, whatever the provider's cache economics. Staleness
990+ // on the outgoing prompt is the anthropic-cache-prompt transform's job.
988991 let continuations = 0;
989992 let nowMs = 10_000_000;
990- const governor = createCompactionGovernor(
991- () => continuations++,
992- "",
993- [],
994- () => nowMs,
995- );
996- governor.noteInferenceDone(
997- ttlInferenceDone(
998- { provider: "anthropic", model: "claude-opus-4-6" },
999- false,
1000- ),
1001- tenTurns,
1002- );
1003-
1004- // Inside the 5-minute Anthropic window: no fire.
1005- nowMs += 4 * MINUTE_MS;
1006- expect(
1007- governor.interceptIdleContinuation(emptyMessage(), capabilities),
1008- ).toBeNull();
1009-
1010- // Past the window: the same fold as the threshold path (same compactor,
1011- // so the fresh tail stays raw) with an attributable reason.
1012- nowMs += MINUTE_MS + 1;
1013- expect(
1014- governor.interceptIdleContinuation(emptyMessage(), capabilities),
1015- ).toBeNull();
1016- expect(continuations).toBe(0);
1017- });
1018-
1019- test("production LastCycleSource: anthropic fires at 5m, codex never does", () => {
1020- // Harness stamps { sourceId, provider, model } with a bare model, not
1021- // slash-form "anthropic/claude-opus-4-6". Anthropic's 5-minute window
1022- // must come from provider, not a dummy model string. Codex has no
1023- // published 5-minute expiry, so it must stay quiet past the old 10-minute
1024- // guess as well.
1025- let nowMs = 15_000_000;
1026- const clock = () => nowMs;
1027- const anthropic = createCompactionGovernor(() => undefined, "", [], clock);
1028- const codex = createCompactionGovernor(() => undefined, "", [], clock);
1029- anthropic.noteInferenceDone(
1030- ttlInferenceDone(
1031- { provider: "anthropic", model: "claude-opus-4-6" },
1032- false,
1033- ),
1034- tenTurns,
1035- );
1036- codex.noteInferenceDone(
1037- ttlInferenceDone(
1038- {
1039- sourceId: "codex/work",
1040- provider: "codex-responses",
1041- model: "gpt-5.6-luna",
1042- },
1043- false,
1044- ),
1045- tenTurns,
1046- );
1047-
1048- nowMs += 5 * MINUTE_MS + 1;
1049- expect(
1050- anthropic.interceptIdleContinuation(emptyMessage(), capabilities),
1051- ).toBeNull();
1052- expect(
1053- codex.interceptIdleContinuation(emptyMessage(), capabilities),
1054- ).toBeNull();
1055-
1056- nowMs += 30 * MINUTE_MS;
1057- expect(
1058- codex.interceptIdleContinuation(emptyMessage(), capabilities),
1059- ).toBeNull();
1060- });
1061-
1062- test("does not idle-recompress DeepSeek or ollama", () => {
1063- let nowMs = 20_000_000;
1064993 const clock = () => nowMs;
1065- const deepseek = createCompactionGovernor(() => undefined, "", [], clock);
1066- const local = createCompactionGovernor(() => undefined, "", [], clock);
1067- deepseek.noteInferenceDone(
1068- ttlInferenceDone("deepseek/deepseek-chat", false),
1069- tenTurns,
1070- );
1071- local.noteInferenceDone(
1072- ttlInferenceDone("ollama/llama3.1", false),
1073- tenTurns,
1074- );
1075-
1076- nowMs += 90 * MINUTE_MS;
1077- expect(
1078- deepseek.interceptIdleContinuation(emptyMessage(), capabilities),
1079- ).toBeNull();
1080- expect(
1081- local.interceptIdleContinuation(emptyMessage(), capabilities),
1082- ).toBeNull();
1083- });
1084-
1085- test("production ollama LastCycleSource never fires cache-ttl-recompress", () => {
1086- // Harness stamps { sourceId, provider, model } with a bare model. Ollama is
1087- // buildOpenAISource: sourceId "ollama/default", provider openai-compatible,
1088- // Keying TTL off the bare model would miss the ollama sourceId. Local
1089- // inference stays disabled.
1090- let nowMs = 25_000_000;
1091- const governor = createCompactionGovernor(
1092- () => undefined,
1093- "",
1094- [],
1095- () => nowMs,
1096- );
1097- governor.noteInferenceDone(
1098- ttlInferenceDone(
1099- {
1100- sourceId: "ollama/default",
1101- provider: "openai-compatible",
1102- model: "llama3",
1103- },
1104- false,
1105- ),
1106- tenTurns,
1107- );
1108-
1109- nowMs += 5 * MINUTE_MS + 1;
1110- expect(
1111- governor.interceptIdleContinuation(emptyMessage(), capabilities),
1112- ).toBeNull();
1113- });
1114-
1115- test("stays inert with no observed cache write", () => {
1116- const governor = createCompactionGovernor(() => undefined);
1117- expect(
1118- governor.interceptIdleContinuation(emptyMessage(), capabilities),
1119- ).toBeNull();
994+ const sources = [
995+ { provider: "anthropic", model: "claude-opus-4-6" },
996+ {
997+ sourceId: "codex/work",
998+ provider: "codex-responses",
999+ model: "gpt-5.6-luna",
1000+ },
1001+ { provider: "deepseek", model: "deepseek-chat" },
1002+ {
1003+ sourceId: "ollama/default",
1004+ provider: "openai-compatible",
1005+ model: "llama3",
1006+ },
1007+ { provider: "custom-proxy", model: "unknown-model" },
1008+ ];
1009+ for (const source of sources) {
1010+ const governor = createCompactionGovernor(
1011+ () => continuations++,
1012+ "",
1013+ [],
1014+ clock,
1015+ );
1016+ governor.noteInferenceDone(ttlInferenceDone(source, false), tenTurns);
1017+ expect(
1018+ governor.interceptIdleContinuation(emptyMessage(), capabilities),
1019+ ).toBeNull();
1020+ nowMs += 90 * MINUTE_MS;
1021+ expect(
1022+ governor.interceptIdleContinuation(emptyMessage(), capabilities),
1023+ ).toBeNull();
1024+ }
1025+ expect(continuations).toBe(0);
11201026 });
11211027
1122- test("defers to the armed threshold path while over threshold ", () => {
1028+ test("an armed threshold fold is not disturbed by idle re-entry ", () => {
11231029 let nowMs = 30_000_000;
11241030 const governor = createCompactionGovernor(
11251031 () => undefined,
@@ -1128,9 +1034,9 @@ describe("provider-aware idle recompress (CL-8745)", () => {
11281034 () => nowMs,
11291035 );
11301036 governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1131- // Fixture provider "p" has no TTL. Threshold arming still owns this session.
1037+ // Threshold arming owns the over-threshold session and fires at the tool
1038+ // pause; a message.received re-entry is not its trigger.
11321039 nowMs += 5 * MINUTE_MS + 1;
1133- // Threshold arming owns the over-threshold session: no TTL double-fold.
11341040 expect(
11351041 governor.interceptIdleContinuation(emptyMessage(), capabilities),
11361042 ).toBeNull();
@@ -1139,11 +1045,10 @@ describe("provider-aware idle recompress (CL-8745)", () => {
11391045 ).toBeNull();
11401046 });
11411047
1142- test("fires cache-ttl-recompress in the hysteresis gap (over-threshold, no growth)", () => {
1143- // Characterization, not a bug: after a threshold compact, a post-compact
1144- // infer at the same usage clears `pending` via growth hysteresis, so the
1145- // threshold path no longer owns the session. Idle past the provider TTL
1146- // still folds — same window- and cap-bounded path as under-threshold.
1048+ test("the hysteresis gap does not fold on cache expiry", () => {
1049+ // After a threshold compact, a post-compact infer at the same usage
1050+ // clears `pending` via growth hysteresis — the exact re-entry where the
1051+ // removed TTL path used to fire.
11471052 let nowMs = 70_000_000;
11481053 const governor = createCompactionGovernor(
11491054 () => undefined,
@@ -1174,37 +1079,7 @@ describe("provider-aware idle recompress (CL-8745)", () => {
11741079 ).toBeNull();
11751080 });
11761081
1177- test("after threshold compact and gap TTL, growth-armed compact stays blocked until a tool_call", () => {
1178- // Threshold compact (consecutive=1) plus TTL fire in the hysteresis gap
1179- // (consecutive=2) fills the shared cap. Later growth that would re-arm
1180- // the threshold path stays blocked until a tool_call occupancy resets it.
1181- let nowMs = 80_000_000;
1182- const governor = createCompactionGovernor(
1183- () => undefined,
1184- "",
1185- [],
1186- () => nowMs,
1187- );
1188- governor.noteInferenceDone(
1189- inferenceDone(overThreshold, "", "anthropic"),
1190- tenTurns,
1191- );
1192- expect(
1193- governor.interceptActions(toolDone(), inferAction, capabilities),
1194- ).not.toBeNull();
1195- expect(governor.resumeAfterCompact(emptyMessage())).toBe("infer");
1196-
1197- governor.noteInferenceDone(
1198- inferenceDone(overThreshold, "", "anthropic"),
1199- tenTurns,
1200- );
1201- nowMs += 5 * MINUTE_MS + 1;
1202- expect(
1203- governor.interceptIdleContinuation(emptyMessage(), capabilities),
1204- ).toBeNull();
1205- });
1206-
1207- test("does not fold under an outstanding tool batch, fires once it settles", () => {
1082+ test("an outstanding tool batch does not change idle re-entry", () => {
12081083 let continuations = 0;
12091084 let nowMs = 40_000_000;
12101085 const governor = createCompactionGovernor(
@@ -1221,7 +1096,6 @@ describe("provider-aware idle recompress (CL-8745)", () => {
12211096 expect(
12221097 governor.interceptIdleContinuation(emptyMessage(), capabilities),
12231098 ).toBeNull();
1224- // The batch settles (threshold path uninvolved: under threshold).
12251099 expect(
12261100 governor.interceptActions(toolDone(), inferAction, capabilities),
12271101 ).toBeNull();
@@ -1231,28 +1105,7 @@ describe("provider-aware idle recompress (CL-8745)", () => {
12311105 expect(continuations).toBe(0);
12321106 });
12331107
1234- test("one fire per window, then the shared consecutive-compact cap stops the spiral", () => {
1235- let continuations = 0;
1236- let nowMs = 50_000_000;
1237- const governor = createCompactionGovernor(
1238- () => continuations++,
1239- "",
1240- [],
1241- () => nowMs,
1242- );
1243- governor.noteInferenceDone(
1244- ttlInferenceDone("anthropic/claude-opus-4-6", false),
1245- tenTurns,
1246- );
1247-
1248- nowMs += 5 * MINUTE_MS + 1;
1249- expect(
1250- governor.interceptIdleContinuation(emptyMessage(), capabilities),
1251- ).toBeNull();
1252- expect(continuations).toBe(0);
1253- });
1254-
1255- test("a raced operator message past the TTL still folds, then re-infers", () => {
1108+ test("a raced operator message past the window does not fold", () => {
12561109 let continuations = 0;
12571110 let nowMs = 60_000_000;
12581111 const governor = createCompactionGovernor(
0 commit comments