@@ -941,6 +941,42 @@ const healthProbeGateFor = (rootDb: object): HealthProbeGate => {
941941const healthProbeGateKey = ( tenant : string , row : ConnectionRow ) : string =>
942942 JSON . stringify ( [ tenant , row . owner , row . subject , row . integration , row . name ] ) ;
943943
944+ /** The verdict a recorded dead grant answers every health read with. The
945+ * persisted `expired` verdict (written together with the dead-grant
946+ * state) is served as-is; a row whose verdict a racing writer buried (or
947+ * that somehow lacks one) gets an expired verdict synthesized from the
948+ * recorded rejection, unpersisted. */
949+ const deadGrantVerdict = (
950+ reauthState : OAuthReauthRequiredState ,
951+ row : ConnectionRow ,
952+ ) : HealthCheckResult => {
953+ const cached = Option . getOrNull ( decodeLastHealth ( row . last_health ) ) ;
954+ if ( cached !== null && cached . status === "expired" ) return cached ;
955+ return {
956+ status : "expired" ,
957+ checkedAt : reauthState . oauthReauthRequiredAt ,
958+ detail :
959+ reauthState . oauthReauthRequiredDetail ??
960+ "The authorization server rejected this connection's refresh token (invalid_grant). Reconnect to continue." ,
961+ } ;
962+ } ;
963+
964+ /** The health a connection row presents on every API read. Derived, never
965+ * written back: while `provider_state` records a dead grant, the row
966+ * presents the dead grant's expired verdict regardless of what a racing
967+ * writer left in `last_health`, so a buried verdict cannot mislead any
968+ * reader. A repair WRITE here instead would race the reconnect mint — a
969+ * stale repair that observed the pre-reconnect dead grant can pass the
970+ * verdict CAS inside one SQLite `updated_at` second and stamp the OLD
971+ * grant's expired verdict onto the fresh connection. The reconnect mint
972+ * rewrites `provider_state` wholesale, which ends this derivation with no
973+ * write to race. */
974+ const presentedLastHealth = ( row : ConnectionRow ) : HealthCheckResult | null => {
975+ const reauthState = oauthReauthRequiredFromProviderState ( row . provider_state ) ;
976+ if ( reauthState !== null ) return deadGrantVerdict ( reauthState , row ) ;
977+ return Option . getOrNull ( decodeLastHealth ( row . last_health ) ) ;
978+ } ;
979+
944980const rowToConnection = ( row : ConnectionRow ) : Connection => {
945981 const owner = row . owner as Owner ;
946982 const integration = IntegrationSlug . make ( row . integration ) ;
@@ -960,7 +996,7 @@ const rowToConnection = (row: ConnectionRow): Connection => {
960996 row . oauth_client_owner == null ? null : ( String ( row . oauth_client_owner ) as Owner ) ,
961997 oauthScope : row . oauth_scope == null ? null : String ( row . oauth_scope ) ,
962998 missingOAuthScopes : missingOAuthScopesFromProviderState ( row . provider_state ) ,
963- lastHealth : Option . getOrNull ( decodeLastHealth ( row . last_health ) ) ,
999+ lastHealth : presentedLastHealth ( row ) ,
9641000 } ;
9651001} ;
9661002
@@ -3993,25 +4029,6 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea
39934029 : { } ) ,
39944030 } ) ;
39954031
3996- /** The verdict a recorded dead grant answers every health read with. The
3997- * persisted `expired` verdict (written together with the dead-grant
3998- * state) is served as-is; a row that somehow lacks one gets an expired
3999- * verdict synthesized from the recorded rejection, unpersisted. */
4000- const deadGrantVerdict = (
4001- reauthState : OAuthReauthRequiredState ,
4002- row : ConnectionRow ,
4003- ) : HealthCheckResult => {
4004- const cached = Option . getOrNull ( decodeLastHealth ( row . last_health ) ) ;
4005- if ( cached !== null && cached . status === "expired" ) return cached ;
4006- return {
4007- status : "expired" ,
4008- checkedAt : reauthState . oauthReauthRequiredAt ,
4009- detail :
4010- reauthState . oauthReauthRequiredDetail ??
4011- "The authorization server rejected this connection's refresh token (invalid_grant). Reconnect to continue." ,
4012- } ;
4013- } ;
4014-
40154032 /** Persist a probe verdict unless the grant died while the probe was in
40164033 * flight: a concurrent refresh discovering invalid_grant writes the
40174034 * authoritative dead-grant state (with its own `expired` verdict), and a
@@ -4063,21 +4080,17 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea
40634080 // lapses — with read-time revalidation then trusting the lie forever.
40644081 // Serve the dead-grant verdict and probe nothing; this covers the
40654082 // manual "Check now" too. Only the reconnect mint, which rewrites
4066- // `provider_state` wholesale, re-opens probing. The one write here is
4067- // a repair: if some racing writer buried the recorder's `expired`
4068- // verdict ( the recorder itself cannot lose — it writes provider_state
4069- // and last_health in one statement — but e.g. a failing tool sync can
4070- // land after it), re-assert it so plain row reads agree with what
4071- // every health read serves. CAS'd on the observed stamps like every
4072- // guarded verdict write, so anything newer than this read still wins,
4073- // and a re-check then repairs against THAT row.
4083+ // `provider_state` wholesale, re-opens probing. Nothing is written:
4084+ // a buried `expired` verdict (e.g. a failing tool sync landing after
4085+ // the recorder) is already re-derived on every read through
4086+ // `presentedLastHealth`, so plain row reads agree with what this
4087+ // serves — and a repair write here could observe a pre-reconnect
4088+ // dead grant, pass the verdict CAS inside one SQLite `updated_at`
4089+ // second, and stamp the OLD grant's expired verdict onto the freshly
4090+ // reconnected row.
40744091 const reauthState = oauthReauthRequiredFromProviderState ( connectionRow . provider_state ) ;
40754092 if ( reauthState !== null ) {
40764093 const result = deadGrantVerdict ( reauthState , connectionRow ) ;
4077- const persisted = Option . getOrNull ( decodeLastHealth ( connectionRow . last_health ) ) ;
4078- if ( persisted === null || persisted . status !== "expired" ) {
4079- yield * persistHealthResult ( ref , connectionRow , result ) ;
4080- }
40814094 yield * annotateHealthVerdict ( "dead_grant" , result ) ;
40824095 return result ;
40834096 }
@@ -5710,7 +5723,7 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea
57105723 integration : IntegrationSlug . make ( row . integration ) ,
57115724 name : ConnectionName . make ( row . name ) ,
57125725 oauthScope : row . oauth_scope == null ? null : String ( row . oauth_scope ) ,
5713- lastHealth : Option . getOrNull ( decodeLastHealth ( row . last_health ) ) ,
5726+ lastHealth : presentedLastHealth ( row ) ,
57145727 } ;
57155728 } ;
57165729
0 commit comments