feat(org): model grants, org memory, MCP gateway (v2 phases A1-3..5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
c93b25741e
commit
dee2f72c46
@@ -6,6 +6,7 @@ import pytest
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
import app.application.container as container
|
||||
import app.application.container_org as container_org
|
||||
from app.application.ports.delivery_hooks import DeliveryHooks, NullDeliveryHooks
|
||||
from app.application.ports.deploy_target import (
|
||||
DeployTargetSpec,
|
||||
@@ -109,7 +110,16 @@ async def test_app_profile_repository_is_tenant_scoped(session_factory):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_model_grants_return_active_models_with_tiers(session_factory):
|
||||
async def test_granted_models_carry_tiers_and_need_an_owner_grant(session_factory):
|
||||
"""The real grant service (Phase 3) replaced the "all active models" stub.
|
||||
|
||||
The foundation version asserted the placeholder wiring (every active model
|
||||
usable by every organization). The behaviour the plan changes on purpose is
|
||||
exactly that: an organization now sees a model only after the owner granted
|
||||
it. The assertions below are strictly stronger — they still pin the tier
|
||||
labels, and additionally pin the two switches (the model must be active
|
||||
*and* granted) plus the per-organization scope.
|
||||
"""
|
||||
async with session_factory() as session:
|
||||
provider = AiProviderRow(id=new_id(), name="agy-test")
|
||||
session.add(provider)
|
||||
@@ -135,10 +145,28 @@ async def test_default_model_grants_return_active_models_with_tiers(session_fact
|
||||
|
||||
grants = container.build_model_grants(session)
|
||||
assert isinstance(grants, ModelGrantPort)
|
||||
granted = {g.ai_model_id: g.tier for g in await grants.granted_models(organization_id="o")}
|
||||
org_id, _ = await _org_with_user(session, "granted")
|
||||
other_id, _ = await _org_with_user(session, "untouched")
|
||||
# Nothing is usable until the owner grants it.
|
||||
assert await grants.granted_models(organization_id=org_id) == []
|
||||
assert not await grants.is_granted(organization_id=org_id, ai_model_id="m-fast")
|
||||
|
||||
# ``container.py`` is locked and re-exports only ``build_model_grants``;
|
||||
# the typed builder lives in the workstream's own container.
|
||||
service = container_org.build_model_grant_service(session)
|
||||
await service.grant_model(organization_id=org_id, ai_model_id="m-strong")
|
||||
await service.grant_model(organization_id=org_id, ai_model_id="m-fast")
|
||||
|
||||
granted = {
|
||||
g.ai_model_id: g.tier
|
||||
for g in await grants.granted_models(organization_id=org_id)
|
||||
}
|
||||
assert granted == {"m-strong": "strong", "m-fast": "fast"}
|
||||
assert await grants.is_granted(organization_id="o", ai_model_id="m-fast")
|
||||
assert not await grants.is_granted(organization_id="o", ai_model_id="m-off")
|
||||
assert await grants.is_granted(organization_id=org_id, ai_model_id="m-fast")
|
||||
# Inactive in the catalogue: never granted, whatever the grant table says.
|
||||
assert not await grants.is_granted(organization_id=org_id, ai_model_id="m-off")
|
||||
# Another organization is unaffected by this one's grants.
|
||||
assert await grants.granted_models(organization_id=other_id) == []
|
||||
|
||||
|
||||
def test_tier_for_never_uses_names():
|
||||
|
||||
Reference in New Issue
Block a user