0010. Enforce tenant isolation on sync rules¶
Date: 2026-07-27 Status: Accepted
Context¶
ADR-0005 validates a SyncRule against
the direction matrix — can the source export the domain, can the target receive it — but says
nothing about who owns either connection. Neither that decision nor the work that implemented
SyncRule considered what happens when a rule's source and target SystemConnections belong to
different Tenants. The direction matrix has no opinion on this: a source that can pull_in
orders and a target that can push_out orders satisfy ADR-0005 regardless of which tenant either
connection belongs to. Left unchecked, this would let one company's data be wired into another
company's connection — exactly the kind of cross-tenant leak the Tenant scope exists to prevent
(see Tenant Model — Function 2).
This surfaced during implementation review, not in the original design.
Decision¶
SyncRule's saving() hook rejects an active rule whose source and target connections resolve to
different tenant_ids, throwing InvalidSyncRuleDirectionException::forCrossTenantRule(). This
check runs alongside the ADR-0005 direction check, in the same hook, under the same bypass rule:
both checks only run when the row being saved has is_active = true, so a rule can be staged as an
inactive draft — even a cross-tenant one — without being rejected, and can always be safely
deactivated regardless of its current field values.
Tenant isolation is enforced at the model layer rather than deferred to a future Form Request or
policy, for the same reason the direction check is: there is no HTTP layer for SyncRule yet, and
whatever layer is added later should not be the only thing standing between a cross-tenant rule and
the database.
Consequences¶
A genuine future need to share data across tenants (if one ever arises) requires a deliberate new decision to relax this constraint — it will not happen by accident through an under-specified rule.