Tenant Controller — domain resolution schema (abridged)¶
Reverse-engineered, and out of our ownership —
nova-tenant-controllerowns this database; we document only the slice Super Agent depends on for domain resolution, not the full schema. Passed the schema-first gate for that slice.
Diagram¶
erDiagram
INSTANCES ||--o{ TENANTS : "hosts"
TENANTS {
string tenant_id PK
string instance FK
string tenant_name
string sys_domain
string user_domains "::-delimited email domains, LIKE-matched"
boolean is_active
}
INSTANCES {
string sn_instance PK
string encryption_key_id
string sn_jwt_provider_sys_id
}
resolve_tenant(email) in nova-a2a-adapter splits email to get a domain,
matches it against TENANTS.user_domains, then follows TENANTS.instance
to INSTANCES.sn_instance to find the ServiceNow instance to call.
What resolves a user to a ServiceNow instance¶
tenants.user_domains (TEXT, ::-delimited, e.g. ::novaworks-ai.biz::)
is the field that maps an email domain to a tenant. Looked up via
get_tenant_by_user_domain() in nova-agent-utils:
Tenants.user_domains.like(f"%::{domain}::%")
tenant_id → instance (FK to instances.sn_instance) then resolves the
tenant to its ServiceNow instance. Read/write for user_domains goes
through tenant-controller's own updateTenant API — there's no separate
lookup endpoint there; the lookup query above lives entirely on the
consumer (Python) side.
The gap: we're not using hd¶
Nothing here needs to change. nova-a2a-adapter's resolve_tenant()
derives the domain by splitting the user's email address
(email.split("@")[-1]) instead of reading Google's verified hd
(hosted-domain) claim, which is already present in the validated JWT
payload. This is a caller-side fix in nova-a2a-adapter, not a schema
change — user_domains already supports arbitrary domains per tenant.
Note¶
Two systems can write this database independently (tenant-controller's TS
migrations, and a separate Python create_all() path in nova-agent-utils
for a couple of tables tenant-controller's migrations don't create). Since
we don't own this schema, that's flagged here only in case it ever affects
Super Agent's read path — not something for us to resolve.