feat(integration): wire v2 workstreams together (engine policy/budget, sandbox, tenant checks, order route, docs)

Engine honours org policy and monthly budget, legacy runner skips engine tasks,
sandbox session provisioned on demand, project fallback for threads, reviewer
fallback, tenant checks on project ids, memory reflection respects grants,
expiry worker, MCP pipeline_status, login redirect, sharing settings, e2e smoke.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mjalmoudy94
2026-10-14 20:57:56 +03:30
co-authored by Claude Sonnet 5.5
parent 36564e3f2b
commit fef5a9deab
43 changed files with 3024 additions and 221 deletions
+152
View File
@@ -0,0 +1,152 @@
"""Org memory reflection runs only on a model the owner granted the organization.
The invariants under test:
* an organization with no granted model learns nothing from a finished task: no
provider call, and the refusal is recorded as ``memory.reflection_no_grant``;
* a granted model is the one that answers, and the cheapest tier wins: the
``fast`` model is chosen over the ``strong`` one, whatever the grant order.
"""
from __future__ import annotations
from contextlib import asynccontextmanager
from sqlalchemy import select
from app.application.container_org import (
build_memory_service,
build_model_grant_service,
build_model_grants,
)
from app.application.ports import ModelResponse
from app.application.ports.memory import TaskOutcome
from app.domain.base import new_id
from app.domain.identity.models import PlatformRole, Role
from app.domain.memory.models import MemoryKind
from app.infrastructure.db.models import (
AiModelRow,
ErrorEventRow,
MembershipRow,
OrganizationRow,
TaskRow,
UserRow,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
async def _org(session_factory, slug: str) -> str:
"""An organization with one owner, written directly to the database."""
async with session_factory() as session:
user = UserRow(
id=new_id(),
email=f"{slug}@example.com",
password_hash="x",
name=slug,
platform_role=PlatformRole.MANAGER.value,
)
session.add(user)
await session.flush()
org = OrganizationRow(id=new_id(), name=slug, slug=slug, owner_user_id=user.id)
session.add(org)
await session.flush()
session.add(
MembershipRow(organization_id=org.id, user_id=user.id, role=Role.OWNER.value)
)
await session.commit()
return org.id
async def _task(session_factory, org_id: str, task_id: str) -> None:
"""A real task row: a memory's evidence is a reference, not a free string."""
async with session_factory() as session:
session.add(TaskRow(id=task_id, organization_id=org_id, title=f"کار {task_id}"))
await session.commit()
async def _grant(session_factory, org_id: str, *internal_names: str) -> None:
"""Grant the named seeded models to the organization, in the order given."""
async with session_factory() as session:
service = build_model_grant_service(session)
for name in internal_names:
row = (
await session.execute(select(AiModelRow).where(AiModelRow.internal_name == name))
).scalar_one()
await service.grant_model(organization_id=org_id, ai_model_id=row.id)
await session.commit()
class _RecordingProvider:
"""Answers every reflection with one lesson, and remembers which model was asked."""
def __init__(self) -> None:
self.asked: list[str] = []
async def complete(self, **kwargs):
model = kwargs["model"]
self.asked.append(model.internal_name)
return ModelResponse(
content="درس ۱: تست‌ها را قبل از ادغام اجرا کن.",
model_internal_name=model.internal_name,
input_tokens=20,
output_tokens=8,
)
@asynccontextmanager
async def _service(session_factory, provider):
"""A memory service wired the way the engine wires it, committed on exit."""
session = session_factory()
try:
service = build_memory_service(
session, provider=provider, grants=build_model_grants(session)
)
yield service
await session.commit()
finally:
await session.close()
def _outcome(task_id: str) -> TaskOutcome:
return TaskOutcome(task_id=task_id, title="ساخت صفحه", status="completed")
# ---------------------------------------------------------------------------
# Reflection and grants
# ---------------------------------------------------------------------------
async def test_an_organization_without_a_grant_learns_nothing(
session_factory, redirect_error_log, seeded
):
org_id = await _org(session_factory, "no-grant")
provider = _RecordingProvider()
async with _service(session_factory, provider) as service:
# The catalogue has models, but the owner granted this organization none.
await service.on_task_finished(organization_id=org_id, task=_outcome("task-no-grant"))
assert await service.list(org_id) == []
assert provider.asked == []
async with session_factory() as session:
kinds = {row.kind for row in (await session.execute(select(ErrorEventRow))).scalars()}
assert "memory.reflection_no_grant" in kinds
async def test_a_granted_model_answers_and_the_cheapest_tier_wins(
session_factory, redirect_error_log, seeded
):
org_id = await _org(session_factory, "granted")
await _task(session_factory, org_id, "task-granted")
# Both models are granted; the reflection must pick the fast one whatever the order.
await _grant(session_factory, org_id, "claude-sonnet-4-5-thinking", "gemini-3-flash")
provider = _RecordingProvider()
async with _service(session_factory, provider) as service:
await service.on_task_finished(organization_id=org_id, task=_outcome("task-granted"))
memories = await service.list(org_id)
assert provider.asked == ["gemini-3-flash"]
assert [m.kind for m in memories] == [MemoryKind.LESSON]
assert all(m.evidence_task_id == "task-granted" for m in memories)