@@ -1133,6 +1133,124 @@ describe("m[0]/m[1] materialization", () => {
11331133 expect ( renderedText ( secondMessages [ 0 ] ) ) . toBe ( firstM0 ) ;
11341134 } ) ;
11351135
1136+ it ( "SOFT /ctx-flush pass keeps m0 byte-identical, refreshes m1, and avoids first_render" , ( ) => {
1137+ db = makeDb ( ) ;
1138+ const projectDirectory = makeProjectDir ( ) ;
1139+ const state = readStateFromMeta ( ) ;
1140+ const hardV1 = {
1141+ systemHash : "sys-v1" ,
1142+ modelKey : "model-v1" ,
1143+ cacheExpired : false ,
1144+ lastResponseTime : 0 ,
1145+ } ;
1146+ const baselineMessages = [ userMessage ( "m1" , "hello" ) ] ;
1147+ injectM0M1 ( {
1148+ db,
1149+ sessionId : SESSION_ID ,
1150+ messages : baselineMessages ,
1151+ state,
1152+ projectPath : PROJECT_PATH ,
1153+ projectDirectory,
1154+ hardSignals : hardV1 ,
1155+ } ) ;
1156+ const m0BeforeFlush =
1157+ baselineMessages [ 0 ] . parts [ 0 ] &&
1158+ renderedText ( baselineMessages [ 0 ] ) . match (
1159+ / < s e s s i o n - h i s t o r y > [ \s \S ] * ?< \/ s e s s i o n - h i s t o r y > / ,
1160+ ) ?. [ 0 ] ;
1161+ expect ( m0BeforeFlush ) . toBeTruthy ( ) ;
1162+
1163+ const flushMessages = [ userMessage ( "m2" , "after flush" ) ] ;
1164+ const flushPass = injectM0M1 ( {
1165+ db,
1166+ sessionId : SESSION_ID ,
1167+ messages : flushMessages ,
1168+ state,
1169+ projectPath : PROJECT_PATH ,
1170+ projectDirectory,
1171+ isCacheBustingPass : true ,
1172+ } ) ;
1173+ expect ( flushPass . decision . reason ) . not . toBe ( "first_render" ) ;
1174+ expect ( flushPass . m0RematerializedThisPass ) . toBe ( false ) ;
1175+ const m0AfterFlush = renderedText ( flushMessages [ 0 ] ) . match (
1176+ / < s e s s i o n - h i s t o r y > [ \s \S ] * ?< \/ s e s s i o n - h i s t o r y > / ,
1177+ ) ?. [ 0 ] ;
1178+ expect ( m0AfterFlush ) . toBe ( m0BeforeFlush ) ;
1179+
1180+ const deferMessages = [ userMessage ( "m3" , "defer" ) ] ;
1181+ const deferPass = injectM0M1 ( {
1182+ db,
1183+ sessionId : SESSION_ID ,
1184+ messages : deferMessages ,
1185+ state,
1186+ projectPath : PROJECT_PATH ,
1187+ projectDirectory,
1188+ } ) ;
1189+ expect ( deferPass . m0RematerializedThisPass ) . toBe ( false ) ;
1190+ expect ( renderedText ( deferMessages [ 0 ] ) ) . toBe ( renderedText ( flushMessages [ 0 ] ) ) ;
1191+ } ) ;
1192+
1193+ it ( "HARD fold binds memory expiry cutoff and materializedAt to one timestamp" , ( ) => {
1194+ db = makeDb ( ) ;
1195+ const projectDirectory = makeProjectDir ( ) ;
1196+ insertMemory ( db , {
1197+ projectPath : PROJECT_PATH ,
1198+ category : "KNOWN_ISSUES" ,
1199+ content : "D16c expiry-gap memory" ,
1200+ expiresAt : 10_500 ,
1201+ } ) ;
1202+ insertMemory ( db , {
1203+ projectPath : PROJECT_PATH ,
1204+ category : "ARCHITECTURE" ,
1205+ content : "D16c permanent anchor" ,
1206+ } ) ;
1207+
1208+ const realNow = Date . now ;
1209+ const foldAt = 10_000 ;
1210+ let nowCalls = 0 ;
1211+ Date . now = ( ) => {
1212+ nowCalls += 1 ;
1213+ return nowCalls === 1 ? foldAt : 99_000 ;
1214+ } ;
1215+
1216+ try {
1217+ const state = readStateFromMeta ( ) ;
1218+ const hard = {
1219+ systemHash : "fold-a" ,
1220+ modelKey : "model-v1" ,
1221+ cacheExpired : false ,
1222+ lastResponseTime : 0 ,
1223+ } ;
1224+ const first = materializeM0 ( {
1225+ db,
1226+ sessionId : SESSION_ID ,
1227+ state,
1228+ projectPath : PROJECT_PATH ,
1229+ projectDirectory,
1230+ hardSignals : hard ,
1231+ } ) ;
1232+ expect ( first . m0Text ) . toContain ( "D16c expiry-gap memory" ) ;
1233+ expect ( getOrCreateSessionMeta ( db , SESSION_ID ) . cachedM0MaterializedAt ) . toBe ( foldAt ) ;
1234+
1235+ nowCalls = 0 ;
1236+ const state2 = readStateFromMeta ( ) ;
1237+ const second = materializeM0 ( {
1238+ db,
1239+ sessionId : SESSION_ID ,
1240+ state : state2 ,
1241+ projectPath : PROJECT_PATH ,
1242+ projectDirectory,
1243+ hardSignals : { ...hard , systemHash : "fold-b" } ,
1244+ } ) ;
1245+ expect ( second . m0Text ) . toContain ( "D16c expiry-gap memory" ) ;
1246+ expect ( second . m0Text . match ( / < p r o j e c t - m e m o r y > [ \s \S ] * ?< \/ p r o j e c t - m e m o r y > / ) ?. [ 0 ] ) . toBe (
1247+ first . m0Text . match ( / < p r o j e c t - m e m o r y > [ \s \S ] * ?< \/ p r o j e c t - m e m o r y > / ) ?. [ 0 ] ,
1248+ ) ;
1249+ } finally {
1250+ Date . now = realNow ;
1251+ }
1252+ } ) ;
1253+
11361254 it ( "does NOT drift-refold on a defer pass when m[1] is the empty placeholder (tiny-baseline guard)" , ( ) => {
11371255 // Regression: the +15% drift refold must key off GENUINE accumulated
11381256 // delta, not the placeholder. With a tiny m[0], the ~80-byte empty
0 commit comments