@@ -347,6 +347,121 @@ describe("config observe recovery", () => {
347347 } ) ;
348348 } ) ;
349349
350+ it ( "does not restore stale last-known-good for plugin schema evolution issues" , async ( ) => {
351+ await withSuiteHome ( async ( home ) => {
352+ const { deps, configPath, warn } = makeDeps ( home ) ;
353+ const staleSnapshot = await makeSnapshot ( configPath , {
354+ gateway : { mode : "local" } ,
355+ agents : { defaults : { model : "sonnet-4.6" } } ,
356+ plugins : {
357+ entries : {
358+ "lossless-claw" : {
359+ enabled : true ,
360+ config : { compactionMode : "legacy" } ,
361+ } ,
362+ } ,
363+ } ,
364+ } ) ;
365+ await expect (
366+ promoteConfigSnapshotToLastKnownGood ( {
367+ deps,
368+ snapshot : staleSnapshot ,
369+ logger : deps . logger ,
370+ } ) ,
371+ ) . resolves . toBe ( true ) ;
372+
373+ const activeConfig = {
374+ gateway : { mode : "local" } ,
375+ agents : { defaults : { model : "gpt-5.4" } } ,
376+ plugins : {
377+ entries : {
378+ "lossless-claw" : {
379+ enabled : true ,
380+ config : { compactionMode : "adaptive" , cacheAwareCompaction : true } ,
381+ } ,
382+ } ,
383+ } ,
384+ } ;
385+ const active = await writeConfigRaw ( configPath , activeConfig ) ;
386+ const restored = await recoverConfigFromLastKnownGood ( {
387+ deps,
388+ snapshot : {
389+ ...staleSnapshot ,
390+ raw : active . raw ,
391+ parsed : active . parsed ,
392+ valid : false ,
393+ issues : [
394+ {
395+ path : "plugins.entries.lossless-claw.config.cacheAwareCompaction" ,
396+ message : "invalid config: must NOT have additional properties" ,
397+ } ,
398+ ] ,
399+ } ,
400+ reason : "reload-invalid-config" ,
401+ } ) ;
402+
403+ expect ( restored ) . toBe ( false ) ;
404+ await expect ( fsp . readFile ( configPath , "utf-8" ) ) . resolves . toBe ( active . raw ) ;
405+ expect ( warn ) . toHaveBeenCalledWith (
406+ expect . stringContaining ( "Config last-known-good recovery skipped" ) ,
407+ ) ;
408+ } ) ;
409+ } ) ;
410+
411+ it ( "does not restore stale last-known-good for plugin minHostVersion skew issues" , async ( ) => {
412+ await withSuiteHome ( async ( home ) => {
413+ const { deps, configPath } = makeDeps ( home ) ;
414+ const staleSnapshot = await makeSnapshot ( configPath , {
415+ gateway : { mode : "local" } ,
416+ plugins : {
417+ entries : {
418+ feishu : { enabled : false } ,
419+ } ,
420+ } ,
421+ } ) ;
422+ await expect (
423+ promoteConfigSnapshotToLastKnownGood ( {
424+ deps,
425+ snapshot : staleSnapshot ,
426+ logger : deps . logger ,
427+ } ) ,
428+ ) . resolves . toBe ( true ) ;
429+
430+ const activeConfig = {
431+ gateway : { mode : "local" } ,
432+ agents : { defaults : { model : "gpt-5.4" } } ,
433+ plugins : {
434+ entries : {
435+ feishu : { enabled : true , config : { appId : "feishu-app" } } ,
436+ whatsapp : { enabled : true , config : { account : "primary" } } ,
437+ } ,
438+ } ,
439+ } ;
440+ const active = await writeConfigRaw ( configPath , activeConfig ) ;
441+ const restored = await recoverConfigFromLastKnownGood ( {
442+ deps,
443+ snapshot : {
444+ ...staleSnapshot ,
445+ raw : active . raw ,
446+ parsed : active . parsed ,
447+ valid : false ,
448+ issues : [
449+ {
450+ path : "plugins.entries.feishu" ,
451+ message :
452+ "plugin feishu: plugin requires OpenClaw >=2026.4.23, but this host is 2026.4.22; skipping load" ,
453+ } ,
454+ ] ,
455+ } ,
456+ reason : "reload-invalid-config" ,
457+ } ) ;
458+
459+ expect ( restored ) . toBe ( false ) ;
460+ await expect ( fsp . readFile ( configPath , "utf-8" ) ) . resolves . toBe ( active . raw ) ;
461+ expect ( JSON5 . parse ( active . raw ) ) . toEqual ( activeConfig ) ;
462+ } ) ;
463+ } ) ;
464+
350465 it ( "refuses to promote redacted secret placeholders" , async ( ) => {
351466 await withSuiteHome ( async ( home ) => {
352467 const warn = vi . fn ( ) ;
0 commit comments