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:
co-authored by
Claude Sonnet 5.5
parent
36564e3f2b
commit
fef5a9deab
@@ -0,0 +1,164 @@
|
||||
"""Integration: a project id from another organization never reaches a thread, a task or a sandbox.
|
||||
|
||||
The caller names the project of a thread or a task. The service answers 404 for a project
|
||||
that is not the caller's organization's, so one tenant cannot put work into another
|
||||
tenant's project. The engine applies the same rule before it reuses or starts a sandbox,
|
||||
so a task row that names a foreign project never gets that project's session, and a
|
||||
workspace of another organization on the same project is ignored.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.application.container_engine import _EngineSandboxSessions
|
||||
from app.domain.base import new_id
|
||||
from app.domain.sandbox.models import SandboxSession, SessionStatus, Workspace, WorkspaceStatus
|
||||
from app.infrastructure.db.models import OrganizationRow, ProjectRow, TaskRow, UserRow
|
||||
from app.infrastructure.repositories.sandbox import (
|
||||
SandboxSessionRepository,
|
||||
SandboxTemplateRepository,
|
||||
WorkspaceRepository,
|
||||
)
|
||||
from app.infrastructure.repositories.workforce import TaskRepository
|
||||
from tests.conftest import auth_header, signup
|
||||
from tests.test_org_wizard import _complete
|
||||
|
||||
|
||||
async def _org_with_project(session, *, label: str) -> tuple[str, str, str]:
|
||||
"""One organization with its owner and one project. Returns (org, owner, project) ids."""
|
||||
user = UserRow(email=f"{label}-{new_id()[:8]}@example.com", password_hash="x")
|
||||
session.add(user)
|
||||
await session.flush()
|
||||
org = OrganizationRow(
|
||||
name=f"سازمان {label}", slug=f"{label}-{user.id[:6]}", owner_user_id=user.id
|
||||
)
|
||||
session.add(org)
|
||||
await session.flush()
|
||||
project = ProjectRow(
|
||||
id=new_id(), organization_id=org.id, name="app", slug="app", test_command="pytest -q"
|
||||
)
|
||||
session.add(project)
|
||||
await session.flush()
|
||||
return org.id, user.id, project.id
|
||||
|
||||
|
||||
async def test_a_thread_or_task_cannot_name_another_orgs_project(client, seeded):
|
||||
headers_a = auth_header((await signup(client, "tenant-a@example.com"))["access_token"])
|
||||
headers_b = auth_header((await signup(client, "tenant-b@example.com"))["access_token"])
|
||||
body_a = await _complete(client, headers_a, {"org_name": "سازمان الف", "slug": "tenant-alpha"})
|
||||
body_b = await _complete(client, headers_b, {"org_name": "سازمان ب", "slug": "tenant-bravo"})
|
||||
org_a = body_a["organization"]["id"]
|
||||
own_project = body_a["project"]["id"]
|
||||
foreign_project = body_b["project"]["id"]
|
||||
|
||||
thread = await client.post(
|
||||
f"/api/organizations/{org_a}/threads",
|
||||
json={"title": "گفتگو", "project_id": foreign_project},
|
||||
headers=headers_a,
|
||||
)
|
||||
task = await client.post(
|
||||
f"/api/organizations/{org_a}/tasks",
|
||||
json={"title": "کار مشترک", "project_id": foreign_project},
|
||||
headers=headers_a,
|
||||
)
|
||||
own = await client.post(
|
||||
f"/api/organizations/{org_a}/tasks",
|
||||
json={"title": "کار خودی", "project_id": own_project},
|
||||
headers=headers_a,
|
||||
)
|
||||
listed = await client.get(f"/api/organizations/{org_a}/tasks", headers=headers_a)
|
||||
|
||||
assert thread.status_code == 404, thread.text
|
||||
assert task.status_code == 404, task.text
|
||||
assert own.status_code == 201, own.text
|
||||
# The refused task was never stored: the only task of org A is the one in its own project.
|
||||
assert [row["project_id"] for row in listed.json()] == [own_project]
|
||||
|
||||
|
||||
async def test_the_engine_never_runs_a_task_in_another_orgs_project(session_factory, seeded):
|
||||
"""Org B has a running session. A task of org A that names B's project gets none of it."""
|
||||
async with session_factory() as session:
|
||||
org_b, _, project_b = await _org_with_project(session, label="sbx-b")
|
||||
org_a, user_a, _ = await _org_with_project(session, label="sbx-a")
|
||||
template = (await SandboxTemplateRepository(session).list())[0]
|
||||
workspace_b = await WorkspaceRepository(session).create(
|
||||
Workspace(
|
||||
organization_id=org_b,
|
||||
project_id=project_b,
|
||||
template_id=template.id,
|
||||
status=WorkspaceStatus.READY,
|
||||
)
|
||||
)
|
||||
await SandboxSessionRepository(session).create(
|
||||
SandboxSession(
|
||||
workspace_id=workspace_b.id,
|
||||
external_session_id="sess-b",
|
||||
status=SessionStatus.RUNNING,
|
||||
)
|
||||
)
|
||||
task = TaskRow(
|
||||
organization_id=org_a,
|
||||
title="کار غریبه",
|
||||
project_id=project_b,
|
||||
created_by_user_id=user_a,
|
||||
)
|
||||
session.add(task)
|
||||
await session.commit()
|
||||
task_id = task.id
|
||||
|
||||
async with session_factory() as session:
|
||||
row = await TaskRepository(session).get(task_id)
|
||||
assert row is not None
|
||||
sandbox_id = await _EngineSandboxSessions(session).for_task(row)
|
||||
await session.commit()
|
||||
workspaces = await WorkspaceRepository(session).list_for_project(project_b)
|
||||
|
||||
assert sandbox_id == ""
|
||||
# Nothing was provisioned under org A's name in org B's project.
|
||||
assert [w.organization_id for w in workspaces] == [org_b]
|
||||
|
||||
|
||||
async def test_a_workspace_of_another_org_on_the_same_project_is_ignored(session_factory, seeded):
|
||||
"""A foreign workspace linked to org A's own project is never the one the task uses."""
|
||||
async with session_factory() as session:
|
||||
org_a, user_a, project_a = await _org_with_project(session, label="sbx-own")
|
||||
org_b, _, _ = await _org_with_project(session, label="sbx-other")
|
||||
template = (await SandboxTemplateRepository(session).list())[0]
|
||||
foreign = await WorkspaceRepository(session).create(
|
||||
Workspace(
|
||||
organization_id=org_b,
|
||||
project_id=project_a,
|
||||
template_id=template.id,
|
||||
status=WorkspaceStatus.READY,
|
||||
)
|
||||
)
|
||||
await SandboxSessionRepository(session).create(
|
||||
SandboxSession(
|
||||
workspace_id=foreign.id,
|
||||
external_session_id="sess-foreign",
|
||||
status=SessionStatus.RUNNING,
|
||||
)
|
||||
)
|
||||
task = TaskRow(
|
||||
organization_id=org_a,
|
||||
title="کار خودی",
|
||||
project_id=project_a,
|
||||
created_by_user_id=user_a,
|
||||
)
|
||||
session.add(task)
|
||||
await session.commit()
|
||||
task_id = task.id
|
||||
|
||||
async with session_factory() as session:
|
||||
row = await TaskRepository(session).get(task_id)
|
||||
assert row is not None
|
||||
sandbox_id = await _EngineSandboxSessions(session).for_task(row)
|
||||
await session.commit()
|
||||
own = [
|
||||
w
|
||||
for w in await WorkspaceRepository(session).list_for_project(project_a)
|
||||
if w.organization_id == org_a
|
||||
]
|
||||
|
||||
assert sandbox_id
|
||||
assert sandbox_id != "sess-foreign"
|
||||
assert len(own) == 1
|
||||
Reference in New Issue
Block a user