@@ -65,6 +65,10 @@ const configState = vi.hoisted(() => ({
6565 cfg : { } as Record < string , unknown > ,
6666 snapshot : { config : { } , exists : false , sourceConfig : { } , valid : true } as Record < string , unknown > ,
6767} ) ) ;
68+ const pristineStartupMigrationPlan = vi . hoisted ( ( ) => ( {
69+ config : vi . fn ( ) ,
70+ state : vi . fn ( ) ,
71+ } ) ) ;
6872const readBestEffortConfig = vi . fn ( async ( ) => configState . cfg ) ;
6973type ConfigSnapshotReadOptionsStub = {
7074 isolateEnv ?: boolean ;
@@ -138,6 +142,13 @@ vi.mock("../../config/config.js", () => ({
138142 readConfigFileSnapshotWithPluginMetadata ( options ) ,
139143} ) ) ;
140144
145+ vi . mock ( "../../commands/doctor/shared/pristine-startup-state.js" , ( ) => ( {
146+ planPristineStartupConfigMigrations : ( config : unknown , env ?: NodeJS . ProcessEnv ) =>
147+ pristineStartupMigrationPlan . config ( config , env ) ,
148+ planPristineStartupStateMigrations : ( env ?: NodeJS . ProcessEnv ) =>
149+ pristineStartupMigrationPlan . state ( env ) ,
150+ } ) ) ;
151+
141152vi . mock ( "../../config/paths.js" , ( ) => ( {
142153 CONFIG_PATH : "/tmp/openclaw-test-missing-config.json" ,
143154 normalizeStateDirEnv : ( env ?: NodeJS . ProcessEnv ) => normalizeStateDirEnv ( env ) ,
@@ -342,6 +353,16 @@ describe("gateway run option collisions", () => {
342353 resetRuntimeCapture ( ) ;
343354 configState . cfg = { } ;
344355 configState . snapshot = { config : { } , exists : false , sourceConfig : { } , valid : true } ;
356+ pristineStartupMigrationPlan . config . mockReset ( ) ;
357+ pristineStartupMigrationPlan . config . mockReturnValue ( {
358+ skipAllStateMigrations : false ,
359+ skipCoreStateMigrations : false ,
360+ } ) ;
361+ pristineStartupMigrationPlan . state . mockReset ( ) ;
362+ pristineStartupMigrationPlan . state . mockReturnValue ( {
363+ skipAllStateMigrations : false ,
364+ skipCoreStateMigrations : false ,
365+ } ) ;
345366 netState . autoBindHost = "127.0.0.1" ;
346367 netState . container = false ;
347368 readBestEffortConfig . mockClear ( ) ;
@@ -425,6 +446,50 @@ describe("gateway run option collisions", () => {
425446 expect ( callOrder ) . toEqual ( [ "bootstrap" , "normalize" , "normalize" , "start" ] ) ;
426447 } ) ;
427448
449+ it ( "drops the pristine core fact when guarded config becomes stateful" , async ( ) => {
450+ const initialConfig = {
451+ gateway : { mode : "local" } ,
452+ plugins : { load : { paths : [ "/plugins/example" ] } } ,
453+ } ;
454+ configState . snapshot = {
455+ config : initialConfig ,
456+ exists : true ,
457+ hash : "initial" ,
458+ parsed : initialConfig ,
459+ path : "/tmp/openclaw.json" ,
460+ sourceConfig : initialConfig ,
461+ valid : true ,
462+ } ;
463+ pristineStartupMigrationPlan . state . mockReturnValue ( {
464+ skipAllStateMigrations : false ,
465+ skipCoreStateMigrations : true ,
466+ } ) ;
467+ const {
468+ prepareGatewayRunBootstrap,
469+ selectGatewayRunEnvironment,
470+ wasPreparedGatewayRunCoreStatePristine,
471+ } = await import ( "./pre-bootstrap.js" ) ;
472+
473+ expect ( await selectGatewayRunEnvironment ( { opts : { } , runtime : defaultRuntime } ) ) . toBe ( true ) ;
474+ const recoveredConfig = {
475+ gateway : { mode : "local" } ,
476+ session : { store : "/tmp/sessions.json" } ,
477+ } ;
478+ configState . snapshot = {
479+ config : recoveredConfig ,
480+ exists : true ,
481+ hash : "recovered" ,
482+ parsed : recoveredConfig ,
483+ path : "/tmp/openclaw.json" ,
484+ sourceConfig : recoveredConfig ,
485+ valid : true ,
486+ } ;
487+
488+ expect ( await prepareGatewayRunBootstrap ( { opts : { } , runtime : defaultRuntime } ) ) . toBe ( true ) ;
489+ expect ( wasPreparedGatewayRunCoreStatePristine ( ) ) . toBe ( false ) ;
490+ expect ( pristineStartupMigrationPlan . config ) . toHaveBeenCalledWith ( recoveredConfig , process . env ) ;
491+ } ) ;
492+
428493 it ( "refreshes the managed proxy from the final accepted config before gateway startup" , async ( ) => {
429494 const finalConfig = {
430495 gateway : { mode : "local" } ,
0 commit comments