@@ -60,6 +60,9 @@ interface LiveEntry {
6060interface RegistryState {
6161 readonly entries : Ref . Ref < ReadonlyMap < ProviderInstanceId , LiveEntry > > ;
6262 readonly unavailable : Ref . Ref < ReadonlyMap < ProviderInstanceId , ServerProvider > > ;
63+ // Configs parked on an unreadable secret. A settings save that leaves one alone does not read
64+ // its vault again, so a locked vault is not prompted for on every unrelated save.
65+ readonly secretsUnreadable : Ref . Ref < ReadonlyMap < ProviderInstanceId , ProviderInstanceConfig > > ;
6366 readonly changes : PubSub . PubSub < void > ;
6467}
6568
@@ -155,7 +158,11 @@ const buildEntry = Effect.fn("ProviderInstanceRegistry.buildEntry")(function* <R
155158 readonly deferSecrets : boolean ;
156159} ) : Effect . fn . Return <
157160 | { readonly kind : "live" ; readonly live : LiveEntry }
158- | { readonly kind : "unavailable" ; readonly snapshot : ServerProvider } ,
161+ | {
162+ readonly kind : "unavailable" ;
163+ readonly snapshot : ServerProvider ;
164+ readonly secretsUnreadable ?: true ;
165+ } ,
159166 never ,
160167 R | SecretResolver . SecretResolver
161168> {
@@ -234,6 +241,7 @@ const buildEntry = Effect.fn("ProviderInstanceRegistry.buildEntry")(function* <R
234241 if ( unreadable . length > 0 ) {
235242 return {
236243 kind : "unavailable" as const ,
244+ secretsUnreadable : true as const ,
237245 snapshot : yield * buildUnavailableProviderSnapshot ( {
238246 driverKind : entry . driver ,
239247 instanceId,
@@ -304,6 +312,7 @@ const makeReconcile = <R>(input: {
304312 Effect . gen ( function * ( ) {
305313 const previousEntries = yield * Ref . get ( state . entries ) ;
306314 const previousUnavailable = yield * Ref . get ( state . unavailable ) ;
315+ const previousSecretsUnreadable = yield * Ref . get ( state . secretsUnreadable ) ;
307316 const nextRaw = Object . entries ( configMap ) ;
308317 const nextKeys = new Set < ProviderInstanceId > (
309318 nextRaw . map ( ( [ raw ] ) => ProviderInstanceId . make ( raw ) ) ,
@@ -338,12 +347,21 @@ const makeReconcile = <R>(input: {
338347 }
339348 }
340349
350+ const parkedSnapshot = ( instanceId : ProviderInstanceId , entry : ProviderInstanceConfig ) => {
351+ const parked = previousSecretsUnreadable . get ( instanceId ) ;
352+ return ! rebuild . has ( instanceId ) && parked !== undefined && entryEqual ( parked , entry )
353+ ? previousUnavailable . get ( instanceId )
354+ : undefined ;
355+ } ;
356+
341357 // Read every secret the builds below need in one go, so a vault unlocks once.
342358 const references = yield * Effect . forEach ( nextRaw , ( [ rawInstanceId , entry ] ) =>
343359 Effect . gen ( function * ( ) {
344360 const instanceId = ProviderInstanceId . make ( rawInstanceId ) ;
345361 const kept = previousEntries . has ( instanceId ) && ! replacedIds . has ( instanceId ) ;
346- return kept || ! ( yield * isEntryEnabled ( driversById , entry ) )
362+ return kept ||
363+ parkedSnapshot ( instanceId , entry ) !== undefined ||
364+ ! ( yield * isEntryEnabled ( driversById , entry ) )
347365 ? [ ]
348366 : secretReferencesOf ( driversById , entry ) ;
349367 } ) ,
@@ -354,6 +372,7 @@ const makeReconcile = <R>(input: {
354372
355373 const builtEntries = new Map < ProviderInstanceId , LiveEntry > ( ) ;
356374 const builtUnavailable = new Map < ProviderInstanceId , ServerProvider > ( ) ;
375+ const builtSecretsUnreadable = new Map < ProviderInstanceId , ProviderInstanceConfig > ( ) ;
357376 let orderChanged = false ;
358377 const previousOrder = [ ...previousEntries . keys ( ) ] ;
359378 const nextOrder : Array < ProviderInstanceId > = [ ] ;
@@ -367,6 +386,12 @@ const makeReconcile = <R>(input: {
367386 builtEntries . set ( instanceId , existing ) ;
368387 continue ;
369388 }
389+ const parked = parkedSnapshot ( instanceId , entry ) ;
390+ if ( parked !== undefined ) {
391+ builtUnavailable . set ( instanceId , parked ) ;
392+ builtSecretsUnreadable . set ( instanceId , entry ) ;
393+ continue ;
394+ }
370395
371396 const result = yield * buildEntry ( {
372397 driversById,
@@ -381,6 +406,7 @@ const makeReconcile = <R>(input: {
381406 builtEntries . set ( instanceId , result . live ) ;
382407 } else {
383408 builtUnavailable . set ( instanceId , result . snapshot ) ;
409+ if ( result . secretsUnreadable ) builtSecretsUnreadable . set ( instanceId , entry ) ;
384410 }
385411 }
386412
@@ -410,6 +436,7 @@ const makeReconcile = <R>(input: {
410436
411437 yield * Ref . set ( state . entries , builtEntries ) ;
412438 yield * Ref . set ( state . unavailable , builtUnavailable ) ;
439+ yield * Ref . set ( state . secretsUnreadable , builtSecretsUnreadable ) ;
413440
414441 if ( entriesChanged || unavailableChanged ) {
415442 yield * PubSub . publish ( state . changes , undefined ) ;
@@ -446,10 +473,13 @@ export const makeProviderInstanceRegistry = Effect.fn(
446473
447474 const entries = yield * Ref . make < ReadonlyMap < ProviderInstanceId , LiveEntry > > ( new Map ( ) ) ;
448475 const unavailable = yield * Ref . make < ReadonlyMap < ProviderInstanceId , ServerProvider > > ( new Map ( ) ) ;
476+ const secretsUnreadable = yield * Ref . make <
477+ ReadonlyMap < ProviderInstanceId , ProviderInstanceConfig >
478+ > ( new Map ( ) ) ;
449479 const changes = yield * PubSub . unbounded < void > ( ) ;
450480 yield * Effect . addFinalizer ( ( ) => PubSub . shutdown ( changes ) ) ;
451481
452- const state : RegistryState = { entries, unavailable, changes } ;
482+ const state : RegistryState = { entries, unavailable, secretsUnreadable , changes } ;
453483 const reconcileWithR = makeReconcile ( {
454484 state,
455485 driversById,
@@ -488,7 +518,10 @@ export const makeProviderInstanceRegistry = Effect.fn(
488518 const rebuild = new Set < ProviderInstanceId > ( ) ;
489519 for ( const [ rawInstanceId , entry ] of targets ) {
490520 const current = live . get ( ProviderInstanceId . make ( rawInstanceId ) ) ;
491- if ( current === undefined ) continue ;
521+ if ( current === undefined ) {
522+ rebuild . add ( ProviderInstanceId . make ( rawInstanceId ) ) ;
523+ continue ;
524+ }
492525 const { secrets, unreadable } = yield * readInstanceSecrets (
493526 driversById . get ( entry . driver ) ,
494527 entry ,
0 commit comments