@@ -166,6 +166,65 @@ describe("session-store-runtime compatibility surface", () => {
|
166 | 166 | expect(getSessionEntry({ sessionKey: staleSessionKey, storePath })).toBeUndefined(); |
167 | 167 | }); |
168 | 168 | |
| 169 | +it("accepts a pre-#88632 maintenanceConfig without the model-run fields", async () => { |
| 170 | +// Backward-compatibility guard: external plugin callers built `maintenanceConfig` |
| 171 | +// before `modelRunPruneAfterMs` / `modelRunPruneAfterConfigured` existed. That old |
| 172 | +// shape must still compile (the fields are optional) and behave as before — no |
| 173 | +// model-run-specific pruning. |
| 174 | +const staleModelRunKey = "agent:main:explicit:model-run-123e4567-e89b-12d3-a456-426614174000"; |
| 175 | +const activeSessionKey = "agent:main:active"; |
| 176 | +const now = Date.now(); |
| 177 | +await saveSessionStore( |
| 178 | +storePath, |
| 179 | +{ |
| 180 | +// A gateway model-run probe older than the 24h model-run window but well within the |
| 181 | +// 7d generic pruneAfter cutoff, so only model-run-specific pruning could remove it. |
| 182 | +[staleModelRunKey]: { |
| 183 | +sessionId: "session-probe", |
| 184 | +updatedAt: now - 2 * DAY_MS, |
| 185 | +}, |
| 186 | +[activeSessionKey]: { |
| 187 | +sessionId: "session-active", |
| 188 | +updatedAt: now, |
| 189 | +}, |
| 190 | +}, |
| 191 | +{ skipMaintenance: true }, |
| 192 | +); |
| 193 | + |
| 194 | +// Note: intentionally NO modelRunPruneAfterMs / modelRunPruneAfterConfigured (old shape), |
| 195 | +// and maxEntries high enough that there is NO cap pressure. |
| 196 | +const legacyMaintenanceConfig = { |
| 197 | +mode: "enforce" as const, |
| 198 | +pruneAfterMs: 7 * DAY_MS, |
| 199 | +maxEntries: 500, |
| 200 | +resetArchiveRetentionMs: 7 * DAY_MS, |
| 201 | +maxDiskBytes: null, |
| 202 | +highWaterBytes: null, |
| 203 | +}; |
| 204 | + |
| 205 | +await expect( |
| 206 | +patchSessionEntry({ |
| 207 | +sessionKey: activeSessionKey, |
| 208 | + storePath, |
| 209 | +maintenanceConfig: legacyMaintenanceConfig, |
| 210 | +update: () => ({ model: "gpt-5.5" }), |
| 211 | +}), |
| 212 | +).resolves.toMatchObject({ |
| 213 | +model: "gpt-5.5", |
| 214 | +sessionId: "session-active", |
| 215 | +}); |
| 216 | + |
| 217 | +// The old-shape config disables model-run pruning, so the stale probe survives |
| 218 | +// (no model-run field => disabled; no cap pressure => nothing capped). |
| 219 | +expect(getSessionEntry({ sessionKey: staleModelRunKey, storePath })).toMatchObject({ |
| 220 | +sessionId: "session-probe", |
| 221 | +}); |
| 222 | +expect(getSessionEntry({ sessionKey: activeSessionKey, storePath })).toMatchObject({ |
| 223 | +model: "gpt-5.5", |
| 224 | +sessionId: "session-active", |
| 225 | +}); |
| 226 | +}); |
| 227 | + |
169 | 228 | it("keeps deprecated whole-store mutations grouped as one compatibility operation", async () => { |
170 | 229 | const firstSessionKey = "agent:main:first"; |
171 | 230 | const secondSessionKey = "agent:main:second"; |
|