Data model — Tenant · Principal · Email-Triage¶
Status: Draft · supports ADR-0017 (employees as principals) · Date: 2026-07-31
Diagram: docs/architecture/diagrams/abos-er.svg (source: abos-er.dot)
This document is the narrative companion to the ER diagram. It describes how an employee (Principal) and a triaged email are represented across the two planes, and — critically — which relationships are enforced foreign keys and which are logical references across a database boundary.
The two planes¶
Control plane — shared registry. Two writers only (Ansible bootstrap, BOS Console edits); the brain reads it read-only (control-plane-holds-the-map ADR, ≈0006 — verify number). Holds the map, not business content: Tenant, Role, Principal, MailboxBinding, Rule.
Tenant data plane — per-tenant Postgres, inside the tenant's own container (ADR-0014 §5–6). Holds business content, every row stamped principal_id, with RLS enforcing cross-employee isolation (ADR-0017). This plane goes with the client on exit (residency/exit ADR, ≈0008). Holds: Message, Classification, Draft, TriageFeedback, GenerationFeedback, VoiceExample, LearnedRuleCandidate.
Enforced FKs vs logical cross-plane references¶
The distinction the diagram encodes with solid vs dashed edges is load-bearing:
- Solid edges = real foreign keys within one database.
- Dashed edges = logical references that cross the plane boundary.
Message.principal_idandDraft.principal_id→Principal, andClassification.matched_rule_id→Rule, are not enforced DB foreign keys, because the registry and the tenant Postgres are separate databases in separate containers. The brain resolves them at runtime. Modelling them as enforced FKs would misrepresent the isolation architecture.
Consequently the registry's tenant → connection-string resolution is security-critical: a wrong mapping is a cross-tenant leak.
Entities of note¶
Principal— an employee, or a shared mailbox (kind = person | shared). Carriesrole_id,autonomy_level(L1 default), and its mailbox binding. Onboarding = create + assign role (inherits ruleset) + delegated OAuth consent.Rule— polymorphicscope_type(system | vertical | tenant | role | personal | learned) with nullablescope_ref. Lives in the control plane because rules are config. Precedence resolves in that order.Draft— the approval queue is the set ofstatus = pendingrows; there is no separate queue table (DB-first, clean workflow exit).approver_principal_idis a pointer: self-review by default, re-routable to a manager/rota without a schema change.Classification→TriageFeedbacktrains the shared role layer.Draft→GenerationFeedbacktrains only that principal's voice. The two channels are never conflated.VoiceExample— pgvector, per-principal, never pooled across employees.LearnedRuleCandidate— evidence aggregated in the tenant plane; promoted to an activeRulein the registry only on human approval (the "No review, no go" gate; also the insertion point for the curator / SkillOpt per ADR-0019).
Runtime path for one email¶
- A Graph change-notification (one subscription per employee mailbox) fires; the subscription id resolves to
(tenant, principal)via the registry and dispatches into that tenant's pipeline. - Capture pulls the message with the principal's token, composes the effective ruleset (
system → vertical → tenant → role → personal → learned), classifies, writes aprincipal-scoped row, decides promote / notify / ignore. - On promote, Generate retrieves only that principal's
VoiceExamples, drafts viallm-client, writes theDraftimmediately (DB-first), statuspending. - Gate: the principal's
autonomy_leveldecides — L1 → queue; higher → auto-send only if the client lifted that gate, with the authorisation recorded. Workflow terminates cleanly; the pending row is the queue. - Feedback on approve/edit splits: triage signal → role layer; edit-diff → that principal's voice corpus. Retrieval, not fine-tuning.
Open items (tracked)¶
- RLS session-principal mechanics: how the brain sets the Postgres session context per invocation so RLS binds.
- Graph subscription lifecycle: renewal and offboarding revocation.
- Confirm tenant compose layout: is the tenant's n8n in the same compose project / isolated network as its Postgres?