Status: accepted · Date: 2026-11-13 · Related: ADR-0003, ADR-0009
Enforcement: [test] instructor.procedures.test.ts (another Organization gets NOT_FOUND/CONFLICT, a non-Member FORBIDDEN) · [test] packages/db/src/rls-catalog.test.ts and db:verify-rls.
The template's tenant is the Organization, and every User gets a personal Organization at sign-up. BJJ Travel has two sides: an Instructor (one person) and an Academy (an owner plus staff).
Decision
- Travel Role is chosen once per Organization (
travel_role):instructororacademy(US-01). The Instructor's personal Organization holdsinstructor_profile,travel_window,visitandconsent_record; an Academy is an Organization with Members (S5). - The network (S2) lives there too:
contact,introduction(insert-only, no UPDATE grant) andintro_request(UPDATE only onstatus,answered_at). A Contact has no email or phone column (RN08). - All Instructor tables use
pgTable.withRLS+tenantPolicy; history tables (visit,consent_record) get no UPDATE grant. - Cross-side reads (an Academy sees published windows; an Instructor sees registered Academies) are deliberate tenant crossings through
asSystem(db, reason, fn), each with its ownSystemReason, and return only what the visibility rules allow.