Status: ⏸️ DEPRIORITIZED (Awaiting User Decision)
Estimated Effort: 2-4 hours
Risk Level: HIGH (breaking changes to existing admin functionality)
Priority: P3 (Nice-to-have, not critical for Phase 8)
Add tenant-scoped filtering to the admin module so that CRUD operations automatically respect tenant boundaries in multi-tenant applications.
Current State: Admin module has no tenant awareness
Desired State: Admin module respects tenant context
File: libs/admin/src/boundary/admin/shell/service.clj
Changes Needed:
;; BEFORE (current)
(defn list-entities [this entity-name options]
(let [query (build-query entity-name options)]
(db/execute! db-ctx query)))
;; AFTER (with tenant filtering)
(defn list-entities [this entity-name options]
(let [tenant-id (:tenant-id options) ; Extract from options
query (build-query entity-name options)
; Add WHERE tenant_id = ? clause
tenant-query (if tenant-id
(assoc query :where [:and (:where query) [:= :tenant-id tenant-id]])
query)]
(db/execute! db-ctx tenant-query)))
Impact:
File: libs/admin/src/boundary/admin/shell/http.clj
Changes Needed:
;; Add tenant middleware integration
(defn list-entities-handler [admin-service]
(fn [request]
(let [entity-name (get-in request [:path-params :entity])
tenant-id (get-in request [:tenant :id]) ; From tenant middleware
options (merge (parse-query-params request)
{:tenant-id tenant-id})]
(list-entities admin-service entity-name options))))
Impact:
File: libs/admin/src/boundary/admin/core/ui.clj
Changes Needed (Optional):
;; Add tenant indicator to header
(defn render-admin-header [tenant]
[:header.admin-header
[:div.tenant-info
[:span.tenant-label "Current Tenant: "]
[:span.tenant-name (:name tenant)]]
; ... existing header content
])
;; Super-admin: Add tenant selector dropdown
(defn render-tenant-selector [tenants current-tenant-id]
[:select {:name "tenant-id"
:hx-get "/web/admin"
:hx-target "#main-content"}
(for [tenant tenants]
[:option {:value (:id tenant)
:selected (= (:id tenant) current-tenant-id)}
(:name tenant)])])
Impact:
File: libs/admin/src/boundary/admin/shell/schema_repository.clj
Changes Needed (PostgreSQL only):
(require '[boundary.tenant.shell.provisioning :as provisioning])
(defn fetch-table-metadata [this table-name]
(if-let [tenant-schema (:tenant-schema context)]
; Query from tenant schema
(provisioning/with-tenant-schema db-ctx tenant-schema
(fn [tenant-ctx]
(introspect-table tenant-ctx table-name)))
; Query from public schema (non-tenant)
(introspect-table db-ctx table-name)))
Impact:
File: resources/conf/dev/config.edn
New Configuration:
:boundary/admin
{:enabled? true
:base-path "/web/admin"
:require-role :admin
; NEW: Tenant configuration
:multi-tenant? true ; Enable tenant filtering
:tenant-mode :single-tenant ; :single-tenant or :super-admin
; :single-tenant - User sees only their tenant
; :super-admin - User can switch tenants via UI
:entities {...}}
Impact:
false);; Test tenant filtering in service layer
(deftest test-list-entities-with-tenant-filter
(let [tenant-a-records (create-test-records :users tenant-a 3)
tenant-b-records (create-test-records :users tenant-b 2)]
; List with tenant-a context
(let [result (list-entities service :users {:tenant-id tenant-a})]
(is (= 3 (count (:records result))))
(is (every? #(= tenant-a (:tenant-id %)) (:records result))))
; List with tenant-b context
(let [result (list-entities service :users {:tenant-id tenant-b})]
(is (= 2 (count (:records result))))
(is (every? #(= tenant-b (:tenant-id %)) (:records result))))))
Coverage:
Total: ~18 new tests, ~120 assertions
;; Test HTTP endpoints with tenant context
(deftest test-admin-list-entities-tenant-isolation
(with-test-tenants [tenant-a tenant-b]
(with-test-data :users [{:tenant-id tenant-a :name "Alice"}
{:tenant-id tenant-b :name "Bob"}]
; Request as tenant-a user
(let [response (app (-> (request :get "/web/admin/users")
(assoc-in [:tenant :id] tenant-a)))]
(is (= 200 (:status response)))
(is (= 1 (count-users-in-response response))))
; Request as tenant-b user
(let [response (app (-> (request :get "/web/admin/users")
(assoc-in [:tenant :id] tenant-b)))]
(is (= 200 (:status response)))
(is (= 1 (count-users-in-response response)))))))
Coverage:
Total: ~13 new tests, ~80 assertions
tenant-id parameter to all service methodsLikelihood: HIGH
Impact: CRITICAL
Mitigation:
multi-tenant? false (backward compatible)Likelihood: MEDIUM
Impact: MEDIUM
Issue: Extra WHERE clause on every query
Mitigation:
tenant_id column is indexedLikelihood: LOW
Impact: CRITICAL
Issue: Bug in filtering logic allows cross-tenant access
Mitigation:
Likelihood: MEDIUM
Impact: MEDIUM
Issue: Tenant selector adds complexity to admin UI
Mitigation:
Pros:
Cons:
Recommendation: Start with this for Phase 8
Pros:
Cons:
Recommendation: Add in Phase 9 (post-MVP)
Pros:
Cons:
Recommendation: Add only if strong user demand
Questions for User:
Is admin tenant integration critical for Phase 8?
What is the admin use case?
What is the timeline?
What is the risk tolerance?
KEEP DEPRIORITIZED for Phase 8 because:
✅ Not Blocking: Multi-tenancy works without admin integration
✅ High Risk: Breaking changes to critical admin module
✅ Alternative Exists: Tenant-scoped database access
with-tenant-schema for manual admin queries✅ Better in Phase 9: After E2E tests stabilized
Checklist Before Starting:
First Steps:
feature/admin-tenant-integrationDocument Version: 1.0
Date: 2026-02-09
Author: Boundary AI Agent
Status: Awaiting user decision on whether to proceed
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |