La cadena de resolución
1
El middleware de Next lee el host
apps/web/src/middleware.ts extrae el slug del subdominio (o resuelve el dominio propio) y lo inyecta como header x-tenant-slug en la petición.2
El middleware de Hono carga el tenant
apps/web/src/server/middleware/tenant.ts lee ese header, busca la institución y la deja en el contexto: c.get('tenant') trae { id, slug, name, ... }.3
Se valida el acceso
tenantAccessMiddleware comprueba que el usuario de la sesión pertenezca a ese tenant. Sin eso, la petición no llega al handler.4
Cada consulta filtra por tenantId
El aislamiento es responsabilidad del código de acceso a datos. Una consulta sin
eq(tabla.tenantId, tenant.id) es una fuga entre instituciones.Probar en local
El ruteo por subdominio funciona igual en desarrollo:Source of Record: quién manda sobre cada dato
Cuando una institución ya tiene un SIS, EdTools no asume que es el dueño de la verdad. Cada tenant declara, por dominio de datos, quién es el maestro:
Y para cada uno, uno de tres modos:
edtools_master
EdTools es el dueño. Es el valor por defecto en instituciones nuevas, en los cuatro dominios.
external_master
Manda un sistema externo. Solo escribe el token que figura como ese maestro.
orchestrate_only
EdTools no es dueño ni acepta ser escrito: solo orquesta procesos sobre datos ajenos.
Cuando no pasa, la respuesta es
403 SOR_WRITE_FORBIDDEN con el dominio en el detalle. GET /v1/me te devuelve las dos mitades de la ecuación: sorByDomain (lo que declaró la institución) y masterDomains (lo que reconoce tu token).
Los dominios de SoR son cuatro, pero el Client API solo puede declarar maestro sobre tres:
students, enrollment y payments. grades se configura por SoR pero no se escribe por esta API.