You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/10/22 14:51:33 UTC

(maka) 09/18: Use injected public model snapshots for settings and admission

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch rust-preview
in repository https://gitbox.apache.org/repos/asf/maka.git

commit 50b9d1bcd256155808ec1169091fc97be0b2a095
Author: M4n5ter <m4...@gmail.com>
AuthorDate: Thu Oct 22 21:59:26 2026 +0800

    Use injected public model snapshots for settings and admission
---
 plugins/agent/native/src/lib.rs                    |  10 +-
 plugins/agent/native/src/model.rs                  |  28 ++++-
 plugins/agent/native/src/owner.rs                  |  26 +++--
 plugins/agent/native/src/owner/compaction.rs       |   1 +
 plugins/agent/native/src/owner/execution.rs        |   1 +
 plugins/agent/native/src/owner/preferences.rs      |   8 +-
 plugins/agent/native/src/owner/recap.rs            |   1 +
 .../agent/native/src/owner/scheduler/dispatch.rs   |   1 +
 plugins/agent/native/src/owner/skills.rs           |  18 +--
 plugins/agent/native/src/owner/tests.rs            |  20 +++-
 plugins/agent/native/src/owner/tests/execution.rs  |   4 +-
 plugins/agent/native/src/owner/tests/executor.rs   |   4 +-
 plugins/agent/native/src/owner/tests/health.rs     |   2 +-
 .../agent/native/src/owner/tests/large_messages.rs |   2 +-
 .../native/src/owner/tests/prompt_language.rs      |   2 +-
 plugins/agent/native/src/owner/tests/queue.rs      |   2 +-
 plugins/agent/native/src/owner/tests/scheduler.rs  |   2 +-
 plugins/agent/native/src/owner/tests/selection.rs  | 108 +++++++++++++++++
 .../agent/native/src/owner/tests/service_tools.rs  |   4 +-
 .../owner/tests/service_tools/storm/combined.rs    |   4 +-
 plugins/agent/native/src/owner/tests/settings.rs   |   2 +-
 .../native/src/owner/tests/skill_preparation.rs    |   2 +-
 plugins/agent/native/src/owner/tests/terminal.rs   |   2 +-
 plugins/agent/native/src/owner/tests/todo.rs       |   4 +-
 plugins/agent/native/src/owner/tests/workhub.rs    |   4 +-
 plugins/agent/native/src/owner/tests/workspaces.rs |   2 +-
 plugins/agent/native/src/owner/tests/worktrees.rs  |   4 +-
 plugins/agent/native/src/workhub.rs                |  10 +-
 plugins/agent/native/src/workhub/presets.rs        |   8 +-
 plugins/agent/providers/src/api.rs                 |  18 ++-
 plugins/agent/providers/src/api/effects.rs         |   8 +-
 plugins/agent/providers/src/api/metadata.rs        |  98 +---------------
 plugins/agent/providers/src/api/options.rs         |  82 +++++--------
 .../agent/providers/src/api/options/compatible.rs  |  54 ++-------
 plugins/agent/providers/src/api/options/relay.rs   |   8 +-
 plugins/agent/providers/src/lib.rs                 |   3 +-
 plugins/agent/providers/tests/api.rs               |  16 ++-
 plugins/agent/providers/tests/api/discovery.rs     |   1 +
 plugins/agent/providers/tests/api/effects.rs       |  15 ++-
 plugins/agent/providers/tests/api/policy.rs        |   1 +
 plugins/settings/native/src/connections.rs         |  26 +++--
 plugins/settings/native/src/credential.rs          |  10 +-
 plugins/settings/native/src/credential/tests.rs    |  24 +++-
 plugins/settings/native/src/lib.rs                 |   8 +-
 plugins/settings/native/src/service.rs             | 130 +++++++++++++++++----
 plugins/settings/native/src/setup.rs               |  15 ++-
 .../maka/terminal-shell/tests/real_host/oauth.rs   |   7 +-
 47 files changed, 493 insertions(+), 317 deletions(-)

diff --git a/plugins/agent/native/src/lib.rs b/plugins/agent/native/src/lib.rs
index 7e8117f02d..7fffcb8271 100644
--- a/plugins/agent/native/src/lib.rs
+++ b/plugins/agent/native/src/lib.rs
@@ -34,12 +34,16 @@ use maka_host_runtime::application::{NativeActivation, NativeContext, NativeRegi
 use maka_workspace_support as workspace;
 use std::sync::Arc;
 
-pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
+pub fn provide(
+    registry: &mut NativeRegistry,
+    catalog: Arc<maka_providers::catalog::Catalog>,
+) -> Result<(), String> {
     registry.insert(
         maka_agent_api::IMPLEMENTATION.into(),
-        Arc::new(|context, configuration| {
+        Arc::new(move |context, configuration| {
+            let catalog = catalog.clone();
             Box::pin(async move {
-                match owner::Owner::open(context, configuration).await {
+                match owner::Owner::open(context, configuration, catalog).await {
                     Ok(owner) => {
                         let service = Arc::new(service::Agent(owner));
                         NativeActivation {
diff --git a/plugins/agent/native/src/model.rs b/plugins/agent/native/src/model.rs
index 2efd1aef69..b15b7f7d8c 100644
--- a/plugins/agent/native/src/model.rs
+++ b/plugins/agent/native/src/model.rs
@@ -82,6 +82,9 @@ pub(crate) struct AcceptedModel {
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub editing_tools: Option<EditingTools>,
     context: ModelRequestContext,
+    /// Public defaults at admission only; account reports and overrides stay live.
+    #[serde(deserialize_with = "Option::deserialize")]
+    public_model: Option<ModelInfo>,
     provider: maka_agent_facts::provider::Identity,
     provider_configuration: Value,
     basis: ExecutionBasis,
@@ -102,6 +105,7 @@ pub(crate) struct Source {
 impl Source {
     pub async fn select(
         configuration: Arc<ConfigurationStore>,
+        catalog: Arc<maka_providers::catalog::Catalog>,
         credentials: Credentials,
         selection: ModelSelection,
         thinking_level: Option<ThinkingLevel>,
@@ -111,6 +115,8 @@ impl Source {
         let span = tracing::warn_span!("agent_model_preparation", session_id = %session,
             connection_id = %selection.connection_id, activation = %revision.activation);
         async move {
+            let public_guard = catalog.snapshot.read().await;
+            let snapshot = public_guard.as_ref().map_err(|_| unavailable())?.clone();
             let material = configuration
                 .observe_selected_model(selection.clone())
                 .await
@@ -119,9 +125,14 @@ impl Source {
                     unavailable()
                 })?
                 .ok_or_else(unavailable)?;
+            drop(public_guard);
             let row = &material.connection;
+            let public_model = snapshot.get(&row.provider.name, &selection.model_id).cloned();
+            let snapshot = Arc::new(maka_providers::catalog::Snapshot::for_model(
+                &row.provider.name, &selection.model_id, public_model.clone(),
+            ).map_err(|_| unavailable())?);
             let definition =
-                Arc::new(maka_providers::definition(&row.provider).map_err(|_| {
+                Arc::new(maka_providers::definition(&row.provider, snapshot).map_err(|_| {
                     tracing::warn!(stage = "provider_definition", cause = "provider_definition_unavailable",
                         provider = %row.provider.name, "Model preparation failed");
                     unavailable()
@@ -133,6 +144,7 @@ impl Source {
                 credentials,
                 accepted: AcceptedModel {
                     context: request_context(row, &model.info)?,
+                    public_model,
                     editing_tools: Some(editing_tools(row, &selection.model_id, &model.base_url)),
                     tool_mode: ToolMode::for_model(
                         &selection.model_id,
@@ -171,8 +183,14 @@ impl Source {
         let _span = tracing::warn_span!("agent_model_preparation", session_id = %session,
             connection_id = %accepted.binding.connection_id, activation = %revision.activation)
         .entered();
+        let snapshot = maka_providers::catalog::Snapshot::for_model(
+            &accepted.provider.name,
+            &accepted.binding.model,
+            accepted.public_model.clone(),
+        )
+        .map_err(|_| unavailable())?;
         let definition =
-            Arc::new(maka_providers::definition(&accepted.provider).map_err(|_| {
+            Arc::new(maka_providers::definition(&accepted.provider, Arc::new(snapshot)).map_err(|_| {
                 tracing::warn!(stage = "restore_provider_definition", cause = "provider_definition_unavailable",
                     provider = %accepted.provider.name, "Model preparation failed");
                 unavailable()
@@ -420,9 +438,12 @@ impl ModelSource for Source {
 /// Resolve tool compatibility without preparing credentials or starting execution.
 pub(crate) async fn candidate_editing_tools(
     configuration: &Arc<ConfigurationStore>,
+    catalog: &maka_providers::catalog::Catalog,
     selection: ModelSelection,
     thinking_level: Option<ThinkingLevel>,
 ) -> Result<EditingTools, RunError> {
+    let public_guard = catalog.snapshot.read().await;
+    let snapshot = public_guard.as_ref().map_err(|_| unavailable())?.clone();
     let material = configuration
         .observe_selected_model(selection.clone())
         .await
@@ -435,8 +456,9 @@ pub(crate) async fn candidate_editing_tools(
             unavailable()
         })?
         .ok_or_else(unavailable)?;
+    drop(public_guard);
     let row = &material.connection;
-    let definition = maka_providers::definition(&row.provider).map_err(|_| {
+    let definition = maka_providers::definition(&row.provider, snapshot).map_err(|_| {
         tracing::warn!(stage = "provider_definition", cause = "provider_definition_unavailable", connection_id = %row.connection_id,
             provider = %row.provider.name, "Model preparation failed");
         unavailable()
diff --git a/plugins/agent/native/src/owner.rs b/plugins/agent/native/src/owner.rs
index eac9659db5..53d5d7085b 100644
--- a/plugins/agent/native/src/owner.rs
+++ b/plugins/agent/native/src/owner.rs
@@ -171,6 +171,7 @@ pub(crate) struct Owner {
     pub prefix: String,
     pub data_source: DataSource,
     pub(crate) configuration: Arc<ConfigurationStore>,
+    pub(crate) catalog: Arc<maka_providers::catalog::Catalog>,
     credentials: crate::model::Credentials,
     revision: SourceRevision,
     behavior: Option<BuiltinBehavior>,
@@ -221,27 +222,26 @@ impl Owner {
         self.executors.setup(input, context).await
     }
 
-    pub async fn open(context: NativeContext, input: Value) -> Result<Arc<Self>, String> {
-        Self::open_behavior(
-            context,
-            input,
-            IMPLEMENTATION,
-            None,
-            vec![],
-            BTreeMap::new(),
-            None,
-        )
-        .await
+    pub async fn open(
+        context: NativeContext,
+        input: Value,
+        catalog: Arc<maka_providers::catalog::Catalog>,
+    ) -> Result<Arc<Self>, String> {
+        Self::open_behavior(context, input, None, vec![], BTreeMap::new(), None, catalog).await
     }
     pub(crate) async fn open_behavior(
         context: NativeContext,
         input: Value,
-        implementation: &'static str,
         behavior: Option<BuiltinBehavior>,
         mut additional: Vec<maka_agent_facts::tools::ToolRegistration>,
         internal_bindings: BTreeMap<String, Option<String>>,
         graphs: Option<Arc<crate::workhub::tasks::graph::Graphs>>,
+        catalog: Arc<maka_providers::catalog::Catalog>,
     ) -> Result<Arc<Self>, String> {
+        let implementation = match behavior {
+            Some(BuiltinBehavior::WorkHub) => maka_agent_api::workhub::IMPLEMENTATION,
+            None => IMPLEMENTATION,
+        };
         if !jsonschema_valid(&input) {
             return Err("agent_configuration_invalid".into());
         }
@@ -463,6 +463,7 @@ impl Owner {
             internal_bindings,
             settings,
             configuration,
+            catalog,
             credentials,
             engine,
             tools,
@@ -911,6 +912,7 @@ impl Owner {
             Selection::Model(selection, thinking_level) => PreparedBackend::Model(Box::new(
                 crate::model::Source::select(
                     self.configuration.clone(),
+                    self.catalog.clone(),
                     self.credentials.clone(),
                     selection,
                     thinking_level,
diff --git a/plugins/agent/native/src/owner/compaction.rs b/plugins/agent/native/src/owner/compaction.rs
index 17731a8968..8c43202490 100644
--- a/plugins/agent/native/src/owner/compaction.rs
+++ b/plugins/agent/native/src/owner/compaction.rs
@@ -118,6 +118,7 @@ impl Owner {
         }
         let (source, prepared) = crate::model::Source::select(
             self.configuration.clone(),
+            self.catalog.clone(),
             self.credentials.clone(),
             input.model,
             input.thinking_level,
diff --git a/plugins/agent/native/src/owner/execution.rs b/plugins/agent/native/src/owner/execution.rs
index c3b0d10b99..bf9e8d9ddb 100644
--- a/plugins/agent/native/src/owner/execution.rs
+++ b/plugins/agent/native/src/owner/execution.rs
@@ -256,6 +256,7 @@ impl Owner {
         }
         let (mut source, prepared) = crate::model::Source::select(
             self.configuration.clone(),
+            self.catalog.clone(),
             self.credentials.clone(),
             input.model,
             input.thinking_level,
diff --git a/plugins/agent/native/src/owner/preferences.rs b/plugins/agent/native/src/owner/preferences.rs
index 193a6b3353..53adca556d 100644
--- a/plugins/agent/native/src/owner/preferences.rs
+++ b/plugins/agent/native/src/owner/preferences.rs
@@ -46,6 +46,11 @@ impl Owner {
                 session: Box::new(crate::service::summary(self, session)),
             });
         }
+        let public_guard = self.catalog.snapshot.read().await;
+        let snapshot = public_guard
+            .as_ref()
+            .map_err(|_| "agent_configuration_unavailable")?
+            .clone();
         let available = match &input.backend {
             BackendPreference::Model {
                 connection_id,
@@ -62,7 +67,7 @@ impl Owner {
                     row.connection_id == *connection_id
                         && row.enabled
                         && row.enabled_model_ids.contains(model_id)
-                        && maka_providers::definition(&row.provider).is_ok()
+                        && maka_providers::definition(&row.provider, snapshot.clone()).is_ok()
                 }),
             BackendPreference::Executor { selection } => self
                 .settings
@@ -70,6 +75,7 @@ impl Owner {
                 .iter()
                 .any(|entry| entry.name == selection.executor),
         };
+        drop(public_guard);
         if !available {
             return Ok(MetadataResult::Rejected {
                 reason: Rejection::ToolsUnavailable,
diff --git a/plugins/agent/native/src/owner/recap.rs b/plugins/agent/native/src/owner/recap.rs
index 22bcf8090e..c64a611379 100644
--- a/plugins/agent/native/src/owner/recap.rs
+++ b/plugins/agent/native/src/owner/recap.rs
@@ -255,6 +255,7 @@ impl Owner {
             input.validate().map_err(failed)?;
             let (source, _) = crate::model::Source::select(
                 self.configuration.clone(),
+                self.catalog.clone(),
                 self.credentials.clone(),
                 model,
                 None,
diff --git a/plugins/agent/native/src/owner/scheduler/dispatch.rs b/plugins/agent/native/src/owner/scheduler/dispatch.rs
index 5e1e98fe81..0ff07837d1 100644
--- a/plugins/agent/native/src/owner/scheduler/dispatch.rs
+++ b/plugins/agent/native/src/owner/scheduler/dispatch.rs
@@ -140,6 +140,7 @@ impl Owner {
         .await?;
         let (source, _) = crate::model::Source::select(
             self.configuration.clone(),
+            self.catalog.clone(),
             self.credentials.clone(),
             model,
             thinking,
diff --git a/plugins/agent/native/src/owner/skills.rs b/plugins/agent/native/src/owner/skills.rs
index dd4a31fd9e..66307b3d0c 100644
--- a/plugins/agent/native/src/owner/skills.rs
+++ b/plugins/agent/native/src/owner/skills.rs
@@ -119,13 +119,17 @@ impl Owner {
         else {
             return Ok(SkillCandidates::default());
         };
-        let editing =
-            match crate::model::candidate_editing_tools(&self.configuration, model, thinking_level)
-                .await
-            {
-                Ok(editing) => editing,
-                Err(_) => return Ok(SkillCandidates::default()),
-            };
+        let editing = match crate::model::candidate_editing_tools(
+            &self.configuration,
+            &self.catalog,
+            model,
+            thinking_level,
+        )
+        .await
+        {
+            Ok(editing) => editing,
+            Err(_) => return Ok(SkillCandidates::default()),
+        };
         workspace::validate(
             &self.workspace_roots,
             &session.configuration.workspace,
diff --git a/plugins/agent/native/src/owner/tests.rs b/plugins/agent/native/src/owner/tests.rs
index 51e6d75026..ed06630586 100644
--- a/plugins/agent/native/src/owner/tests.rs
+++ b/plugins/agent/native/src/owner/tests.rs
@@ -483,6 +483,12 @@ fn log_text(file: &mut std::fs::File) -> String {
     text
 }
 
+fn bundled_catalog() -> Arc<maka_providers::catalog::Catalog> {
+    Arc::new(maka_providers::catalog::Catalog::new(
+        maka_providers::catalog::Snapshot::bundled().unwrap(),
+    ))
+}
+
 struct Fixture {
     services: maka_host_runtime::application::NativeServices,
     _dir: tempfile::TempDir,
@@ -647,7 +653,8 @@ impl Fixture {
         let capture = captured.clone();
         let replacement = Arc::new(StdMutex::new(model));
         let mut native = NativeRegistry::default();
-        maka_settings_native::provide(&mut native).unwrap();
+        let catalog = bundled_catalog();
+        maka_settings_native::provide(&mut native, catalog.clone()).unwrap();
         if let Some(tooling) = &tooling {
             tooling.provide(&mut native);
         }
@@ -658,10 +665,11 @@ impl Fixture {
                 IMPLEMENTATION.into(),
                 Arc::new(move |context, configuration| {
                     let capture = capture.clone();
+                    let catalog = catalog.clone();
                     let replacement = replacement.lock().unwrap().take();
                     Box::pin(async move {
                         let services = context.services();
-                        let mut owner = Owner::open(context, configuration).await.unwrap();
+                        let mut owner = Owner::open(context, configuration, catalog).await.unwrap();
                         if let Some(model) = replacement {
                             let store = owner.store.clone();
                             Arc::get_mut(&mut owner).unwrap().engine = Engine::new(
@@ -1499,7 +1507,7 @@ async fn public_availability_resumes_stable_sessions_and_separates_deployments()
         let deployment = fixture.app.source().unwrap().deployment;
         fixture.app.shutdown().await.unwrap();
         let mut native = NativeRegistry::default();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         crate::owner::tests::provide_all(&mut native).unwrap();
         let resumed = Application::start_from_store(
             fixture.store.clone(),
@@ -1540,7 +1548,7 @@ async fn public_availability_resumes_stable_sessions_and_separates_deployments()
             .await
             .unwrap();
         let mut native = NativeRegistry::default();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         crate::owner::tests::provide_all(&mut native).unwrap();
         let other = Application::start_from_store(
             fixture.store.clone(),
@@ -1894,7 +1902,7 @@ fn catalog_interaction(
 }
 
 fn provide_all(registry: &mut NativeRegistry) -> Result<(), String> {
-    crate::provide(registry)?;
+    crate::provide(registry, bundled_catalog())?;
     maka_acp_native::provide(registry)?;
     maka_files_native::provide(registry)?;
     maka_processes_native::provide(registry)?;
@@ -1903,6 +1911,6 @@ fn provide_all(registry: &mut NativeRegistry) -> Result<(), String> {
     maka_interactions_native::forms::provide(registry)?;
     maka_web::provide(registry)?;
     maka_jev_native::provide(registry)?;
-    crate::workhub::provide(registry)?;
+    crate::workhub::provide(registry, bundled_catalog())?;
     Ok(())
 }
diff --git a/plugins/agent/native/src/owner/tests/execution.rs b/plugins/agent/native/src/owner/tests/execution.rs
index 7d2b4cb6ab..323a4808e9 100644
--- a/plugins/agent/native/src/owner/tests/execution.rs
+++ b/plugins/agent/native/src/owner/tests/execution.rs
@@ -408,14 +408,14 @@ async fn application_reactivation_resumes_sealed_source_through_original_engine(
         let captured = Arc::new(StdMutex::new(None));
         let capture = captured.clone();
         let mut native = NativeRegistry::default();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         native
             .insert(
                 IMPLEMENTATION.into(),
                 Arc::new(move |context, configuration| {
                     let capture = capture.clone();
                     Box::pin(async move {
-                        let owner = Owner::open(context, configuration).await.unwrap();
+                        let owner = Owner::open(context, configuration, crate::owner::tests::bundled_catalog()).await.unwrap();
                         *capture.lock().unwrap() = Some(owner.clone());
                         let service = Arc::new(Agent(owner));
                         NativeActivation {
diff --git a/plugins/agent/native/src/owner/tests/executor.rs b/plugins/agent/native/src/owner/tests/executor.rs
index aedf457592..1cefd1e323 100644
--- a/plugins/agent/native/src/owner/tests/executor.rs
+++ b/plugins/agent/native/src/owner/tests/executor.rs
@@ -394,7 +394,7 @@ async fn activate_backend(
             .unwrap();
     }
     let mut native = NativeRegistry::default();
-    maka_settings_native::provide(&mut native).unwrap();
+    maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
     if acp.is_some() {
         maka_acp_native::provide(&mut native).unwrap();
         maka_processes_native::provide(&mut native).unwrap();
@@ -410,7 +410,7 @@ async fn activate_backend(
                 let capture = capture.clone();
                 Box::pin(async move {
                     let services = context.services();
-                    let owner = Owner::open(context, configuration).await.unwrap();
+                    let owner = Owner::open(context, configuration, crate::owner::tests::bundled_catalog()).await.unwrap();
                     *capture.lock().unwrap() = Some((owner.clone(), services));
                     let agent = Arc::new(Agent(owner));
                     NativeActivation {
diff --git a/plugins/agent/native/src/owner/tests/health.rs b/plugins/agent/native/src/owner/tests/health.rs
index 838b9e98c8..85b35f38ae 100644
--- a/plugins/agent/native/src/owner/tests/health.rs
+++ b/plugins/agent/native/src/owner/tests/health.rs
@@ -198,7 +198,7 @@ mod acceptance {
             let original_receipt = f.store.operation_observation(&original).await.unwrap().unwrap();
             f.app.shutdown().await.unwrap();
             let mut native = NativeRegistry::default();
-            maka_settings_native::provide(&mut native).unwrap();
+            maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
             provide_all(&mut native).unwrap();
             let policy = service_tools::Setup::health().policy();
             f.issuer = policy.issuer();
diff --git a/plugins/agent/native/src/owner/tests/large_messages.rs b/plugins/agent/native/src/owner/tests/large_messages.rs
index 8df9ff6397..2cfc619155 100644
--- a/plugins/agent/native/src/owner/tests/large_messages.rs
+++ b/plugins/agent/native/src/owner/tests/large_messages.rs
@@ -154,7 +154,7 @@ async fn public_send_preserves_six_hundred_kib_through_durable_admission_and_mai
             f.store.shutdown().await.unwrap();
             f.store = Arc::new(EventLog::for_root(root_owner).await.unwrap());
             let mut native = NativeRegistry::default();
-            maka_settings_native::provide(&mut native).unwrap();
+            maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
             provide_all(&mut native).unwrap();
             f.app = Application::start_from_store(
                 f.store.clone(), deployment, native, f.issuer.clone(),
diff --git a/plugins/agent/native/src/owner/tests/prompt_language.rs b/plugins/agent/native/src/owner/tests/prompt_language.rs
index 9ac2f024dd..df897877c7 100644
--- a/plugins/agent/native/src/owner/tests/prompt_language.rs
+++ b/plugins/agent/native/src/owner/tests/prompt_language.rs
@@ -145,7 +145,7 @@ async fn update_preferences(
 
 async fn start_settings_app(fixture: &Fixture, deployment: Uuid) -> Application {
     let mut native = NativeRegistry::default();
-    maka_settings_native::provide(&mut native).unwrap();
+    maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
     provide_all(&mut native).unwrap();
     let app = Application::start_from_store(
         fixture.store.clone(),
diff --git a/plugins/agent/native/src/owner/tests/queue.rs b/plugins/agent/native/src/owner/tests/queue.rs
index fa60a56b3e..fa871ba3fe 100644
--- a/plugins/agent/native/src/owner/tests/queue.rs
+++ b/plugins/agent/native/src/owner/tests/queue.rs
@@ -329,7 +329,7 @@ use maka_agent_facts::execution::ThinkingLevel::High;
         drop(raw);
         let mut native = NativeRegistry::default();
         crate::owner::tests::provide_all(&mut native).unwrap();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         let app = Application::start_from_store(fixture.store.clone(), deployment, native, fixture.issuer.clone(), Arc::new(Grant(fixture.issuer.clone(), None)), Some(maka_host_js::factory(maka_js_runtime::plugin::Limits::default()))).await.unwrap();
         app.ready().await.unwrap();
         let mut efforts = Vec::new();
diff --git a/plugins/agent/native/src/owner/tests/scheduler.rs b/plugins/agent/native/src/owner/tests/scheduler.rs
index a1bf9257cb..adabe12323 100644
--- a/plugins/agent/native/src/owner/tests/scheduler.rs
+++ b/plugins/agent/native/src/owner/tests/scheduler.rs
@@ -58,7 +58,7 @@ async fn reopen(f: &mut Fixture) {
     let deployment = f.app.source().unwrap().deployment;
     f.app.shutdown().await.unwrap();
     let mut native = NativeRegistry::default();
-    maka_settings_native::provide(&mut native).unwrap();
+    maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
     crate::owner::tests::provide_all(&mut native).unwrap();
     f.app = Application::start_from_store(
         f.store.clone(),
diff --git a/plugins/agent/native/src/owner/tests/selection.rs b/plugins/agent/native/src/owner/tests/selection.rs
index 1b069f47cd..14ba6e17e0 100644
--- a/plugins/agent/native/src/owner/tests/selection.rs
+++ b/plugins/agent/native/src/owner/tests/selection.rs
@@ -198,8 +198,28 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
 {
     let fixture = Fixture::new("http://127.0.0.1:1/v1", None).await;
     let session = fixture.create("Original execution basis").await;
+    let public = ModelInfo {
+        context_window: Some(128_000),
+        max_output_tokens: Some(4096),
+        thinking_levels: Some(vec![ThinkingLevel::Low, ThinkingLevel::High]),
+        default_thinking_level: Some(ThinkingLevel::High),
+        capabilities: Some(ModelCapabilities {
+            vision: Some(true),
+            ..Default::default()
+        }),
+        ..ModelInfo::new("fixture-model")
+    };
+    *fixture.owner.catalog.snapshot.write().await = Ok(Arc::new(
+        maka_providers::catalog::Snapshot::for_model(
+            "openai-compatible",
+            "fixture-model",
+            Some(public.clone()),
+        )
+        .unwrap(),
+    ));
     let (mut source, _) = crate::model::Source::select(
         fixture.config.clone(),
+        fixture.owner.catalog.clone(),
         fixture.owner.credentials.clone(),
         fixture.selection().await,
         None,
@@ -210,6 +230,43 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
     .unwrap();
     let persisted = serde_json::to_string(&source.accepted).unwrap();
     assert!(!persisted.contains("private-fixture-key"));
+    let stored: Value = serde_json::from_str(&persisted).unwrap();
+    assert_eq!(stored["public_model"]["maxOutputTokens"], 4096);
+    let mut missing = stored.clone();
+    missing.as_object_mut().unwrap().remove("public_model");
+    assert!(
+        serde_json::from_value::<crate::model::AcceptedModel>(missing).is_err(),
+        "captured absence must be explicit null, never inferred from a missing field"
+    );
+    let refreshed_public = ModelInfo {
+        context_window: Some(1_000_000),
+        max_output_tokens: Some(8192),
+        default_thinking_level: Some(ThinkingLevel::Low),
+        capabilities: Some(ModelCapabilities {
+            vision: Some(false),
+            ..Default::default()
+        }),
+        ..public
+    };
+    *fixture.owner.catalog.snapshot.write().await = Ok(Arc::new(
+        maka_providers::catalog::Snapshot::for_model(
+            "openai-compatible",
+            "fixture-model",
+            Some(refreshed_public),
+        )
+        .unwrap(),
+    ));
+    for prepared in [
+        source.capture().await.unwrap(),
+        source.capture_auxiliary().await.unwrap(),
+    ] {
+        assert_eq!(prepared.main_output_limit, Some(4096));
+        assert_eq!(
+            prepared.context.unwrap().model_context_window,
+            Some(128_000)
+        );
+        assert!(prepared.supports_vision);
+    }
     source = crate::model::Source::restore(
         fixture.config.clone(),
         fixture.owner.credentials.clone(),
@@ -218,6 +275,39 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
         fixture.owner.revision.clone(),
     )
     .unwrap();
+    let recovered = source.capture().await.unwrap();
+    assert_eq!(recovered.main_output_limit, Some(4096));
+    assert_eq!(
+        recovered
+            .options
+            .as_object()
+            .unwrap()
+            .values()
+            .cloned()
+            .collect::<Vec<_>>(),
+        vec![json!({"reasoningEffort":"high"})],
+    );
+    assert!(recovered.supports_vision);
+    let choices = models(&fixture, json!({"kind":"start","query":"fixture-model"}))
+        .await
+        .unwrap();
+    assert_eq!(choices["choices"][0]["limits"]["contextWindow"], 1_000_000);
+    assert_eq!(choices["choices"][0]["limits"]["maxOutputTokens"], 8192);
+    assert_eq!(choices["choices"][0]["defaultThinkingLevel"], "low");
+    let (_, next) = crate::model::Source::select(
+        fixture.config.clone(),
+        fixture.owner.catalog.clone(),
+        fixture.owner.credentials.clone(),
+        fixture.selection().await,
+        None,
+        "new-public-defaults".into(),
+        fixture.owner.revision.clone(),
+    )
+    .await
+    .unwrap();
+    assert_eq!(next.main_output_limit, Some(8192));
+    assert_eq!(next.context.unwrap().model_context_window, Some(1_000_000));
+    assert!(!next.supports_vision);
     edit(&fixture, |changes| {
         changes.name = Some("A different label".into());
         changes
@@ -286,6 +376,7 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
     assert!(source.capture().await.is_err());
     let (current, _) = crate::model::Source::select(
         fixture.config.clone(),
+        fixture.owner.catalog.clone(),
         fixture.owner.credentials.clone(),
         fixture.selection().await,
         None,
@@ -301,6 +392,7 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
     assert!(current.capture().await.is_err());
     let (current, _) = crate::model::Source::select(
         fixture.config.clone(),
+        fixture.owner.catalog.clone(),
         fixture.owner.credentials.clone(),
         fixture.selection().await,
         None,
@@ -678,6 +770,7 @@ async fn restored_thinking_preserves_explicit_choice_and_refuses_changed_default
     for level in [None, Some(High)] {
         let (source, prepared) = crate::model::Source::select(
             fixture.config.clone(),
+            fixture.owner.catalog.clone(),
             fixture.owner.credentials.clone(),
             fixture.selection().await,
             level,
@@ -865,6 +958,7 @@ async fn accepted_code_mode_survives_settings_changes_and_legacy_messages_remain
     .await;
     let (source, prepared) = crate::model::Source::select(
         fixture.config.clone(),
+        fixture.owner.catalog.clone(),
         fixture.owner.credentials.clone(),
         fixture.selection().await,
         None,
@@ -883,6 +977,19 @@ async fn accepted_code_mode_survives_settings_changes_and_legacy_messages_remain
     assert_eq!(context.context_window, Some(900_000));
     assert_eq!(context.declared_window, Some(700_000));
     let stored = serde_json::to_value(&source.accepted).unwrap();
+    assert!(stored.as_object().unwrap().contains_key("public_model"));
+    assert!(stored["public_model"].is_null());
+    *fixture.owner.catalog.snapshot.write().await = Ok(Arc::new(
+        maka_providers::catalog::Snapshot::for_model(
+            "openai-compatible",
+            "fixture-model",
+            Some(ModelInfo {
+                max_output_tokens: Some(4096),
+                ..ModelInfo::new("fixture-model")
+            }),
+        )
+        .unwrap(),
+    ));
     edit(&fixture, |changes| {
         changes.model_overrides = Patch::Set(BTreeMap::from([(
             "fixture-model".into(),
@@ -912,6 +1019,7 @@ async fn accepted_code_mode_survives_settings_changes_and_legacy_messages_remain
     );
     let (next, prepared) = crate::model::Source::select(
         fixture.config.clone(),
+        fixture.owner.catalog.clone(),
         fixture.owner.credentials.clone(),
         fixture.selection().await,
         None,
diff --git a/plugins/agent/native/src/owner/tests/service_tools.rs b/plugins/agent/native/src/owner/tests/service_tools.rs
index 391f3b352e..2de72bba3f 100644
--- a/plugins/agent/native/src/owner/tests/service_tools.rs
+++ b/plugins/agent/native/src/owner/tests/service_tools.rs
@@ -2699,7 +2699,7 @@ async fn editing_policy_and_code_mode_survive_actual_host_restart_with_original_
         drop(raw);
         let mut native = NativeRegistry::default();
         provide_all(&mut native).unwrap();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         let restored = Application::start_from_store(
             fixture.store.clone(), deployment, native, fixture.issuer.clone(),
             Arc::new(Grant(fixture.issuer.clone(), Some(policy))), Some(maka_host_js::factory(Default::default())),
@@ -2757,7 +2757,7 @@ async fn queued_messages_resume_with_equivalent_native_service_targets_after_res
             drop(raw);
             let mut native = NativeRegistry::default();
             provide_all(&mut native).unwrap();
-            maka_settings_native::provide(&mut native).unwrap();
+            maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
             let restored = Application::start_from_store(
                 fixture.store.clone(), deployment, native, fixture.issuer.clone(),
                 Arc::new(Grant(fixture.issuer.clone(), Some(policy))), Some(maka_host_js::factory(Default::default())),
diff --git a/plugins/agent/native/src/owner/tests/service_tools/storm/combined.rs b/plugins/agent/native/src/owner/tests/service_tools/storm/combined.rs
index 1a6bfd60d5..c2a6d3ba0e 100644
--- a/plugins/agent/native/src/owner/tests/service_tools/storm/combined.rs
+++ b/plugins/agent/native/src/owner/tests/service_tools/storm/combined.rs
@@ -220,7 +220,7 @@ struct LocalOwner {
 
 fn providers(setup: &Setup, capture: Arc<StdMutex<Option<Arc<Owner>>>>) -> NativeRegistry {
     let mut native = NativeRegistry::default();
-    maka_settings_native::provide(&mut native).unwrap();
+    maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
     setup.provide(&mut native);
     native
         .insert(
@@ -228,7 +228,7 @@ fn providers(setup: &Setup, capture: Arc<StdMutex<Option<Arc<Owner>>>>) -> Nativ
             Arc::new(move |context, configuration| {
                 let capture = capture.clone();
                 Box::pin(async move {
-                    let owner = Owner::open(context, configuration).await.unwrap();
+                    let owner = Owner::open(context, configuration, crate::owner::tests::bundled_catalog()).await.unwrap();
                     *capture.lock().unwrap() = Some(owner.clone());
                     let service = Arc::new(Agent(owner));
                     NativeActivation {
diff --git a/plugins/agent/native/src/owner/tests/settings.rs b/plugins/agent/native/src/owner/tests/settings.rs
index 634da025f5..c9ee0590a1 100644
--- a/plugins/agent/native/src/owner/tests/settings.rs
+++ b/plugins/agent/native/src/owner/tests/settings.rs
@@ -764,7 +764,7 @@ async fn saved_model_survives_restart_and_resolves_locally_at_the_current_revisi
         assert_eq!(current["choice"]["limits"]["contextWindow"], 100000);
         assert_eq!(overrides["fixture-model"].max_output_tokens, Some(4000));
         let (_, prepared) = crate::model::Source::select(
-            f.config.clone(), f.owner.credentials.clone(), f.selection().await,
+            f.config.clone(), f.owner.catalog.clone(), f.owner.credentials.clone(), f.selection().await,
             None, session.clone(), f.owner.revision.clone(),
         ).await.unwrap();
         assert_eq!(prepared.main_output_limit, Some(4000));
diff --git a/plugins/agent/native/src/owner/tests/skill_preparation.rs b/plugins/agent/native/src/owner/tests/skill_preparation.rs
index 3c84e4c1f7..35b0dab17a 100644
--- a/plugins/agent/native/src/owner/tests/skill_preparation.rs
+++ b/plugins/agent/native/src/owner/tests/skill_preparation.rs
@@ -199,7 +199,7 @@ async fn skill_preparation_survives_host_restart_without_preparing_again() {
         document(dir.path(), "CHANGED METADATA", "CHANGED INSTRUCTIONS");
         let mut native = NativeRegistry::default();
         crate::owner::tests::provide_all(&mut native).unwrap();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         // Restore has no native prepare grant: it must use the original durable message.
         let resumed = Application::start_from_store(f.store.clone(), deployment, native,
             f.issuer.clone(), Arc::new(Grant(f.issuer.clone(), None)), Some(maka_host_js::factory(Default::default()))).await.unwrap();
diff --git a/plugins/agent/native/src/owner/tests/terminal.rs b/plugins/agent/native/src/owner/tests/terminal.rs
index 8f60477f05..3bc0c6ba03 100644
--- a/plugins/agent/native/src/owner/tests/terminal.rs
+++ b/plugins/agent/native/src/owner/tests/terminal.rs
@@ -717,7 +717,7 @@ async fn terminal_styled_display_survives_retirement_cancel_and_host_reopen_beyo
         f.store.shutdown().await.unwrap();
         f.store = Arc::new(EventLog::for_root(root).await.unwrap());
         let mut native = NativeRegistry::default();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         provide_all(&mut native).unwrap();
         let tooling = service_tools::Setup::processes();
         f.app = Application::start_from_store(f.store.clone(), deployment, native, f.issuer.clone(),
diff --git a/plugins/agent/native/src/owner/tests/todo.rs b/plugins/agent/native/src/owner/tests/todo.rs
index d7c493ce69..20bed3e8da 100644
--- a/plugins/agent/native/src/owner/tests/todo.rs
+++ b/plugins/agent/native/src/owner/tests/todo.rs
@@ -64,7 +64,7 @@ pub(super) async fn reopen(f: &mut Fixture) {
     let deployment = f.app.source().unwrap().deployment;
     f.app.shutdown().await.unwrap();
     let mut native = NativeRegistry::default();
-    maka_settings_native::provide(&mut native).unwrap();
+    maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
     provide_all(&mut native).unwrap();
     f.app = Application::start_from_store(
         f.store.clone(),
@@ -188,7 +188,7 @@ async fn todo_archived_result_next_is_readable_by_the_selected_native_read_tool(
         let deployment = f.app.source().unwrap().deployment;
         f.app.shutdown().await.unwrap();
         let mut native = NativeRegistry::default();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         provide_all(&mut native).unwrap();
         f.app = Application::start_from_store(
             f.store.clone(),
diff --git a/plugins/agent/native/src/owner/tests/workhub.rs b/plugins/agent/native/src/owner/tests/workhub.rs
index 5c04794503..7355bcf072 100644
--- a/plugins/agent/native/src/owner/tests/workhub.rs
+++ b/plugins/agent/native/src/owner/tests/workhub.rs
@@ -308,7 +308,7 @@ async fn activate_worker_mode(
                     let behavior = behavior.clone();
                     Box::pin(async move {
                         let owner = Arc::new(Worker(
-                            Agent(Owner::open(context, settings).await.unwrap()),
+                            Agent(Owner::open(context, settings, crate::owner::tests::bundled_catalog()).await.unwrap()),
                             behavior,
                         ));
                         NativeActivation {
@@ -326,7 +326,7 @@ async fn activate_worker_mode(
             )
             .unwrap();
     }
-    maka_settings_native::provide(&mut native).unwrap();
+    maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
     fixture.app = Application::start_from_store(
         fixture.store.clone(),
         source.deployment,
diff --git a/plugins/agent/native/src/owner/tests/workspaces.rs b/plugins/agent/native/src/owner/tests/workspaces.rs
index 6eaafe4032..a8571c42c5 100644
--- a/plugins/agent/native/src/owner/tests/workspaces.rs
+++ b/plugins/agent/native/src/owner/tests/workspaces.rs
@@ -42,7 +42,7 @@ pub(super) async fn publish_with_policy(
         .await
         .unwrap();
     let mut native = NativeRegistry::default();
-    maka_settings_native::provide(&mut native).unwrap();
+    maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
     crate::owner::tests::provide_all(&mut native).unwrap();
     fixture.app = Application::start_from_store(
         fixture.store.clone(),
diff --git a/plugins/agent/native/src/owner/tests/worktrees.rs b/plugins/agent/native/src/owner/tests/worktrees.rs
index 8fb1366743..a2ec815b47 100644
--- a/plugins/agent/native/src/owner/tests/worktrees.rs
+++ b/plugins/agent/native/src/owner/tests/worktrees.rs
@@ -141,7 +141,7 @@ async fn isolated_native_files_and_immutable_patch_survive_successor_and_restart
         let deployment=f.app.source().unwrap().deployment;
         f.app.shutdown().await.unwrap();
         let mut native=NativeRegistry::default();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         crate::owner::tests::provide_all(&mut native).unwrap();
         f.app=Application::start_from_store(f.store.clone(),deployment,native,f.issuer.clone(),Arc::new(Grant(f.issuer.clone(),None)),Some(maka_host_js::factory(Default::default()))).await.unwrap();
         f.app.ready().await.unwrap();
@@ -314,7 +314,7 @@ async fn large_completed_isolated_patch_is_complete_and_immutable_after_successo
         let deployment = f.app.source().unwrap().deployment;
         f.app.shutdown().await?;
         let mut native = NativeRegistry::default();
-        maka_settings_native::provide(&mut native).unwrap();
+        maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
         crate::owner::tests::provide_all(&mut native).unwrap();
         f.app = Application::start_from_store(
             f.store.clone(),
diff --git a/plugins/agent/native/src/workhub.rs b/plugins/agent/native/src/workhub.rs
index 41128aab03..3b7a8c9c80 100644
--- a/plugins/agent/native/src/workhub.rs
+++ b/plugins/agent/native/src/workhub.rs
@@ -66,10 +66,14 @@ fn namespace() -> Namespace {
     Namespace::new(IMPLEMENTATION, maka_plugin_core::scope::Scope::Profile)
         .expect("built-in WorkHub namespace")
 }
-pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
+pub fn provide(
+    registry: &mut NativeRegistry,
+    catalog: Arc<maka_providers::catalog::Catalog>,
+) -> Result<(), String> {
     registry.insert(
         maka_agent_api::workhub::IMPLEMENTATION.into(),
-        Arc::new(|context, settings| {
+        Arc::new(move |context, settings| {
+            let catalog = catalog.clone();
             Box::pin(async move {
                 let prepared = tasks::prepare(&context, settings);
                 let (settings, tasks, additional, link) = match prepared {
@@ -84,11 +88,11 @@ pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
                 match Owner::open_behavior(
                     context,
                     settings,
-                    IMPLEMENTATION,
                     Some(crate::owner::BuiltinBehavior::WorkHub),
                     additional,
                     tasks.bindings(),
                     Some(tasks.graphs.clone()),
+                    catalog,
                 )
                 .await
                 {
diff --git a/plugins/agent/native/src/workhub/presets.rs b/plugins/agent/native/src/workhub/presets.rs
index 7328592c43..098fbdcd46 100644
--- a/plugins/agent/native/src/workhub/presets.rs
+++ b/plugins/agent/native/src/workhub/presets.rs
@@ -64,6 +64,11 @@ impl Hub {
             .get(&preset.worker)
             .and_then(Option::as_ref)
             .ok_or("workhub_worker_unavailable")?;
+        let public_guard = self.agent.0.catalog.snapshot.read().await;
+        let snapshot = public_guard
+            .as_ref()
+            .map_err(|_| "workhub_configuration_unavailable")?
+            .clone();
         let connection = self
             .agent
             .0
@@ -77,7 +82,8 @@ impl Hub {
                 connection.connection_id == preset.model.connection_id && connection.enabled
             })
             .ok_or("workhub_preset_connection_unavailable")?;
-        if maka_providers::definition(&connection.provider).is_err() {
+        drop(public_guard);
+        if maka_providers::definition(&connection.provider, snapshot).is_err() {
             return Err("workhub_preset_connection_unavailable".into());
         }
         if !connection
diff --git a/plugins/agent/providers/src/api.rs b/plugins/agent/providers/src/api.rs
index 25b44e041d..9448bb898d 100644
--- a/plugins/agent/providers/src/api.rs
+++ b/plugins/agent/providers/src/api.rs
@@ -57,8 +57,11 @@ pub struct ApiProviders;
 impl ApiProviders {
     /// A compiled API implementation for a native application owner. Availability
     /// does not activate legacy contributions or grant execution authority.
-    pub fn definition(id: &str) -> Result<Definition, String> {
-        Self::provider(id)?.definition()
+    pub fn definition(
+        id: &str,
+        snapshot: Arc<crate::catalog::Snapshot>,
+    ) -> Result<Definition, String> {
+        Self::provider(id, snapshot)?.definition()
     }
 
     /// Selectable model metadata, checked by the same preparation policy as
@@ -66,11 +69,12 @@ impl ApiProviders {
     /// configured default is an error, never silently replaced by another level.
     pub fn model_metadata(
         id: &str,
+        snapshot: Arc<crate::catalog::Snapshot>,
         connection: Connection,
         model: ModelInfo,
         overrides: Option<ModelOverride>,
     ) -> Result<ModelMetadata, String> {
-        let provider = Self::provider(id)?;
+        let provider = Self::provider(id, snapshot)?;
         let mut metadata = provider.model_metadata(model.clone(), overrides.as_ref());
         let mut request = Resolve {
             connection,
@@ -88,7 +92,7 @@ impl ApiProviders {
         Ok(metadata)
     }
 
-    fn provider(id: &str) -> Result<ApiProvider, String> {
+    fn provider(id: &str, snapshot: Arc<crate::catalog::Snapshot>) -> Result<ApiProvider, String> {
         let (id, facts) = facts::all()
             .map_err(|error| error.to_string())?
             .iter()
@@ -99,6 +103,7 @@ impl ApiProviders {
         Ok(ApiProvider {
             id: id.as_str(),
             facts,
+            snapshot,
         })
     }
 
@@ -117,6 +122,7 @@ impl ApiProviders {
 struct ApiProvider {
     id: &'static str,
     facts: &'static ProviderFacts,
+    snapshot: Arc<crate::catalog::Snapshot>,
 }
 
 #[derive(Deserialize, Serialize, schemars::JsonSchema)]
@@ -179,7 +185,7 @@ impl ApiProvider {
     }
 
     fn model_metadata(&self, model: ModelInfo, overrides: Option<&ModelOverride>) -> ModelMetadata {
-        let reported = metadata::enrich(self.id, self.facts, model);
+        let reported = metadata::enrich(&self.snapshot, self.id, model);
         let info =
             overrides.map_or_else(|| reported.clone(), |overrides| overrides.apply(&reported));
         let thinking_levels = overrides
@@ -216,7 +222,7 @@ impl ApiProvider {
         if level.is_some_and(|level| !thinking_levels.contains(&level)) {
             return Err(Error::UnsupportedThinkingLevel);
         }
-        let options = options::resolve(&selection, self.facts, level, &route)?;
+        let options = options::resolve(&selection, self.facts, &thinking_levels, level, &route)?;
         let output = output::resolve(&selection, route.wire, &options)?;
         let adapter = request
             .overrides
diff --git a/plugins/agent/providers/src/api/effects.rs b/plugins/agent/providers/src/api/effects.rs
index 8c3a53a2b8..c22dabb131 100644
--- a/plugins/agent/providers/src/api/effects.rs
+++ b/plugins/agent/providers/src/api/effects.rs
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-use super::{ApiProvider, Configuration, Selection, metadata, route};
+use super::{ApiProvider, Configuration, Selection, route};
 use maka_agent_facts::configuration::ModelInfo;
 use maka_agent_ports::model::Credentials;
 use maka_agent_ports::model::ProviderKind;
@@ -34,11 +34,7 @@ pub(super) async fn discover(
     request: Discovery,
     context: Context,
 ) -> Result<Vec<ModelInfo>, Error> {
-    let inventory = super::discovery::discover(provider, request, context).await?;
-    Ok(inventory
-        .into_iter()
-        .map(|model| metadata::enrich(provider.id, provider.facts, model))
-        .collect())
+    super::discovery::discover(provider, request, context).await
 }
 
 pub(super) async fn verify(
diff --git a/plugins/agent/providers/src/api/metadata.rs b/plugins/agent/providers/src/api/metadata.rs
index 46887bb745..f45b8af4c5 100644
--- a/plugins/agent/providers/src/api/metadata.rs
+++ b/plugins/agent/providers/src/api/metadata.rs
@@ -17,99 +17,11 @@
  * under the License.
  */
 
-use crate::facts::ProviderFacts;
-use maka_agent_facts::configuration::ModelCapabilities;
+use crate::catalog::Snapshot;
 use maka_agent_facts::configuration::ModelInfo;
-use maka_agent_facts::execution::ThinkingLevel;
 
-/// Account facts win field by field; explicit false and empty choices are facts.
-pub(super) fn enrich(provider: &str, facts: &ProviderFacts, mut model: ModelInfo) -> ModelInfo {
-    if let Some(known) = facts.models.get(&model.id) {
-        let metadata = &known.metadata;
-        macro_rules! fallback {
-            ($($field:ident),* $(,)?) => { $(if model.$field.is_none() {
-                model.$field = metadata.$field.clone();
-            })* };
-        }
-        fallback!(
-            display_name,
-            description,
-            context_window,
-            input_limit,
-            max_output_tokens,
-            knowledge_cutoff,
-            structured_output,
-            last_updated,
-            modalities
-        );
-        if model.thinking_levels.is_none() {
-            model.thinking_levels = Some(known.entry.thinking_levels.clone());
-            if provider == "openai"
-                && known
-                    .metadata
-                    .thinking_options
-                    .as_ref()
-                    .and_then(|options| options.efforts.as_ref())
-                    .is_some_and(|efforts| efforts.iter().any(|effort| effort == "medium"))
-            {
-                model.default_thinking_level =
-                    model.default_thinking_level.or(Some(ThinkingLevel::Medium));
-                model.supports_reasoning_summary = model.supports_reasoning_summary.or(Some(true));
-            }
-        }
-        let mut defaults = metadata.capabilities.unwrap_or_default();
-        defaults.chat = defaults.chat.or(Some(known.entry.can_use_as_chat_default));
-        defaults.vision = defaults.vision.or(Some(known.entry.supports_vision));
-        let caps = model.capabilities.get_or_insert_default();
-        fill(caps, defaults);
-    }
-    if provider == "openai" || provider == "anthropic" {
-        let caps = model.capabilities.get_or_insert_default();
-        caps.web_search = caps.web_search.or(Some(true));
-    }
-    if provider == "anthropic" && claude_vision(&model.id) {
-        let caps = model.capabilities.get_or_insert_default();
-        caps.vision = caps.vision.or(Some(true));
-    }
-    model
-}
-
-fn claude_vision(id: &str) -> bool {
-    let Some(mut tail) = id.strip_prefix("claude-") else {
-        return false;
-    };
-    loop {
-        for family in ["opus", "sonnet", "haiku", "fable"] {
-            if let Some(rest) = tail.strip_prefix(family)
-                && rest
-                    .chars()
-                    .next()
-                    .is_none_or(|c| !c.is_ascii_alphanumeric() && c != '_')
-            {
-                return true;
-            }
-        }
-        let Some((part, rest)) = tail.split_once('-') else {
-            return false;
-        };
-        if part.is_empty() || !part.chars().all(|c| c.is_ascii_digit() || c == '.') {
-            return false;
-        }
-        tail = rest;
-    }
-}
-
-fn fill(caps: &mut ModelCapabilities, defaults: ModelCapabilities) {
-    macro_rules! fallback {
-        ($($field:ident),* $(,)?) => { $(caps.$field = caps.$field.or(defaults.$field);)* };
-    }
-    fallback!(
-        chat,
-        vision,
-        reasoning,
-        function_calling,
-        parallel_tool_calls,
-        image_generation,
-        web_search
-    );
+/// The same immutable public base fills account omissions for selection and
+/// execution. Explicit account false/empty values remain authoritative.
+pub(super) fn enrich(snapshot: &Snapshot, provider: &str, model: ModelInfo) -> ModelInfo {
+    snapshot.resolve(provider, model)
 }
diff --git a/plugins/agent/providers/src/api/options.rs b/plugins/agent/providers/src/api/options.rs
index c45e68f7c5..230d921088 100644
--- a/plugins/agent/providers/src/api/options.rs
+++ b/plugins/agent/providers/src/api/options.rs
@@ -34,37 +34,29 @@ mod relay;
 pub(super) fn resolve(
     row: &Selection<'_>,
     facts: &ProviderFacts,
+    thinking_levels: &[ThinkingLevel],
     thinking_level: Option<ThinkingLevel>,
     route: &Route,
 ) -> Result<Value, Error> {
-    let model = row.model.id.as_str();
     let wire = route.wire;
-    let known = facts.models.get(model);
-    let stored = Some(row.model);
     let parallel = row
-        .overrides
-        .and_then(|value| value.capabilities.as_ref())
-        .and_then(|value| value.parallel_tool_calls)
-        .or_else(|| {
-            stored
-                .and_then(|item| item.capabilities)
-                .and_then(|caps| caps.parallel_tool_calls)
-        })
-        .or_else(|| {
-            known
-                .and_then(|item| item.metadata.capabilities)
-                .and_then(|caps| caps.parallel_tool_calls)
-        });
+        .model
+        .capabilities
+        .and_then(|caps| caps.parallel_tool_calls);
     match &route.kind {
-        ProviderKind::Anthropic => Ok(anthropic(facts, model, thinking_level, parallel)),
+        ProviderKind::Anthropic => Ok(anthropic(
+            facts,
+            row.model,
+            thinking_levels,
+            thinking_level,
+            parallel,
+        )),
         ProviderKind::OpenaiResponses if row.provider == "openai-responses-compatible" => {
             relay::responses(row, thinking_level, parallel.unwrap_or(true))
         }
         ProviderKind::OpenaiChat | ProviderKind::OpenaiResponses => {
             // An explicit account capability can opt out; it never gates rendering.
-            let summary = stored
-                .and_then(|model| model.supports_reasoning_summary)
-                .unwrap_or(false);
+            let summary = row.model.supports_reasoning_summary.unwrap_or(false);
             Ok(openai(
                 wire,
                 thinking_level,
@@ -73,7 +65,7 @@ pub(super) fn resolve(
             ))
         }
         ProviderKind::OpenaiCompatible { name } => {
-            compatible::chat(row.provider, name, model, thinking_level, parallel)
+            Ok(compatible::chat(name, thinking_level, parallel))
         }
         ProviderKind::OpenResponses(contract) => {
             if parallel == Some(false) {
@@ -98,17 +90,6 @@ pub(super) fn resolve(
     }
 }
 
-fn default_medium(facts: &ProviderFacts, model: &str) -> bool {
-    let family = model.rsplit('/').next().unwrap_or(model);
-    facts.models.get(family).is_some_and(|item| {
-        item.metadata
-            .thinking_options
-            .as_ref()
-            .and_then(|thinking| thinking.efforts.as_ref())
-            .is_some_and(|efforts| efforts.iter().any(|effort| effort == "medium"))
-    })
-}
-
 fn openai(wire: Wire, level: Option<ThinkingLevel>, summary: bool, parallel: bool) -> Value {
     let mut options = json!({"store": false, "parallelToolCalls": parallel});
     if let Some(level) = level {
@@ -125,42 +106,35 @@ fn openai(wire: Wire, level: Option<ThinkingLevel>, summary: bool, parallel: boo
 
 fn anthropic(
     facts: &ProviderFacts,
-    model: &str,
+    model: &maka_agent_facts::configuration::ModelInfo,
+    levels: &[ThinkingLevel],
     level: Option<ThinkingLevel>,
     parallel: Option<bool>,
 ) -> Value {
-    let family = claude_family(model);
-    let known = facts.models.get(model);
+    let family = claude_family(&model.id);
+    let known = facts.models.get(&model.id);
     let family_facts = facts.models.get(family.as_ref());
     let metadata = known.map(|item| &item.metadata);
     let family_metadata = family_facts.map(|item| &item.metadata);
-    let thinking = metadata
+    // The SDK's off encoding and adaptive wire support remain compiled policy.
+    let off_behavior = metadata
         .and_then(|metadata| metadata.thinking_options.as_ref())
-        .or_else(|| family_metadata.and_then(|metadata| metadata.thinking_options.as_ref()));
+        .and_then(|thinking| thinking.off_behavior)
+        .or_else(|| {
+            family_metadata
+                .and_then(|metadata| metadata.thinking_options.as_ref())
+                .and_then(|thinking| thinking.off_behavior)
+        });
     let bare_legacy = matches!(family.as_ref(), "claude-opus-4" | "claude-sonnet-4");
     let visible = family.starts_with("claude-")
-        && (thinking.is_some_and(|thinking| {
-            thinking.toggle == Some(true)
-                || thinking
-                    .efforts
-                    .as_ref()
-                    .is_some_and(|efforts| !efforts.is_empty())
-        }) || metadata
-            .and_then(|metadata| metadata.capabilities)
-            .and_then(|caps| caps.reasoning)
-            == Some(true)
-            || family_metadata
-                .and_then(|metadata| metadata.capabilities)
-                .and_then(|caps| caps.reasoning)
-                == Some(true)
-            || bare_legacy);
+        && (model.capabilities.and_then(|caps| caps.reasoning) == Some(true)
+            || levels.iter().any(|level| *level != ThinkingLevel::Off));
     let mut options = json!({"cacheControl":{"type":"ephemeral"}});
     if parallel == Some(false) {
         options["disableParallelToolUse"] = json!(true);
     }
     if level == Some(ThinkingLevel::Off)
-        && thinking.and_then(|thinking| thinking.off_behavior)
-            == Some(ThinkingOffBehavior::AnthropicThinkingDisabled)
+        && off_behavior == Some(ThinkingOffBehavior::AnthropicThinkingDisabled)
     {
         options["thinking"] = json!({"type":"disabled"});
     } else if visible {
diff --git a/plugins/agent/providers/src/api/options/compatible.rs b/plugins/agent/providers/src/api/options/compatible.rs
index 0ce1f439ae..bc23d576b5 100644
--- a/plugins/agent/providers/src/api/options/compatible.rs
+++ b/plugins/agent/providers/src/api/options/compatible.rs
@@ -17,24 +17,9 @@
  * under the License.
  */
 
-use super::{Error, ThinkingLevel, Value, json};
-use crate::facts::provider_facts;
+use super::{ThinkingLevel, Value, json};
 
-pub(super) fn chat(
-    provider: &str,
-    name: &str,
-    model: &str,
-    level: Option<ThinkingLevel>,
-    parallel: Option<bool>,
-) -> Result<Value, Error> {
-    let level = if provider == "openrouter" {
-        // Its own catalog controls the default; a similarly named OpenAI model
-        // does not establish the routed model's enabled state or effort.
-        level
-    } else {
-        let facts = provider_facts("openai").map_err(|_| Error::ProviderFactsUnavailable)?;
-        level.or_else(|| super::default_medium(facts, model).then_some(ThinkingLevel::Medium))
-    };
+pub(super) fn chat(name: &str, level: Option<ThinkingLevel>, parallel: Option<bool>) -> Value {
     let mut options = json!({});
     if let Some(level) = level {
         options["reasoningEffort"] = match level {
@@ -46,11 +31,11 @@ pub(super) fn chat(
     if let Some(parallel) = parallel {
         options["parallel_tool_calls"] = json!(parallel);
     }
-    Ok(if options.as_object().unwrap().is_empty() {
+    if options.as_object().unwrap().is_empty() {
         json!({})
     } else {
         json!({camel_case(name): options})
-    })
+    }
 }
 
 // Match model-factory.ts and the SDK alias: /[_-]([a-z])/g.
@@ -72,39 +57,20 @@ mod tests {
     use super::*;
 
     #[test]
-    fn compatible_policy_preserves_explicit_effort_and_only_defaults_known_families() {
+    fn compatible_policy_encodes_only_resolved_effort_and_declared_parallel_support() {
         for (level, wire) in [(ThinkingLevel::High, "high"), (ThinkingLevel::Max, "max")] {
             assert_eq!(
-                chat(
-                    "openai-compatible",
-                    "custom-chat_relay",
-                    "unknown",
-                    Some(level),
-                    None
-                )
-                .unwrap(),
+                chat("custom-chat_relay", Some(level), None),
                 json!({"customChatRelay":{"reasoningEffort":wire}}),
             );
         }
+        assert_eq!(chat("relay", None, None), json!({}));
         assert_eq!(
-            chat("openai-compatible", "relay", "unknown", None, None).unwrap(),
-            json!({})
+            chat("custom-chat", None, Some(false)),
+            json!({"customChat":{"parallel_tool_calls":false}}),
         );
-        for model in ["gpt-5.2", "relay/gpt-5.2"] {
-            assert_eq!(
-                chat("openai-compatible", "custom-chat", model, None, Some(false)).unwrap(),
-                json!({"customChat":{"reasoningEffort":"medium","parallel_tool_calls":false}}),
-            );
-        }
         assert_eq!(
-            chat(
-                "openai-compatible",
-                "relay",
-                "gpt-5.2",
-                Some(ThinkingLevel::Off),
-                Some(true)
-            )
-            .unwrap(),
+            chat("relay", Some(ThinkingLevel::Off), Some(true)),
             json!({"relay":{"reasoningEffort":"none","parallel_tool_calls":true}}),
         );
         assert_eq!(camel_case("relay--chat_A-2_b"), "relay-Chat_A-2B");
diff --git a/plugins/agent/providers/src/api/options/relay.rs b/plugins/agent/providers/src/api/options/relay.rs
index a0e896cbd2..c1a7764957 100644
--- a/plugins/agent/providers/src/api/options/relay.rs
+++ b/plugins/agent/providers/src/api/options/relay.rs
@@ -18,7 +18,6 @@
  */
 
 use super::{Error, Selection, ThinkingLevel, Value, Wire};
-use crate::facts::provider_facts;
 
 pub(super) fn responses(
     row: &Selection<'_>,
@@ -26,13 +25,10 @@ pub(super) fn responses(
     parallel: bool,
 ) -> Result<Value, Error> {
     let model = row.model.id.as_str();
-    // Relay catalogs may have no models. Defaults and summaries follow canonical
-    // OpenAI family facts, never the relay's declared thinking levels.
-    let facts = provider_facts("openai").map_err(|_| Error::ProviderFactsUnavailable)?;
     let mut options = super::openai(
         Wire::OpenaiResponses,
-        level.or_else(|| super::default_medium(facts, model).then_some(ThinkingLevel::Medium)),
-        super::default_medium(facts, model),
+        level,
+        row.model.supports_reasoning_summary.unwrap_or(false),
         parallel,
     );
     options["openai"]["forceReasoning"] = Value::Bool(true);
diff --git a/plugins/agent/providers/src/lib.rs b/plugins/agent/providers/src/lib.rs
index 0126c052ca..dfbcd9754d 100644
--- a/plugins/agent/providers/src/lib.rs
+++ b/plugins/agent/providers/src/lib.rs
@@ -27,13 +27,14 @@ mod facts;
 /// code or change the provider scope accepted by a native application.
 pub fn definition(
     identity: &maka_agent_facts::provider::Identity,
+    snapshot: std::sync::Arc<catalog::Snapshot>,
 ) -> Result<maka_agent_ports::provider::Definition, String> {
     use maka_plugin_core::scope::Scope;
     if identity.scope != Scope::Profile || identity.package_id != identity.entry_id {
         return Err("provider is unavailable".into());
     }
     match identity.package_id.as_str() {
-        api::ID => api::ApiProviders::definition(&identity.name),
+        api::ID => api::ApiProviders::definition(&identity.name, snapshot),
         codex::ID if identity.name == "chatgpt" => codex::Codex::default()
             .definition()
             .map_err(|e| e.to_string()),
diff --git a/plugins/agent/providers/tests/api.rs b/plugins/agent/providers/tests/api.rs
index 27c8f06cd7..c4013d6eb6 100644
--- a/plugins/agent/providers/tests/api.rs
+++ b/plugins/agent/providers/tests/api.rs
@@ -40,8 +40,12 @@ mod effects;
 #[path = "api/policy.rs"]
 mod policy;
 
+fn snapshot() -> std::sync::Arc<maka_providers::catalog::Snapshot> {
+    std::sync::Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap())
+}
+
 fn binding(name: &str) -> Definition {
-    ApiProviders::definition(name).unwrap()
+    ApiProviders::definition(name, snapshot()).unwrap()
 }
 fn request(binding: &Definition, id: &str, overrides: ModelOverride) -> Resolve {
     let configuration = binding
@@ -212,10 +216,9 @@ async fn account_metadata_and_reply_capacity_survive_plugin_policy_resolution()
     let input = request(&openai, "gpt-5.2", ModelOverride::default());
     let model = openai.prepare(input.clone()).await.unwrap();
     assert_eq!(
-        model.provider_options["openai"]["reasoningEffort"],
-        "medium"
+        model.provider_options,
+        json!({"openai":{"store":false,"parallelToolCalls":true}})
     );
-    assert_eq!(model.provider_options["openai"]["reasoningSummary"], "auto");
     let mut account = input;
     account.model.thinking_levels = Some(vec![High]);
     let model = openai.prepare(account).await.unwrap();
@@ -272,6 +275,7 @@ async fn catalog_metadata_and_execution_share_provider_facts_and_explicit_overri
         let mut input = request(&binding, "gpt-5.2", overrides.clone());
         let metadata = ApiProviders::model_metadata(
             "openai",
+            snapshot(),
             input.connection.clone(),
             reported.clone(),
             Some(overrides),
@@ -300,6 +304,7 @@ async fn selectable_thinking_choices_respect_native_encoder_and_invalid_default(
     );
     let metadata = ApiProviders::model_metadata(
         "anthropic-compatible",
+        snapshot(),
         input.connection.clone(),
         input.model.clone(),
         input.overrides.clone(),
@@ -327,6 +332,7 @@ async fn selectable_thinking_choices_respect_native_encoder_and_invalid_default(
     input.overrides.as_mut().unwrap().default_thinking_level = Some(High);
     let metadata = ApiProviders::model_metadata(
         "anthropic-compatible",
+        snapshot(),
         input.connection.clone(),
         input.model.clone(),
         input.overrides.clone(),
@@ -347,6 +353,7 @@ async fn selectable_thinking_choices_respect_native_encoder_and_invalid_default(
     assert!(
         ApiProviders::model_metadata(
             "anthropic-compatible",
+            snapshot(),
             input.connection.clone(),
             input.model.clone(),
             input.overrides.clone(),
@@ -357,6 +364,7 @@ async fn selectable_thinking_choices_respect_native_encoder_and_invalid_default(
     assert!(
         ApiProviders::model_metadata(
             "openai-compatible",
+            snapshot(),
             input.connection,
             input.model,
             input.overrides
diff --git a/plugins/agent/providers/tests/api/discovery.rs b/plugins/agent/providers/tests/api/discovery.rs
index 4b93f19035..a06fa02a90 100644
--- a/plugins/agent/providers/tests/api/discovery.rs
+++ b/plugins/agent/providers/tests/api/discovery.rs
@@ -183,6 +183,7 @@ async fn openrouter_discovery_preserves_reported_efforts_defaults_and_explicit_p
         resolve.model = model.clone();
         let metadata = ApiProviders::model_metadata(
             "openrouter",
+            snapshot(),
             resolve.connection.clone(),
             resolve.model.clone(),
             resolve.overrides.clone(),
diff --git a/plugins/agent/providers/tests/api/effects.rs b/plugins/agent/providers/tests/api/effects.rs
index 9e013896f3..a211598eb8 100644
--- a/plugins/agent/providers/tests/api/effects.rs
+++ b/plugins/agent/providers/tests/api/effects.rs
@@ -210,16 +210,19 @@ async fn discovery_survives_persistence_and_verification_does_not_require_stream
         let persisted: Vec<ModelInfo> =
             serde_json::from_slice(&serde_json::to_vec(&inventory).unwrap()).unwrap();
         input.model = persisted.into_iter().find(|model| model.id == id).unwrap();
+        assert_eq!(
+            serde_json::to_value(&input.model).unwrap(),
+            json!({"id":id})
+        );
         let model = binding.prepare(input.clone()).await.unwrap();
         if name == "openai" {
             assert_eq!(
-                model.provider_options["openai"]["reasoningEffort"],
-                "medium"
+                model.provider_options,
+                json!({"openai":{"store":false,"parallelToolCalls":true}})
             );
-            assert_eq!(model.provider_options["openai"]["reasoningSummary"], "auto");
         } else {
-            assert_eq!(model.info.capabilities.unwrap().vision, Some(true));
-            input.model.capabilities.as_mut().unwrap().vision = Some(false);
+            assert_eq!(model.info.capabilities, None);
+            input.model.capabilities.get_or_insert_default().vision = Some(false);
             assert_eq!(
                 binding
                     .prepare(input.clone())
@@ -312,7 +315,7 @@ async fn discovery_survives_persistence_and_verification_does_not_require_stream
 #[tokio::test]
 async fn compiled_definition_authenticates_without_legacy_registration_or_secret_diagnostics() {
     use maka_agent_ports::provider::authentication::Authenticate;
-    let definition = ApiProviders::definition("openai-compatible").unwrap();
+    let definition = ApiProviders::definition("openai-compatible", snapshot()).unwrap();
     let transport = Arc::new(Exchange::default());
     let context = Context {
         transport: transport.clone(),
diff --git a/plugins/agent/providers/tests/api/policy.rs b/plugins/agent/providers/tests/api/policy.rs
index 924bf4cda9..5c5a543bf1 100644
--- a/plugins/agent/providers/tests/api/policy.rs
+++ b/plugins/agent/providers/tests/api/policy.rs
@@ -68,6 +68,7 @@ async fn kimi_catalog_choices_and_supported_default_remain_selectable() {
         );
         let metadata = ApiProviders::model_metadata(
             "kimi-coding-plan",
+            snapshot(),
             input.connection.clone(),
             input.model.clone(),
             input.overrides.clone(),
diff --git a/plugins/settings/native/src/connections.rs b/plugins/settings/native/src/connections.rs
index 5bc3ee34d0..e7a4b0641b 100644
--- a/plugins/settings/native/src/connections.rs
+++ b/plugins/settings/native/src/connections.rs
@@ -23,18 +23,28 @@ use maka_agent_facts::provider::Identity;
 use maka_agent_ports::provider::Definition;
 use maka_config::ConfigurationStore;
 use maka_plugin_core::scope::Scope;
+use maka_providers::catalog::Snapshot;
 use serde::Serialize;
 use serde_json::Value;
+use std::sync::Arc;
 
 pub(crate) fn encode(value: impl Serialize) -> Result<Value, String> {
     let value = serde_json::to_value(value).map_err(|_| "settings_output_invalid")?;
     Ok(value)
 }
-pub(crate) fn definition(identity: &Identity) -> Result<Definition, String> {
-    maka_providers::definition(identity).map_err(|_| "settings_provider_unavailable".into())
+pub(crate) fn definition(
+    identity: &Identity,
+    snapshot: Arc<Snapshot>,
+) -> Result<Definition, String> {
+    maka_providers::definition(identity, snapshot)
+        .map_err(|_| "settings_provider_unavailable".into())
 }
 
-pub(crate) async fn page(store: &ConfigurationStore, input: Value) -> Result<Value, String> {
+pub(crate) async fn page(
+    store: &ConfigurationStore,
+    input: Value,
+    snapshot: Arc<Snapshot>,
+) -> Result<Value, String> {
     let input: ConnectionsRead =
         serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
     let catalog = store
@@ -59,7 +69,7 @@ pub(crate) async fn page(store: &ConfigurationStore, input: Value) -> Result<Val
     let connections = catalog.connections[offset..end]
         .iter()
         .map(|row| ConnectionSummary {
-            editable: definition(&row.provider).is_ok(),
+            editable: definition(&row.provider, snapshot.clone()).is_ok(),
             basis: ConnectionVersionBasis {
                 connection_id: row.connection_id.clone(),
                 revision: row.revision,
@@ -117,7 +127,7 @@ pub(crate) async fn detail(store: &ConfigurationStore, input: Value) -> Result<V
         }),
     }
 }
-pub(crate) fn providers(input: Value) -> Result<Value, String> {
+pub(crate) fn providers(input: Value, snapshot: Arc<Snapshot>) -> Result<Value, String> {
     let input: ProvidersRead =
         serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
     let mut identities = vec![Identity {
@@ -151,7 +161,7 @@ pub(crate) fn providers(input: Value) -> Result<Value, String> {
     .map_err(|_| "settings_output_invalid")?
     .len();
     for (offset, identity) in identities.into_iter().enumerate().skip(input.offset) {
-        let provider = definition(&identity)?;
+        let provider = definition(&identity, snapshot.clone())?;
         let descriptor = provider.descriptor();
         let summary = ProviderSummary {
             identity,
@@ -180,9 +190,9 @@ pub(crate) fn providers(input: Value) -> Result<Value, String> {
     encode(ProvidersPage { providers, next })
 }
 
-pub(crate) fn provider(input: Value) -> Result<Value, String> {
+pub(crate) fn provider(input: Value, snapshot: Arc<Snapshot>) -> Result<Value, String> {
     let identity = serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
-    let descriptor = definition(&identity)?.descriptor().clone();
+    let descriptor = definition(&identity, snapshot)?.descriptor().clone();
     encode(Provider {
         identity,
         descriptor,
diff --git a/plugins/settings/native/src/credential.rs b/plugins/settings/native/src/credential.rs
index e42c5b60c8..cd82ee1b2a 100644
--- a/plugins/settings/native/src/credential.rs
+++ b/plugins/settings/native/src/credential.rs
@@ -72,6 +72,7 @@ fn rejected(reason: CredentialRejection) -> Result<Value, String> {
 }
 pub(crate) async fn save(
     store: &Arc<ConfigurationStore>,
+    snapshot: Arc<maka_providers::catalog::Snapshot>,
     input: Value,
     context: Context,
 ) -> Result<Value, String> {
@@ -79,7 +80,7 @@ pub(crate) async fn save(
     let Ok(request) = serde_json::from_value::<SaveCredential>(input) else {
         return rejected(CredentialRejection::InvalidInput);
     };
-    let Ok(definition) = connections::definition(&request.expected.provider) else {
+    let Ok(definition) = connections::definition(&request.expected.provider, snapshot) else {
         return rejected(CredentialRejection::ProviderUnavailable);
     };
     if !definition
@@ -108,6 +109,7 @@ pub(crate) async fn save(
     }
     enroll(
         store,
+        definition,
         request,
         AuthenticationInput {
             method: "api-key".into(),
@@ -144,6 +146,7 @@ impl provider::authentication::Interaction for LoginInteraction {
 
 pub(crate) async fn login(
     store: &Arc<ConfigurationStore>,
+    snapshot: Arc<maka_providers::catalog::Snapshot>,
     input: Value,
     context: Context,
 ) -> Result<Value, String> {
@@ -151,7 +154,7 @@ pub(crate) async fn login(
     let Ok(input) = serde_json::from_value::<LoginCredential>(input) else {
         return rejected(CredentialRejection::InvalidInput);
     };
-    let Ok(definition) = connections::definition(&input.expected.provider) else {
+    let Ok(definition) = connections::definition(&input.expected.provider, snapshot) else {
         return rejected(CredentialRejection::ProviderUnavailable);
     };
     if !definition
@@ -164,6 +167,7 @@ pub(crate) async fn login(
     }
     enroll(
         store,
+        definition,
         SaveCredential {
             expected: input.expected,
             expected_credential: input.expected_credential,
@@ -180,13 +184,13 @@ pub(crate) async fn login(
 
 async fn enroll(
     store: &Arc<ConfigurationStore>,
+    definition: provider::Definition,
     request: SaveCredential,
     authentication: AuthenticationInput,
     context: Context,
     interactive: bool,
 ) -> Result<Value, String> {
     let operation = context.operation().ok_or("settings_mutation_required")?;
-    let definition = connections::definition(&request.expected.provider)?;
     let attempt_id = format!(
         "settings-{}",
         hex::encode(Sha256::digest(
diff --git a/plugins/settings/native/src/credential/tests.rs b/plugins/settings/native/src/credential/tests.rs
index a7537e688e..c8be43959a 100644
--- a/plugins/settings/native/src/credential/tests.rs
+++ b/plugins/settings/native/src/credential/tests.rs
@@ -110,7 +110,11 @@ async fn chatgpt_login_saves_privately_and_rejects_cancelled_or_changed_connecti
             Arc::new(RootOwner::create(&directory.path().join("root"), &namespaces).unwrap());
         let store = Arc::new(ConfigurationStore::for_root(root).await.unwrap());
         let identity: provider::Identity = serde_json::from_value(json!({"packageId":"maka.codex","entryId":"maka.codex","scope":"profile","name":"chatgpt"})).unwrap();
-        let definition = connections::definition(&identity).unwrap();
+        let definition = connections::definition(
+            &identity,
+            Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap()),
+        )
+        .unwrap();
         let revision = store.catalog().await.unwrap().revision;
         store.create_connection(serde_json::from_value(json!({"expectedCatalogRevision":revision,"connection":{"slug":"chatgpt","name":"ChatGPT","provider":identity,"configuration":definition.descriptor().configuration_defaults,"enabled":true,"enabledModelIds":[]}})).unwrap()).await.unwrap();
         let row = store.catalog().await.unwrap().connections.remove(0);
@@ -207,7 +211,11 @@ async fn chatgpt_login_saves_privately_and_rejects_cancelled_or_changed_connecti
                 }
                 let discovered = crate::setup::discover_prepared(
                     ticket,
-                    connections::definition(&identity).unwrap(),
+                    connections::definition(
+                        &identity,
+                        Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap()),
+                    )
+                    .unwrap(),
                     provider::Context {
                         transport: Arc::new(Script {
                             fail: discovery == "network",
@@ -290,7 +298,11 @@ fn compiled_catalog_projects_search_metadata_and_resolves_selected_provider() {
     let mut offset = 0;
     let mut found = false;
     loop {
-        let page = connections::providers(json!({"offset":offset})).unwrap();
+        let page = connections::providers(
+            json!({"offset":offset}),
+            Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap()),
+        )
+        .unwrap();
         let rows = page["providers"].as_array().unwrap();
         assert!(page.to_string().len() <= 128 * 1024 || rows.len() == 1);
         for row in rows {
@@ -299,7 +311,11 @@ fn compiled_catalog_projects_search_metadata_and_resolves_selected_provider() {
                 assert_eq!(row["label"], "ChatGPT Subscription");
                 assert_eq!(row["authentication"][0]["interactive"], true);
                 assert!(row.get("descriptor").is_none());
-                let selected = connections::provider(row["identity"].clone()).unwrap();
+                let selected = connections::provider(
+                    row["identity"].clone(),
+                    Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap()),
+                )
+                .unwrap();
                 assert_eq!(selected["identity"], row["identity"]);
                 assert_eq!(selected["descriptor"]["label"], "ChatGPT Subscription");
                 assert_eq!(selected["descriptor"]["authentication"][0]["id"], "chatgpt");
diff --git a/plugins/settings/native/src/lib.rs b/plugins/settings/native/src/lib.rs
index 39cdcbac83..71e55ac6e4 100644
--- a/plugins/settings/native/src/lib.rs
+++ b/plugins/settings/native/src/lib.rs
@@ -46,7 +46,10 @@ fn configuration_schema() -> serde_json::Value {
 
 pub const IMPLEMENTATION: &str = "maka.settings";
 pub const EXPORT: &str = "Settings";
-pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
+pub fn provide(
+    registry: &mut NativeRegistry,
+    catalog: Arc<maka_providers::catalog::Catalog>,
+) -> Result<(), String> {
     for (implementation, export, contract) in [
         (IMPLEMENTATION, EXPORT, contract::contract()),
         ("maka.settings.setup", "Setup", contract::setup_contract()),
@@ -56,10 +59,12 @@ pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
             contract::authentication_contract(),
         ),
     ] {
+        let catalog = catalog.clone();
         registry.insert(
             implementation.into(),
             Arc::new(move |context, input| {
                 let contract = contract.clone();
+                let catalog = catalog.clone();
                 Box::pin(async move {
                     let opened = async {
                         let configuration: Configuration = serde_json::from_value(input)
@@ -76,6 +81,7 @@ pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
                             .await
                             .map_err(|_| "settings_store_unavailable")?;
                         Ok::<_, String>(Arc::new(service::Settings {
+                            catalog,
                             configuration: Arc::new(store),
                             log: persistence.store().clone(),
                             configured_locale: LanguagePreference::from_locale(
diff --git a/plugins/settings/native/src/service.rs b/plugins/settings/native/src/service.rs
index 631256c9fa..182d92d3af 100644
--- a/plugins/settings/native/src/service.rs
+++ b/plugins/settings/native/src/service.rs
@@ -24,6 +24,7 @@ use maka_host_runtime::application::{Context, Handler, NativeOwner};
 use serde_json::Value;
 use std::sync::Arc;
 pub(crate) struct Settings {
+    pub catalog: Arc<maka_providers::catalog::Catalog>,
     pub configuration: Arc<maka_config::ConfigurationStore>,
     pub log: Arc<maka_event_log::EventLog>,
     pub configured_locale: maka_agent_facts::language::LanguagePreference,
@@ -58,10 +59,17 @@ fn credential_result(
         Err(_) => Err("settings_effect_unknown".into()),
     }
 }
-fn selectable(row: &ConnectionCatalogEntry) -> bool {
-    row.enabled && maka_providers::definition(&row.provider).is_ok()
+fn selectable(
+    row: &ConnectionCatalogEntry,
+    snapshot: Arc<maka_providers::catalog::Snapshot>,
+) -> bool {
+    row.enabled && maka_providers::definition(&row.provider, snapshot).is_ok()
 }
-async fn model_choice(row: &ConnectionCatalogEntry, model: &str) -> Option<ModelChoice> {
+async fn model_choice(
+    row: &ConnectionCatalogEntry,
+    model: &str,
+    snapshot: Arc<maka_providers::catalog::Snapshot>,
+) -> Option<ModelChoice> {
     let info = row
         .models
         .iter()
@@ -75,7 +83,7 @@ async fn model_choice(row: &ConnectionCatalogEntry, model: &str) -> Option<Model
             .and_then(|all| all.get(model))
             .cloned();
         let default_thinking_level = overrides.as_ref().and_then(|o| o.default_thinking_level);
-        let resolved = maka_providers::definition(&row.provider)
+        let resolved = maka_providers::definition(&row.provider, snapshot.clone())
             .ok()?
             .prepare(maka_agent_ports::provider::Resolve {
                 connection: maka_agent_ports::provider::Connection {
@@ -97,6 +105,7 @@ async fn model_choice(row: &ConnectionCatalogEntry, model: &str) -> Option<Model
     } else {
         maka_providers::api::ApiProviders::model_metadata(
             &row.provider.name,
+            snapshot,
             maka_agent_ports::provider::Connection {
                 id: row.connection_id.clone(),
                 revision: row.revision,
@@ -142,14 +151,15 @@ async fn model_choice(row: &ConnectionCatalogEntry, model: &str) -> Option<Model
 async fn resolved_model(
     catalog: &ConnectionCatalogSnapshot,
     target: &ConnectionTarget,
+    snapshot: Arc<maka_providers::catalog::Snapshot>,
 ) -> DefaultModel {
     let row = catalog.connections.iter().find(|row| {
         row.connection_id == target.connection_id
-            && selectable(row)
+            && selectable(row, snapshot.clone())
             && row.enabled_model_ids.contains(&target.model_id)
     });
     let choice = match row {
-        Some(row) => model_choice(row, &target.model_id).await,
+        Some(row) => model_choice(row, &target.model_id, snapshot).await,
         None => None,
     };
     match choice {
@@ -159,10 +169,13 @@ async fn resolved_model(
         },
     }
 }
-async fn default_model(catalog: &ConnectionCatalogSnapshot) -> DefaultModel {
+async fn default_model(
+    catalog: &ConnectionCatalogSnapshot,
+    snapshot: Arc<maka_providers::catalog::Snapshot>,
+) -> DefaultModel {
     match &catalog.default_target {
         None => DefaultModel::None,
-        Some(target) => resolved_model(catalog, target).await,
+        Some(target) => resolved_model(catalog, target, snapshot).await,
     }
 }
 
@@ -174,6 +187,7 @@ impl Handler for Settings {
         context: Context,
     ) -> BoxFuture<'static, Result<Value, String>> {
         let store = self.configuration.clone();
+        let public_catalog = self.catalog.clone();
         let log = self.log.clone();
         let configured_locale = self.configured_locale;
         let native_language = self.native_language;
@@ -196,12 +210,50 @@ impl Handler for Settings {
                     context.operation().ok_or("settings_mutation_required")?;
                     super::preferences::update(&store, input).await
                 }
-                "connections" => super::connections::page(&store, input).await,
+                "connections" => {
+                    let snapshot = public_catalog
+                        .snapshot
+                        .read()
+                        .await
+                        .as_ref()
+                        .map_err(|_| "settings_store_unavailable")?
+                        .clone();
+                    super::connections::page(&store, input, snapshot).await
+                }
                 "connection" => super::connections::detail(&store, input).await,
-                "providers" => super::connections::providers(input),
-                "provider" => super::connections::provider(input),
-                "discoverModels" => super::setup::discover(&store, input, context).await,
-                "loginCredential" => super::credential::login(&store, input, context).await,
+                "providers" => {
+                    let snapshot = public_catalog
+                        .snapshot
+                        .read()
+                        .await
+                        .as_ref()
+                        .map_err(|_| "settings_store_unavailable")?
+                        .clone();
+                    super::connections::providers(input, snapshot)
+                }
+                "provider" => {
+                    let snapshot = public_catalog
+                        .snapshot
+                        .read()
+                        .await
+                        .as_ref()
+                        .map_err(|_| "settings_store_unavailable")?
+                        .clone();
+                    super::connections::provider(input, snapshot)
+                }
+                "discoverModels" => {
+                    super::setup::discover(&store, &public_catalog, input, context).await
+                }
+                "loginCredential" => {
+                    let snapshot = public_catalog
+                        .snapshot
+                        .read()
+                        .await
+                        .as_ref()
+                        .map_err(|_| "settings_store_unavailable")?
+                        .clone();
+                    super::credential::login(&store, snapshot, input, context).await
+                }
                 "create" => {
                     context.operation().ok_or("settings_mutation_required")?;
                     let Ok(mut input) =
@@ -209,7 +261,15 @@ impl Handler for Settings {
                     else {
                         return rejected(ConfigurationRejection::InvalidInput);
                     };
-                    let Ok(definition) = super::connections::definition(&input.connection.provider)
+                    let snapshot = public_catalog
+                        .snapshot
+                        .read()
+                        .await
+                        .as_ref()
+                        .map_err(|_| "settings_store_unavailable")?
+                        .clone();
+                    let Ok(definition) =
+                        super::connections::definition(&input.connection.provider, snapshot)
                     else {
                         return rejected(ConfigurationRejection::ProviderUnavailable);
                     };
@@ -235,7 +295,15 @@ impl Handler for Settings {
                             row.connection_id == input.expected.connection_id
                                 && row.revision == input.expected.revision
                         }) {
-                            let Ok(definition) = super::connections::definition(&row.provider)
+                            let snapshot = public_catalog
+                                .snapshot
+                                .read()
+                                .await
+                                .as_ref()
+                                .map_err(|_| "settings_store_unavailable")?
+                                .clone();
+                            let Ok(definition) =
+                                super::connections::definition(&row.provider, snapshot)
                             else {
                                 return rejected(ConfigurationRejection::ProviderUnavailable);
                             };
@@ -272,7 +340,16 @@ impl Handler for Settings {
                     };
                     credential_result(store.delete_credential(input).await)
                 }
-                "saveCredential" => super::credential::save(&store, input, context).await,
+                "saveCredential" => {
+                    let snapshot = public_catalog
+                        .snapshot
+                        .read()
+                        .await
+                        .as_ref()
+                        .map_err(|_| "settings_store_unavailable")?
+                        .clone();
+                    super::credential::save(&store, snapshot, input, context).await
+                }
                 "updateModelSettings" => {
                     context.operation().ok_or("settings_mutation_required")?;
                     let input: UpdateModelSettings =
@@ -334,19 +411,31 @@ impl Handler for Settings {
                 "resolveModel" => {
                     let target: ConnectionTarget =
                         serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
+                    let public_guard = public_catalog.snapshot.read().await;
+                    let snapshot = public_guard
+                        .as_ref()
+                        .map_err(|_| "settings_store_unavailable")?
+                        .clone();
                     let catalog = store
                         .catalog()
                         .await
                         .map_err(|_| "settings_store_unavailable")?;
-                    super::connections::encode(resolved_model(&catalog, &target).await)
+                    drop(public_guard);
+                    super::connections::encode(resolved_model(&catalog, &target, snapshot).await)
                 }
                 "models" => {
                     let input: ModelsRead =
                         serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
+                    let public_guard = public_catalog.snapshot.read().await;
+                    let snapshot = public_guard
+                        .as_ref()
+                        .map_err(|_| "settings_store_unavailable")?
+                        .clone();
                     let catalog = store
                         .catalog()
                         .await
                         .map_err(|_| "settings_store_unavailable")?;
+                    drop(public_guard);
                     let start = match input {
                         ModelsRead::Start { query } => ModelCursor {
                             query,
@@ -384,7 +473,7 @@ impl Handler for Settings {
                         .enumerate()
                         .skip(start.connection)
                     {
-                        if !selectable(row) {
+                        if !selectable(row, snapshot.clone()) {
                             continue;
                         }
                         for (at, model) in row.enabled_model_ids.iter().enumerate().skip(
@@ -402,7 +491,8 @@ impl Handler for Settings {
                                 });
                                 break 'rows;
                             }
-                            let Some(choice) = model_choice(row, model).await else {
+                            let Some(choice) = model_choice(row, model, snapshot.clone()).await
+                            else {
                                 continue;
                             };
                             let matches = terms.is_empty() || {
@@ -422,7 +512,7 @@ impl Handler for Settings {
                     }
                     serde_json::to_value(ModelsPage::Ready {
                         revision: catalog.revision,
-                        default_model: Box::new(default_model(&catalog).await),
+                        default_model: Box::new(default_model(&catalog, snapshot).await),
                         choices,
                         next,
                     })
diff --git a/plugins/settings/native/src/setup.rs b/plugins/settings/native/src/setup.rs
index 5ffa52dea4..cd51c1816f 100644
--- a/plugins/settings/native/src/setup.rs
+++ b/plugins/settings/native/src/setup.rs
@@ -34,13 +34,26 @@ use std::{
 
 pub(crate) async fn discover(
     store: &Arc<ConfigurationStore>,
+    catalog: &maka_providers::catalog::Catalog,
     input: Value,
     context: Context,
 ) -> Result<Value, String> {
     context.operation().ok_or("settings_mutation_required")?;
     let expected: ConnectionCredentialTarget =
         serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
-    let Ok(definition) = crate::connections::definition(&expected.provider) else {
+    // Explicit refresh schedules the Root-owned public sources without waiting
+    // for an unrelated public endpoint. Account discovery uses last-good facts.
+    if let Ok(completed) = catalog.request_refresh() {
+        drop(completed);
+    }
+    let snapshot = catalog
+        .snapshot
+        .read()
+        .await
+        .as_ref()
+        .map_err(|_| "settings_store_unavailable")?
+        .clone();
+    let Ok(definition) = crate::connections::definition(&expected.provider, snapshot) else {
         return encode(SetupResult::Failed);
     };
     let ticket = match store
diff --git a/products/maka/terminal-shell/tests/real_host/oauth.rs b/products/maka/terminal-shell/tests/real_host/oauth.rs
index 145e3ef3dc..2e8d2fbb19 100644
--- a/products/maka/terminal-shell/tests/real_host/oauth.rs
+++ b/products/maka/terminal-shell/tests/real_host/oauth.rs
@@ -46,7 +46,12 @@ impl Handler for Authentication {
                 let identity = serde_json::from_value(input)
                     .map_err(|_| "invalid provider identity".to_owned());
                 identity.and_then(|identity| {
-                    let descriptor = maka_providers::definition(&identity)?.descriptor().clone();
+                    let descriptor = maka_providers::definition(
+                        &identity,
+                        std::sync::Arc::new(maka_providers::catalog::Snapshot::bundled()?),
+                    )?
+                    .descriptor()
+                    .clone();
                     serde_json::to_value(maka_settings_native::contract::Provider {
                         identity,
                         descriptor,


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org