Skip to content

Hosted safety guard resolves the model without org scoping → false model_unavailable when the model is served from the DB under a non-default org #152

Description

@sandeep-agami

Summary

On a hosted MCP-server deployment that serves its semantic model from the database (no on-disk model artifacts) and whose model is stored under a non-default organization id, the safety guard cannot resolve the model, so every execute_sql is refused with model_unavailable — even though the serve/discovery tools (list_datasources, get_datasource_schema) resolve the same model fine.

Root cause

The guard-model and serve-model resolvers scope their DB reads differently:

  • Serve (tools._load_org): model_store.load_datasource(store, profile, org_id=_current_org_id())
  • Guard (execute_sql._resolve_guard_model): model_store.load_datasource(store, profile)no org_id

model_store.load_datasource(...) defaults org_id to DEFAULT_ORG ("local"). When the model is seeded under a real (minted) org id, the guard's DB read returns None and the hosted guard fails closed with model_unavailable. Introduced in #108.

It is masked on the usual deployment because the resolver then falls through to the on-disk AGAMI_ARTIFACTS_DIR model; a deployment that serves the model only from the DB has no fallback and refuses every query.

Impact

Any hosted deployment that (a) serves the model from Postgres, (b) has a non-local org id, and (c) does not also ship the model on disk → 100% of queries refused. Invisible to deployments that bake the model on disk.

Reproduction

  1. Set AGAMI_DB_URL (Postgres or SQLite).
  2. Seed a model under a non-local org: model_store.write_datasource(store, ds, model, org_id="<minted>").
  3. Resolve the runtime org to that id (via organization.yaml or AGAMI_ORG_ID).
  4. Point AGAMI_ARTIFACTS_DIR at an empty dir (no on-disk model).
  5. Call execute_sql on a declared table → refused model_unavailable (expected: the query runs, guarded).

Suggested fix

Scope the guard's DB load by the resolved org, mirroring the serve path — pass the resolved org id (with a lean fallback for the stdlib-only executor mirror) into model_store.load_datasource(...) inside _resolve_guard_model.

Test gap

The existing DB-sourced guard tests seed and read under the default local org, so this org-scoping divergence isn't exercised. A regression test should seed under a non-local org and assert the guard resolves it.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions