fix(provider): harden Agent Identity OAuth lifecycle

This commit is contained in:
elky
2026-07-23 09:33:00 +08:00
parent e49024d33b
commit 3606290ac8
84 changed files with 8063 additions and 1104 deletions
@@ -5,9 +5,10 @@ pub use snapshot::ProviderCatalogSnapshot;
pub use types::{
ProviderCatalogKeyAdaptiveState, ProviderCatalogKeyAdaptiveStateUpdate,
ProviderCatalogKeyHealthStateUpdate, ProviderCatalogKeyListOrder, ProviderCatalogKeyListQuery,
ProviderCatalogKeyRuntimeMetadataUpdate, ProviderCatalogKeyStatusSnapshotUpdate,
ProviderCatalogReadRepository, ProviderCatalogUpstreamMetadataNamespaceUpdate,
ProviderCatalogWriteRepository, StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
ProviderCatalogKeyOAuthRuntimeStateCasUpdate, ProviderCatalogKeyRuntimeMetadataUpdate,
ProviderCatalogKeyStatusSnapshotUpdate, ProviderCatalogReadRepository,
ProviderCatalogUpstreamMetadataNamespaceUpdate, ProviderCatalogWriteRepository,
StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
StoredProviderCatalogKeyMaintenanceSummary, StoredProviderCatalogKeyPage,
StoredProviderCatalogKeyStats, StoredProviderCatalogProvider,
};
@@ -31,6 +31,9 @@ impl ProviderCatalogKeyAdaptiveState {
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ProviderCatalogKeyAdaptiveStateUpdate {
pub key_id: String,
/// Optional auth_config fence for request-owned adaptive feedback.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_encrypted_auth_config: Option<String>,
pub expected: ProviderCatalogKeyAdaptiveState,
pub next: ProviderCatalogKeyAdaptiveState,
/// Top-level status fields owned by adaptive rate-limit learning.
@@ -64,9 +67,37 @@ pub struct ProviderCatalogKeyStatusSnapshotUpdate {
pub updated_at_unix_secs: Option<u64>,
}
/// Agent/runtime-owned OAuth state update fenced by the exact encrypted
/// auth_config observed before the refresh started. Repositories must update
/// only these fields and return `false` when the expected config changed.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ProviderCatalogKeyOAuthRuntimeStateCasUpdate {
pub key_id: String,
pub expected_encrypted_auth_config: Option<String>,
pub encrypted_auth_config: String,
/// Optional access-token ciphertext replacement owned by refresh success.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encrypted_api_key_update: Option<String>,
/// `None` preserves expiry; `Some(None)` clears it; `Some(Some(_))` replaces it.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at_unix_secs_update: Option<Option<u64>>,
pub oauth_invalid_at_unix_secs: Option<u64>,
pub oauth_invalid_reason: Option<String>,
/// Top-level runtime metadata namespaces to merge in the same fenced write.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub upstream_metadata_patch: Option<serde_json::Value>,
pub status_snapshot_patch: serde_json::Value,
#[serde(default)]
pub reset_error_count: bool,
pub updated_at_unix_secs: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ProviderCatalogKeyHealthStateUpdate {
pub key_id: String,
/// Optional auth_config fence for lifecycle-owned health recovery.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_encrypted_auth_config: Option<String>,
pub expected_health_by_format: Option<serde_json::Value>,
pub expected_circuit_breaker_by_format: Option<serde_json::Value>,
pub health_by_format: Option<serde_json::Value>,
@@ -829,6 +860,18 @@ pub trait ProviderCatalogWriteRepository: Send + Sync {
updated_at_unix_secs: Option<u64>,
) -> Result<bool, crate::DataLayerError>;
/// Compare-and-swap OAuth runtime credentials/status without replacing any
/// administrator-owned key fields.
async fn compare_and_update_key_oauth_runtime_state(
&self,
_update: &ProviderCatalogKeyOAuthRuntimeStateCasUpdate,
) -> Result<bool, crate::DataLayerError> {
Err(crate::DataLayerError::InvalidConfiguration(
"provider catalog OAuth runtime CAS updates are not supported by this repository"
.to_string(),
))
}
async fn update_key_health_state(
&self,
key_id: &str,