@@ -128,30 +128,81 @@ func TestEnsureOriginRollsBackWhenBootstrapFails(t *testing.T) {
128128 require .NoError (t , err )
129129 assert .Empty (t , stored , "failed bootstrap must roll the origin back" )
130130
131+ // The rollback deletes the origin key entirely rather than writing an empty
132+ // value, so the export gate (which tests key existence) stays closed. A
133+ // session written after the failed creation must not enqueue.
134+ seedSession (t , database , "sess-2" , "alpha" )
135+ gated , err := database .PendingArtifactExports (t .Context (), 10 )
136+ require .NoError (t , err )
137+ require .Empty (t , gated , "failed origin creation must leave the export gate closed" )
138+
131139 bootstrapExportQueue = (* db .DB ).BootstrapArtifactExportQueue
132140 origin , err := EnsureOrigin (database )
133141 require .NoError (t , err )
134142 require .NotEmpty (t , origin , "retry after rollback must re-run creation" )
135143 pending , err := database .PendingArtifactExports (t .Context (), 10 )
136144 require .NoError (t , err )
137- require .Len (t , pending , 1 , "retry must re-run the bootstrap" )
138- assert .Equal (t , "sess-1" , pending [0 ].SessionID )
145+ require .Len (t , pending , 2 , "retry must re-run the bootstrap for every pre-existing session" )
146+ assert .ElementsMatch (t , []string {"sess-1" , "sess-2" }, []string {
147+ pending [0 ].SessionID , pending [1 ].SessionID ,
148+ })
139149}
140150
141- func TestAdoptOriginRestoresPreviousOriginWhenBootstrapFails (t * testing.T ) {
151+ func TestAdoptOriginRestoresPreviousOriginWhenRequeueFails (t * testing.T ) {
142152 database := testDB (t )
143153 require .NoError (t , AdoptOrigin (database , "before-a1b2c3" ))
144154
145- injected := errors .New ("bootstrap exploded" )
146- bootstrapExportQueue = func (* db.DB ) error { return injected }
147- t .Cleanup (func () { bootstrapExportQueue = (* db .DB ).BootstrapArtifactExportQueue })
155+ injected := errors .New ("requeue exploded" )
156+ requeueExportQueue = func (* db.DB ) error { return injected }
157+ t .Cleanup (func () { requeueExportQueue = (* db .DB ).RequeueAllArtifactExports })
148158
159+ // Adopting a divergent origin over an established one routes through the
160+ // requeue path, not bootstrap.
149161 err := AdoptOrigin (database , "after-d4e5f6" )
150162 require .ErrorIs (t , err , injected )
151163 stored , err := StoredOrigin (database )
152164 require .NoError (t , err )
153165 assert .Equal (t , "before-a1b2c3" , stored ,
154- "failed adoption must restore the previous origin" )
166+ "failed divergent adoption must restore the previous origin" )
167+ }
168+
169+ // TestAdoptOriginRequeuesAllExportsOnDivergentAdoption covers the divergent
170+ // adoption path: when a new origin replaces an established one whose sessions
171+ // are already acknowledged, INSERT OR IGNORE bootstrap would leave the ledger
172+ // empty, so every owned session must be force-requeued with a bumped
173+ // generation.
174+ func TestAdoptOriginRequeuesAllExportsOnDivergentAdoption (t * testing.T ) {
175+ database := testDB (t )
176+ require .NoError (t , AdoptOrigin (database , "origin-a1b2c3" ))
177+ seedSession (t , database , "sess-1" , "alpha" )
178+ seedSession (t , database , "sess-2" , "alpha" )
179+
180+ ctx := t .Context ()
181+ pending , err := database .PendingArtifactExports (ctx , 10 )
182+ require .NoError (t , err )
183+ require .Len (t , pending , 2 )
184+ genBefore := map [string ]int64 {}
185+ for _ , item := range pending {
186+ genBefore [item .SessionID ] = item .Generation
187+ }
188+
189+ // Simulate the prior origin having fully published every session.
190+ require .NoError (t , database .AcknowledgeArtifactExports (ctx , pending ))
191+ drained , err := database .PendingArtifactExports (ctx , 10 )
192+ require .NoError (t , err )
193+ require .Empty (t , drained )
194+
195+ require .NoError (t , AdoptOrigin (database , "origin-d4e5f6" ))
196+ pending , err = database .PendingArtifactExports (ctx , 10 )
197+ require .NoError (t , err )
198+ require .Len (t , pending , 2 , "divergent adoption re-verifies every owned session" )
199+ assert .ElementsMatch (t , []string {"sess-1" , "sess-2" }, []string {
200+ pending [0 ].SessionID , pending [1 ].SessionID ,
201+ })
202+ for _ , item := range pending {
203+ assert .Greater (t , item .Generation , genBefore [item .SessionID ],
204+ "divergent adoption must bump the generation of every requeued session" )
205+ }
155206}
156207
157208// TestAdoptOriginBootstrapsPreExistingLocalSessions mirrors the EnsureOrigin
0 commit comments