mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-09 19:04:59 +03:00
Single idempotent write boundary provision_organization coordinates Organization, OWNER membership or invitation, subscription/usage, system departments sales/support, audit and transactional outbox in one transaction under set_local_tenant. - New hub_platform.platform domain: PlatformOperator, hash-only PlatformToken, OrganizationProvisioning process record with state machine PENDING/IN_PROGRESS/WAITING_FOR_OWNER/COMPLETED/FAILED. - Platform API POST platform.custocrm.ru/api/v1/organizations: machine token auth + capability platform.organizations.provision, Idempotency-Key header, replay/conflict handling, no secrets in response. - OWNER invitation accept (accept_invitation + auth view) activates organization, subscription and first usage period; idempotent re-accept does not duplicate membership or period. - DB write boundary (tenancy/0005): platform INSERT/UPDATE on identity_organization and platform DML + tenant-isolation policy on identity_department/identity_employeeprofile; Organization.status ACTIVE/PENDING_OWNER (identity/0015). - System department codes centralised in identity/system_departments. Machine token without MFA is an owner-approved deviation from ADR-HUB-0031 section 9; human+MFA platform login belongs to track D. concurrent_p2p_calls derived from membership count is a separate C07 decision (PLAN-CUSTOCRM-0003 section 12). Gate: idempotent replay creates no second tenant; operator never becomes OWNER; provisioning creates no AI-agent/product/connection/demo data. Full backend regression 379 passed (one pre-existing unrelated SOCKS5 test); 19 C06 targeted tests passed. UI unchanged. Production migration/deploy, publication of production PlanVersion and bootstrap of the production platform token are not part of this commit.
29 lines
1.1 KiB
Python
29 lines
1.1 KiB
Python
from __future__ import annotations
|
|
|
|
from hub_platform.platform.capabilities import is_valid_platform_capability
|
|
from hub_platform.platform.models import PlatformOperator
|
|
from hub_platform.platform.tokens import issue_platform_token
|
|
from hub_platform.subscriptions.keys import PlanCode
|
|
from hub_platform.subscriptions.models import PlanVersion
|
|
from hub_platform.subscriptions.plan_service import publish_plan_version
|
|
|
|
|
|
def create_platform_operator(
|
|
*, name: str = "Test operator", capabilities: list[str] | None = None
|
|
) -> tuple[PlatformOperator, str]:
|
|
caps = (
|
|
["platform.organizations.provision"]
|
|
if capabilities is None
|
|
else capabilities
|
|
)
|
|
for code in caps:
|
|
assert is_valid_platform_capability(code), f"unknown capability {code}"
|
|
operator = PlatformOperator.objects.create(name=name)
|
|
_token, plaintext = issue_platform_token(operator=operator, name="default", capabilities=caps)
|
|
return operator, plaintext
|
|
|
|
|
|
def published_plan_version(plan_code: str = PlanCode.STARTUP) -> PlanVersion:
|
|
version = PlanVersion.objects.get(plan__code=plan_code, version=1)
|
|
return publish_plan_version(version)
|