"""The manager AI: chat → tasks → team, triage, final verdict, bootstrap. What these tests defend: * an order becomes **at most three** tasks, each with its own pipeline state at ``pending_manager_review`` and a link back to the thread * HIRE respects the employee cap and picks a **granted** model for the tier; the last manager cannot be fired * ``ASK_USER`` parks the task with a human and asks in the thread * triage assigns to the least busy employee, and a name is matched **inside the org only** — the manager of org A can never assign an employee of org B, even by exact name * ``COMPLETE`` merges, ``REVISE`` goes back to the developer with cycle + 1 * ``KNOWLEDGE`` tags become org memory * bootstrap is idempotent, and with no grants it creates an inactive team plus the "waiting for the owner" message """ from __future__ import annotations import pytest import pytest_asyncio from sqlalchemy import select from app.application.container_org import build_memory from app.application.engine.manager import ( NO_MODEL_GRANTED_MESSAGE, ManagerTurns, ) from app.core.config import settings from app.core.errors import ConflictError, NotFoundError from app.domain.base import new_id from app.domain.workforce.engine import EmployeeProfile from app.domain.workforce.models import Employee, RoleKey from app.domain.workforce.pipeline import PipelineKey, Stage, TaskPipelineState from app.infrastructure.db.models import ( AiModelRow, AiProviderRow, EmployeeRow, OrganizationRow, RunRow, TaskRow, ThreadRow, UserRow, ) from app.infrastructure.db.models_org import OrgMemoryRow from app.infrastructure.repositories.ai_control import ( AiModelRepository, ModelCredentialRepository, RateCardRepository, ) from app.infrastructure.repositories.audit import UsageLedgerRepository from app.infrastructure.repositories.engine import ( EmployeeProfileRepository, PipelineStateRepository, ) from app.infrastructure.repositories.workforce import ( EmployeeRepository, MessageRepository, RunRepository, RunStepRepository, TaskRepository, ThreadRepository, ToolCallRepository, ) from tests.engine_fakes import FakeGrants, ScriptedModel, ScriptedReply @pytest_asyncio.fixture async def world(session_factory, seeded): """One organization with a manager, a developer, a granted model.""" async with session_factory() as session: user = UserRow(email=f"mgr-{new_id()[:8]}@example.com", password_hash="x") session.add(user) await session.flush() org = OrganizationRow(name="سازمان مدیر", slug=f"mgr-{user.id[:6]}", owner_user_id=user.id) session.add(org) await session.flush() provider_id = (await session.execute(select(AiProviderRow.id).limit(1))).scalar_one() strong = await AiModelRepository(session).create( AiModelRow( provider_id=provider_id, internal_name=f"strong-{new_id()[:4]}", display_name_for_admin="مدل قوی", capabilities=["text", "code", "reasoning"], input_price_per_1m=0.5, output_price_per_1m=3.0, is_active=True, ) ) fast = await AiModelRepository(session).create( AiModelRow( provider_id=provider_id, internal_name=f"fast-{new_id()[:4]}", display_name_for_admin="مدل سریع", capabilities=["text", "code"], input_price_per_1m=0.1, output_price_per_1m=0.5, is_active=True, ) ) await session.commit() yield _World( session_factory=session_factory, organization_id=org.id, user_id=user.id, strong_id=strong.id, fast_id=fast.id, ) class _World: def __init__(self, **kwargs) -> None: self.__dict__.update(kwargs) async def employee(self, session, name: str, role: RoleKey = RoleKey.DEVELOPER): return await EmployeeRepository(session).create( Employee(organization_id=self.organization_id, name=name, role_key=role) ) async def _manager(world, session, *, scripted, grants, employee_ids=None) -> ManagerTurns: """A manager service with real repositories and scripted everything else.""" return ManagerTurns( provider=scripted, employees=EmployeeRepository(session), messages=MessageRepository(session), tasks=TaskRepository(session), threads=ThreadRepository(session), models=AiModelRepository(session), credentials=ModelCredentialRepository(session), rate_cards=RateCardRepository(session), ledger=UsageLedgerRepository(session), grants=grants, memory=build_memory(session), profiles=EmployeeProfileRepository(session), states=PipelineStateRepository(session), runner=None, ) async def _with_model_profile(world, session, employee_id: str, model_id: str) -> None: await EmployeeProfileRepository(session).upsert( EmployeeProfile(employee_id=employee_id, ai_model_id=model_id, roles=["manager"]) ) # --------------------------------------------------------------------------- # 3.1 chat: an order becomes tasks # --------------------------------------------------------------------------- async def test_an_order_becomes_tasks_with_pipeline_state(world, session_factory): scripted = ScriptedModel( [ ScriptedReply( content=( "باشه، دو کار می‌سازم.\n" '[[CREATE_TASK title="فرم ورود" kind="feature"]]' "ساخت فرم ورود با اعتبارسنجی[[/CREATE_TASK]]" '[[CREATE_TASK title="تست فرم" kind="feature" depends_on=""]]' "تست فرم ورود[[/CREATE_TASK]]" ) ) ] ) async with session_factory() as session: manager_id = (await world.employee(session, "مدیر یک", RoleKey.MANAGER)).id await world.employee(session, "سارا", RoleKey.DEVELOPER) await _with_model_profile(world, session, manager_id, world.strong_id) thread = await ThreadRepository(session).create( ThreadRow(organization_id=world.organization_id, title="گفتگو") ) await session.commit() manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) user_message, assistant = await manager.chat( organization_id=world.organization_id, thread_id=thread.id, content="یک صفحهٔ ورود با تست بساز.", user_id=world.user_id, ) await session.commit() tasks = await TaskRepository(session).list_for_org(world.organization_id) states = PipelineStateRepository(session) assert "باشه" in assistant.content assert "کار ساخته شد: فرم ورود" in assistant.content assert len(tasks) == 2 assert tasks[0].thread_id == thread.id for task in tasks: state = await states.get(task.id) assert state.stage is Stage.PENDING_MANAGER_REVIEW assert state.pipeline is PipelineKey.PLAN_CODE_TEST assert state.organization_id == world.organization_id # The task-created event went out, so the manager chat shows the cards live. assert scripted.calls == 1 async def test_at_most_three_tasks_per_order(world, session_factory): scripted = ScriptedModel( [ ScriptedReply( content="".join( f'[[CREATE_TASK title="کار {i}"]]توضیح[[/CREATE_TASK]]' for i in range(5) ) ) ] ) async with session_factory() as session: manager_id = (await world.employee(session, "مدیر یک", RoleKey.MANAGER)).id await _with_model_profile(world, session, manager_id, world.strong_id) thread = await ThreadRepository(session).create( ThreadRow(organization_id=world.organization_id, title="گفتگو") ) await session.commit() manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) _user, assistant = await manager.chat( organization_id=world.organization_id, thread_id=thread.id, content="پنج کار بساز.", user_id=world.user_id, ) await session.commit() tasks = await TaskRepository(session).list_for_org(world.organization_id) # Three created, two refused by the protocol limit, and the user is told. assert len(tasks) == 3 assert assistant.content.count("کار ساخته شد") == 3 async def test_the_chat_prompt_asks_for_atomic_tasks(world, session_factory): scripted = ScriptedModel([ScriptedReply(content="سلام")]) async with session_factory() as session: manager_id = (await world.employee(session, "مدیر یک", RoleKey.MANAGER)).id await _with_model_profile(world, session, manager_id, world.strong_id) thread = await ThreadRepository(session).create( ThreadRow(organization_id=world.organization_id, title="گفتگو") ) await session.commit() manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) await manager.chat( organization_id=world.organization_id, thread_id=thread.id, content="یک کار بساز.", user_id=world.user_id, ) await session.commit() prompt = scripted.all_text() assert "اتمیک" in prompt # atomic tasks that do not touch the same files assert "هم‌زمان" in prompt assert "مدیر یک" in prompt # the roster is in the context async def test_a_chat_model_call_is_billed(world, session_factory): scripted = ScriptedModel([ScriptedReply(content="سلام", input_tokens=9, output_tokens=4)]) from app.infrastructure.db.models import UsageEventRow async with session_factory() as session: manager_id = (await world.employee(session, "مدیر یک", RoleKey.MANAGER)).id await _with_model_profile(world, session, manager_id, world.strong_id) thread = await ThreadRepository(session).create( ThreadRow(organization_id=world.organization_id, title="گفتگو") ) await session.commit() manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) await manager.chat( organization_id=world.organization_id, thread_id=thread.id, content="سلام", user_id=world.user_id, ) await session.commit() rows = ( ( await session.execute( select(UsageEventRow).where( UsageEventRow.organization_id == world.organization_id ) ) ) .scalars() .all() ) assert len(rows) == 2 assert sum(r.quantity for r in rows if r.unit == "model_input_tokens") == 9 assert sum(r.quantity for r in rows if r.unit == "model_output_tokens") == 4 async def test_a_chat_without_grants_explains_itself(world, session_factory): """No grant, no answer — and the user is told why, in Persian.""" scripted = ScriptedModel([ScriptedReply(content="سلام")]) async with session_factory() as session: manager_id = (await world.employee(session, "مدیر یک", RoleKey.MANAGER)).id await _with_model_profile(world, session, manager_id, world.strong_id) thread = await ThreadRepository(session).create( ThreadRow(organization_id=world.organization_id, title="گفتگو") ) await session.commit() manager = await _manager(world, session, scripted=scripted, grants=FakeGrants({})) _user, assistant = await manager.chat( organization_id=world.organization_id, thread_id=thread.id, content="سلام", user_id=world.user_id, ) await session.commit() messages = await MessageRepository(session).list_for_thread(thread.id) assert scripted.calls == 0 assert "دستور دریافت شد" in assistant.content assert any("مدیری با مدل" in m.content for m in messages) # --------------------------------------------------------------------------- # 3.2 team # --------------------------------------------------------------------------- async def test_hire_picks_a_granted_model_of_the_tier(world, session_factory): async with session_factory() as session: grants = FakeGrants({world.strong_id: "strong", world.fast_id: "fast"}) manager = await _manager(world, session, scripted=ScriptedModel(), grants=grants) employee = await manager.hire( organization_id=world.organization_id, role=RoleKey.TESTER, tier="fast" ) await session.commit() profile = await EmployeeProfileRepository(session).get(employee.id) assert profile is not None assert profile.ai_model_id == world.fast_id assert profile.role_list() == ["tester"] assert profile.hired_by == "manager" async def test_hire_without_a_granted_model_is_refused(world, session_factory): async with session_factory() as session: manager = await _manager( world, session, scripted=ScriptedModel(), grants=FakeGrants({world.fast_id: "fast"}) ) with pytest.raises(ConflictError): await manager.hire( organization_id=world.organization_id, role=RoleKey.REVIEWER, tier="strong" ) async def test_the_employee_cap_is_enforced(world, session_factory, monkeypatch): monkeypatch.setattr(settings, "engine_max_employees", 2) async with session_factory() as session: await world.employee(session, "یک") await world.employee(session, "دو") await session.commit() manager = await _manager( world, session, scripted=ScriptedModel(), grants=FakeGrants({world.fast_id: "fast"}) ) with pytest.raises(ConflictError): await manager.hire( organization_id=world.organization_id, role=RoleKey.TESTER, tier="fast", ) async def test_the_last_manager_cannot_be_fired(world, session_factory): async with session_factory() as session: manager_row = await world.employee(session, "مدیر یک", RoleKey.MANAGER) await session.commit() manager = await _manager(world, session, scripted=ScriptedModel(), grants=FakeGrants({})) with pytest.raises(ConflictError): await manager.fire(organization_id=world.organization_id, employee_id=manager_row.id) # A second manager can be fired. second = await world.employee(session, "مدیر دو", RoleKey.MANAGER) await session.commit() fired = await manager.fire(organization_id=world.organization_id, employee_id=second.id) await session.commit() assert fired.status.value == "archived" async def test_firing_a_foreign_employee_is_a_404(world, session_factory): """Another tenant's employee id is indistinguishable from a missing one.""" async with session_factory() as session: other_user = UserRow(email=f"other-{new_id()[:6]}@example.com", password_hash="x") session.add(other_user) await session.flush() other_org = OrganizationRow( name="سازمان دیگر", slug=f"oth-{other_user.id[:6]}", owner_user_id=other_user.id ) session.add(other_org) await session.flush() stranger = await session.execute(select(EmployeeRow).limit(1)) foreign = None if stranger.scalars().first() is not None: foreign = stranger.scalars().first().id await session.commit() if foreign is None: async with session_factory() as session: foreign = (await world.employee(session, "بومی")).id await session.commit() async with session_factory() as session: # The employee belongs to *this* org; the *other* org must not see it. manager = await _manager(world, session, scripted=ScriptedModel(), grants=FakeGrants({})) with pytest.raises(NotFoundError): await manager.fire(organization_id="org-does-not-exist", employee_id=foreign) # --------------------------------------------------------------------------- # 3.3 triage # --------------------------------------------------------------------------- async def _task_with_state( world, session, title: str = "کار", stage: Stage = Stage.PENDING_MANAGER_REVIEW ) -> tuple[str, str]: task = TaskRow(organization_id=world.organization_id, title=title) session.add(task) await session.flush() run = RunRow(task_id=task.id) session.add(run) await session.flush() await PipelineStateRepository(session).create( TaskPipelineState(task_id=task.id, organization_id=world.organization_id, stage=stage) ) await session.flush() return task.id, run.id async def test_triage_assigns_to_the_least_busy_employee(world, session_factory, monkeypatch): monkeypatch.setattr(settings, "engine_max_tool_calls_per_turn", 40) scripted = ScriptedModel( [ScriptedReply(content='[[ASSIGN employee="" pipeline="plan_code_test"/]]')] ) async with session_factory() as session: manager_row = await world.employee(session, "مدیر یک", RoleKey.MANAGER) await _with_model_profile(world, session, manager_row.id, world.strong_id) busy = await world.employee(session, "مشغول", RoleKey.DEVELOPER) free = await world.employee(session, "آzاد", RoleKey.DEVELOPER) # The busy one already has two open tasks. for _ in range(2): session.add( TaskRow( organization_id=world.organization_id, title="کار دیگر", assigned_employee_id=busy.id, ) ) await session.flush() task_id, run_id = await _task_with_state(world, session) await session.commit() from app.application.engine.runner import TurnRunner runner = TurnRunner( provider=scripted, models=AiModelRepository(session), credentials=ModelCredentialRepository(session), rate_cards=RateCardRepository(session), ledger=UsageLedgerRepository(session), grants=FakeGrants({world.strong_id: "strong"}), memory=build_memory(session), turns=None, run_steps=RunStepRepository(session), tool_calls=ToolCallRepository(session), runs=RunRepository(session), states=PipelineStateRepository(session), ) manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) manager.runner = runner task = await TaskRepository(session).get(task_id) outcome = await manager.triage(task=task, run_id=run_id, employee=manager_row) await session.commit() state = await PipelineStateRepository(session).get(task_id) refreshed = await TaskRepository(session).get(task_id) assert outcome.acted is True assert outcome.decision == "assigned" assert outcome.employee_id == free.id assert state.stage is Stage.PENDING_ANALYSIS assert refreshed.assigned_employee_id == free.id async def test_a_named_employee_is_matched_inside_the_org(world, session_factory): scripted = ScriptedModel([ScriptedReply(content='[[ASSIGN employee="سارا"/]]')]) async with session_factory() as session: manager_row = await world.employee(session, "مدیر یک", RoleKey.MANAGER) await _with_model_profile(world, session, manager_row.id, world.strong_id) sara = await world.employee(session, "سارا", RoleKey.DEVELOPER) await session.flush() task_id, run_id = await _task_with_state(world, session) await session.commit() from app.application.engine.runner import TurnRunner runner = TurnRunner( provider=scripted, models=AiModelRepository(session), credentials=ModelCredentialRepository(session), rate_cards=RateCardRepository(session), ledger=UsageLedgerRepository(session), grants=FakeGrants({world.strong_id: "strong"}), memory=build_memory(session), states=PipelineStateRepository(session), ) manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) manager.runner = runner task = await TaskRepository(session).get(task_id) outcome = await manager.triage(task=task, run_id=run_id, employee=manager_row) await session.commit() assert outcome.employee_id == sara.id async def test_the_manager_of_org_a_cannot_assign_an_employee_of_org_b(world, session_factory): """Cross-tenant assignment by exact name must resolve to nothing.""" scripted = ScriptedModel([ScriptedReply(content='[[ASSIGN employee="دزد"/]]')]) async with session_factory() as session: manager_row = await world.employee(session, "مدیر یک", RoleKey.MANAGER) await _with_model_profile(world, session, manager_row.id, world.strong_id) other_user = UserRow(email=f"b-{new_id()[:6]}@example.com", password_hash="x") session.add(other_user) await session.flush() other_org = OrganizationRow( name="سازمان ب", slug=f"b-{other_user.id[:6]}", owner_user_id=other_user.id ) session.add(other_org) await session.flush() # The other org's employee is named exactly like the assignment asks. session.add(EmployeeRow(organization_id=other_org.id, name="دزد", role_key="developer")) await session.flush() task_id, run_id = await _task_with_state(world, session) await session.commit() from app.application.engine.runner import TurnRunner runner = TurnRunner( provider=scripted, models=AiModelRepository(session), credentials=ModelCredentialRepository(session), rate_cards=RateCardRepository(session), ledger=UsageLedgerRepository(session), grants=FakeGrants({world.strong_id: "strong"}), memory=build_memory(session), states=PipelineStateRepository(session), ) manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) manager.runner = runner task = await TaskRepository(session).get(task_id) outcome = await manager.triage(task=task, run_id=run_id, employee=manager_row) await session.commit() state = await PipelineStateRepository(session).get(task_id) assert outcome.acted is False assert outcome.employee_id is None assert state.stage is Stage.PENDING_MANAGER_REVIEW async def test_ask_user_parks_the_task_and_asks_in_the_thread(world, session_factory): scripted = ScriptedModel([ScriptedReply(content="[[ASK_USER]]کدام درگاه پرداخت؟[[/ASK_USER]]")]) async with session_factory() as session: manager_row = await world.employee(session, "مدیر یک", RoleKey.MANAGER) await _with_model_profile(world, session, manager_row.id, world.strong_id) thread = await ThreadRepository(session).create( ThreadRow(organization_id=world.organization_id, title="گفتگو") ) await session.flush() task_id, run_id = await _task_with_state(world, session) task = await TaskRepository(session).get(task_id) task.thread_id = thread.id await session.commit() from app.application.engine.runner import TurnRunner runner = TurnRunner( provider=scripted, models=AiModelRepository(session), credentials=ModelCredentialRepository(session), rate_cards=RateCardRepository(session), ledger=UsageLedgerRepository(session), grants=FakeGrants({world.strong_id: "strong"}), memory=build_memory(session), states=PipelineStateRepository(session), ) manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) manager.runner = runner outcome = await manager.triage(task=task, run_id=run_id, employee=manager_row) await session.commit() state = await PipelineStateRepository(session).get(task_id) messages = await MessageRepository(session).list_for_thread(thread.id) assert outcome.decision == "asked_user" assert state.stage is Stage.PENDING_USER_DECISION assert any(m.content == "کدام درگاه پرداخت؟" for m in messages) # --------------------------------------------------------------------------- # 3.4 final verdict # --------------------------------------------------------------------------- async def test_complete_moves_the_task_to_merge_request(world, session_factory): scripted = ScriptedModel([ScriptedReply(content="[[COMPLETE/]]")]) async with session_factory() as session: manager_row = await world.employee(session, "مدیر یک", RoleKey.MANAGER) await _with_model_profile(world, session, manager_row.id, world.strong_id) task_id, run_id = await _task_with_state(world, session, stage=Stage.PENDING_FINAL_REVIEW) await session.commit() from app.application.engine.runner import TurnRunner runner = TurnRunner( provider=scripted, models=AiModelRepository(session), credentials=ModelCredentialRepository(session), rate_cards=RateCardRepository(session), ledger=UsageLedgerRepository(session), grants=FakeGrants({world.strong_id: "strong"}), memory=build_memory(session), states=PipelineStateRepository(session), ) manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) manager.runner = runner task = await TaskRepository(session).get(task_id) outcome = await manager.final_review( task=task, run_id=run_id, employee=manager_row, spec="برنامه", test_output="3 passed", ) await session.commit() state = await PipelineStateRepository(session).get(task_id) assert outcome.decision == "completed" assert state.stage is Stage.MERGE_REQUEST async def test_revise_sends_the_task_back_and_counts_the_cycle(world, session_factory): scripted = ScriptedModel([ScriptedReply(content="[[REVISE]]تست‌ها قرمز است[[/REVISE]]")]) async with session_factory() as session: manager_row = await world.employee(session, "مدیر یک", RoleKey.MANAGER) await _with_model_profile(world, session, manager_row.id, world.strong_id) task_id, run_id = await _task_with_state(world, session, stage=Stage.PENDING_FINAL_REVIEW) await session.commit() from app.application.engine.runner import TurnRunner runner = TurnRunner( provider=scripted, models=AiModelRepository(session), credentials=ModelCredentialRepository(session), rate_cards=RateCardRepository(session), ledger=UsageLedgerRepository(session), grants=FakeGrants({world.strong_id: "strong"}), memory=build_memory(session), states=PipelineStateRepository(session), ) manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) manager.runner = runner task = await TaskRepository(session).get(task_id) outcome = await manager.final_review( task=task, run_id=run_id, employee=manager_row, test_output="1 failed" ) await session.commit() state = await PipelineStateRepository(session).get(task_id) assert outcome.decision == "revised" assert state.stage is Stage.PENDING_IMPLEMENTATION assert state.cycle == 1 async def test_resolve_error_retries_with_a_hint(world, session_factory): scripted = ScriptedModel( [ScriptedReply(content='[[RESOLVE_ERROR hint="ابتدا مهاجرت را بنویس"/]]')] ) async with session_factory() as session: manager_row = await world.employee(session, "مدیر یک", RoleKey.MANAGER) await _with_model_profile(world, session, manager_row.id, world.strong_id) task_id, run_id = await _task_with_state(world, session) await session.commit() from app.application.engine.runner import TurnRunner runner = TurnRunner( provider=scripted, models=AiModelRepository(session), credentials=ModelCredentialRepository(session), rate_cards=RateCardRepository(session), ledger=UsageLedgerRepository(session), grants=FakeGrants({world.strong_id: "strong"}), memory=build_memory(session), states=PipelineStateRepository(session), ) manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) manager.runner = runner task = await TaskRepository(session).get(task_id) await PipelineStateRepository(session).advance( task_id=task_id, expected_from=Stage.PENDING_MANAGER_REVIEW, to=Stage.ERROR_REVIEW ) await session.commit() outcome = await manager.error_review( task=task, run_id=run_id, employee=manager_row, error_summary="تست قرمز" ) await session.commit() state = await PipelineStateRepository(session).get(task_id) assert outcome.decision == "resolved" assert state.stage is Stage.PENDING_IMPLEMENTATION assert "مهاجرت" in outcome.message # --------------------------------------------------------------------------- # 3.5 knowledge and 3.6 bootstrap # --------------------------------------------------------------------------- async def test_knowledge_tags_become_org_memory(world, session_factory): scripted = ScriptedModel( [ ScriptedReply( content=('[[KNOWLEDGE kind="rule" key="deploy"]]قبل از پوش تست بگیر.[[/KNOWLEDGE]]') ) ] ) async with session_factory() as session: manager_row = await world.employee(session, "مدیر یک", RoleKey.MANAGER) await _with_model_profile(world, session, manager_row.id, world.strong_id) thread = await ThreadRepository(session).create( ThreadRow(organization_id=world.organization_id, title="گفتگو") ) await session.commit() manager = await _manager( world, session, scripted=scripted, grants=FakeGrants({world.strong_id: "strong"}) ) await manager.chat( organization_id=world.organization_id, thread_id=thread.id, content="یادت باشد.", user_id=world.user_id, ) await session.commit() rows = ( ( await session.execute( select(OrgMemoryRow).where( OrgMemoryRow.organization_id == world.organization_id ) ) ) .scalars() .all() ) assert len(rows) == 1 assert rows[0].kind == "rule" assert rows[0].source == "manager" assert rows[0].content == "قبل از پوش تست بگیر." async def test_bootstrap_creates_the_team_and_is_idempotent(world, session_factory): async with session_factory() as session: manager = await _manager( world, session, scripted=ScriptedModel(), grants=FakeGrants({world.strong_id: "strong", world.fast_id: "fast"}), ) await manager.bootstrap_team(organization_id=world.organization_id, project_id=None) await session.commit() employees = await EmployeeRepository(session).list_for_org(world.organization_id) profiles = {} for employee in employees: profile = await EmployeeProfileRepository(session).get(employee.id) profiles[employee.role_key] = profile.ai_model_id if profile else None # A second call changes nothing. await manager.bootstrap_team(organization_id=world.organization_id, project_id=None) await session.commit() again = await EmployeeRepository(session).list_for_org(world.organization_id) assert len(employees) == 4 assert len(again) == 4 assert {e.role_key for e in employees} == { RoleKey.MANAGER, RoleKey.PLANNER, RoleKey.DEVELOPER, RoleKey.TESTER, } assert all(e.status.value == "active" for e in employees) # The manager runs on the strongest granted tier, the tester on the fast one. assert profiles[RoleKey.MANAGER] == world.strong_id assert profiles[RoleKey.TESTER] == world.fast_id async def test_bootstrap_without_grants_creates_an_inactive_team(world, session_factory): async with session_factory() as session: manager = await _manager(world, session, scripted=ScriptedModel(), grants=FakeGrants({})) await manager.bootstrap_team(organization_id=world.organization_id, project_id=None) await session.commit() employees = await EmployeeRepository(session).list_for_org(world.organization_id) threads = await ThreadRepository(session).list_for_org(world.organization_id) notes: list[str] = [] for thread in threads: notes.extend( m.content for m in await MessageRepository(session).list_for_thread(thread.id) ) assert len(employees) == 4 assert all(e.status.value == "paused" for e in employees) assert NO_MODEL_GRANTED_MESSAGE in notes