loopable / platform /harness /tenants.py
fsanyoto's picture
Deploy AIOS web (React glide grid + FastAPI slice)
c14ceee verified
Raw
History Blame Contribute Delete
4.25 kB
"""Tenant definitions β€” one entry per client of the platform.
Royal Imports is the first tenant. Everything that was HARD-CODED in core/odoo.py (teams 5/6, the
GIFTWARE house account, the sale/done statuses) moves here as per-client CONFIG. A second client is
just another Tenant with its own connectors + config β€” the skills and UI don't change.
In production this comes from a tenants store (DB / HF dataset), not a literal; kept literal here so
the pivot is legible and runnable today.
"""
from __future__ import annotations
from harness.base import Tenant
from harness.canonical import Capability
from harness.connectors.odoo import OdooConnector
def royal_imports(doc_mode: str = 'order') -> Tenant:
odoo = OdooConnector(doc_mode=doc_mode)
return Tenant(
key='royal-imports', name='Royal Imports',
sources={odoo.key: odoo},
config={
'business_units': {'Fisch': 5, 'Royal': 6}, # canonical BU -> Odoo team_id
'confirmed_statuses': ['sale', 'done'],
'excluded_customer_ids': OdooConnector.excluded_customer_ids(), # GIFTWARE house account
'channels': {'Amazon': 'GIFTWARE DEALS'}, # marketplace split (see Amazon channel memory)
'branding': {'navy': '#1F4E78', 'gold': '#C8A24B', 'name': 'Royal Imports'},
})
class _EmptyConnector:
"""A source that answers every canonical query with NOTHING β€” the X8 isolation fixture.
Deliberately NOT a mock library object and NOT a subclass shortcut around `aggregate`: the
isolation proof has to run against the SAME code path a real tenant uses, so this answers the
real interface and returns real (empty) results. A test double that skipped the interface
would prove the double is isolated, not that the runtime is.
An EMPTY pool is the strongest possible fixture for the assertion that matters: tenant B's
`/customers` must return ZERO rows. Any row at all in that response came from somewhere it
should not have, and there is no ambiguity about whose it is.
"""
key = 'empty'
name = 'Empty (QA)'
def capabilities(self) -> set:
return set()
def aggregate(self, q) -> list[dict]:
return []
def records(self, q) -> list[dict]:
return []
def distinct_count(self, q, fieldname: str) -> int:
return 0
def supports(self, cap: Capability) -> bool:
return False
def entities(self) -> set:
return set()
def qa_tenant_b() -> Tenant:
"""Tenant 'qa-b' β€” the SECOND tenant the isolation proof needs, with no Odoo behind it.
D-2's assertion is "tenant B cannot reach tenant A", and that cannot be tested with one
tenant configured. This is the minimum second tenant: its own slug (so its own store
namespace and its own DuckDB path), no credentials, and a connector that returns nothing.
⚠ NOT registered by default. `harness.runtime` only admits it when `AIOS_ENABLE_QA_TENANT=1`,
so a production deployment has exactly one tenant and an attacker cannot mint a session
against a fixture. (X8 says "the registry gains qa-b"; gating it is an additive safety
amendment, recorded in the S1 mailbox 2026-07-30 β€” the isolation MECHANISM under test, the
store namespace and the cookie's tenant claim, is identical either way.)
"""
empty = _EmptyConnector()
return Tenant(
key='qa-b', name='QA Tenant B',
sources={empty.key: empty},
config={
'business_units': {},
'confirmed_statuses': [],
'excluded_customer_ids': [],
'channels': {},
'branding': {'name': 'QA Tenant B'},
})
# Example of a *different-shape* client (accounting-only), to show the harness adapts. Uncomment and
# wire real QBO creds to light up ledger/AR/customer skills β€” and NOT the SKU/inventory ones.
# def demo_qbo_client() -> Tenant:
# from harness.connectors.quickbooks import QuickBooksConnector
# qb = QuickBooksConnector(realm_id='...', token='...')
# return Tenant(key='demo-qbo', name='Demo (QuickBooks)', sources={qb.key: qb},
# config={'business_units': {}, 'confirmed_statuses': [], 'excluded_customer_ids': []})