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
+20 -7
View File
@@ -302,11 +302,12 @@ class ManagerTurns:
raise ValidationError("این نقش قابل استخدام نیست.")
if not tier:
raise ValidationError("سطح کارمند را انتخاب کنید.")
# The owner's per-org policy may lower the global cap, never raise it.
policy = await self.grants.get_org_policy(organization_id)
cap = min(settings.engine_max_employees, policy.max_employees)
count = await self.employees.count_for_org(organization_id)
if count >= settings.engine_max_employees:
raise ConflictError(
f"سقف تعداد کارمندان ({settings.engine_max_employees}) پر شده است."
)
if count >= cap:
raise ConflictError(f"سقف تعداد کارمندان ({cap}) پر شده است.")
granted = await self.grants.granted_models(organization_id=organization_id)
# A hire is an explicit request for a tier: no silent downgrade, or the
# manager would think it hired a strong employee and got a fast one.
@@ -529,12 +530,14 @@ class ManagerTurns:
if draft.kind in {k.value for k in TaskKind}
else TaskKind.FEATURE
)
# A thread made by the web UI has no project; the org's project is the fallback.
project_id = thread.project_id or await self._project_id_for(organization_id)
task = await self.tasks.create(
Task(
organization_id=organization_id,
title=draft.title[:200],
description=draft.description,
project_id=thread.project_id,
project_id=project_id,
thread_id=thread.id,
created_by_user_id=user_id,
)
@@ -544,9 +547,11 @@ class ManagerTurns:
TaskPipelineState(
task_id=task.id,
organization_id=organization_id,
project_id=thread.project_id,
project_id=project_id,
pipeline=resolve_pipeline(
manager_choice=draft.pipeline or None, task_kind=kind
manager_choice=draft.pipeline or None,
task_kind=kind,
has_active_reviewer=await self._has_active_reviewer(organization_id),
),
kind=kind,
risk=Risk.MEDIUM,
@@ -719,6 +724,7 @@ class ManagerTurns:
pipeline = resolve_pipeline(
manager_choice=assign.pipeline or None,
task_kind=state.kind if state else None,
has_active_reviewer=await self._has_active_reviewer(task.organization_id),
)
first_stage = (
Stage.PENDING_ANALYSIS
@@ -1067,6 +1073,13 @@ class ManagerTurns:
return employee.project_id
return None
async def _has_active_reviewer(self, organization_id: str) -> bool:
"""A review stage needs an active reviewer to act on it, or the task dead-ends."""
return any(
e.role_key is RoleKey.REVIEWER and e.is_available
for e in await self.employees.list_for_org(organization_id)
)
__all__ = [
"BOOTSTRAP_TIERS",