Audit only, no code changed - every fact gathered by reading current facades/gateways/components on this branch, not assumed. Covers all 13 admin modules (Dashboard, Products, Categories, Orders, Customers, Users, Analytics, Reviews/Moderation, Media, CMS, Builder, Settings, Monitoring, Transactions). Per module: answers the 3 readiness questions (does Marketplace Owner see everything / would Seller see only their own / would Seller Staff be limited), documents where a future scope would be injected (an existing method/interface parameter to extend - no "if seller" checks introduced anywhere), lists components that currently assume global ownership, and classifies Ready / Needs scope / Needs permissions / Needs API change. Key findings: - Only 3 of 13 gateways (Categories, Dashboard-metrics, Media) are DI-token-swappable today; everything else needs that seam added first, independent of seller scoping. - Orders is the load-bearing blocker: Customers, Transactions, and half of Analytics all derive from its same unscoped full-fetch order list, and AdminOrderItem has no per-item seller attribution at all - the concrete gap behind Seller-Management.md's open Unified-vs-Split- Orders question. - Users already carries an AdminUserScope/AdminRole concept (label- only today) - the natural future home for the Marketplace Owner/ Seller/Seller Staff/Platform Admin role vocabulary. - CMS/Static Pages and Builder/Project Editor are structurally not about data scoping at all (marketplace-wide content, single global config document respectively) - seller-level work there is new product surface, not an extension. - No admin module anywhere does role-based hiding of buttons or data today - confirmed, not assumed. Linked from docs/architecture/foundation/README.md alongside the other Seller Management docs.
16 KiB
Seller Management — Backoffice Readiness Audit
Audit only. No code changed by this pass — every fact below was gathered
by reading current source (facades, gateway interfaces, local
implementations, components) on feature/seller-management-foundation, not
inferred or assumed. Companion to
Seller-Management.md §5 (Roles & Permissions) and §6
(Seller Ownership).
How to read this
For each module: three readiness questions, then where a future scope would be injected (not "if seller" conditionals — an injection point in an existing method signature or facade call), then components that currently assume there is exactly one owner of the whole dataset, then a classification.
Classification legend:
- Ready — either already scope-injectable with no structural change, or conceptually marketplace-wide data that a seller scope shouldn't apply to at all.
- Needs scope — a filters object or DI-token seam already exists; adding a scope field is additive, not structural.
- Needs permissions — the open question is who sees this at all (Marketplace Owner vs Seller vs Seller Staff vs Platform Admin), not data filtering.
- Needs API change — the method signature itself has no parameter to extend (bare no-arg calls), the data model has no owner attribution field to filter on, or the module's whole premise assumes one global record.
Repo-wide baseline established by this audit: only Categories, Dashboard-
metrics, and Media have a DI-token-swappable gateway today (per
BACKEND.md §8's pattern — interface + *LocalGateway + *ApiGateway +
InjectionToken). Every other admin domain's facade injects its
*LocalGateway concretely; that seam has to be added before any real
scoping work lands, independent of the scoping question itself. No module
currently does role-based hiding of any button or data — Users displays
role/permission labels only, nothing gates on them.
Module-by-module
Dashboard
- Owner sees everything? Yes — today's only mode.
- Seller sees only their own? Not possible today —
loadMetrics()has no parameters at all. - Seller Staff limited? Undetermined — no permission model touches this yet.
- Injection point:
AdminDashboardMetricsGateway.loadMetrics()would need a new parameter (interface change, not a filters-object field — there is no object to extend). - Assumes global ownership:
admin-dashboard-metrics.local.gateway.tscomputescategoriesCount/productsCountas raw.lengthover the entire catalog. - Classification: Needs API change.
Products
- Owner sees everything? Yes.
- Seller sees only their own? Not yet, but the shape is close — filters object already exists.
- Seller Staff limited? Undetermined.
- Injection point:
AdminProductListFilters(already hassearch/categoryId/visibility/stock/includeArchived/sort/page/pageSize) — an optionalsellerIdfield slots in next to the existing ones; one more.filter()line inAdminProductsLocalGateway. - Assumes global ownership:
loadDashboardStats()callsloadProducts({page:1, pageSize:100000, includeArchived:true, ...})to sum stock/health stats with no per-seller split. - Classification: Needs scope (small, once the DI-token seam this module still lacks is added — see baseline note above).
Categories
- Owner sees everything? Yes.
- Seller sees only their own? Categories are conceptually a shared, marketplace-wide taxonomy — the real future question is "which products in category X belong to seller Y," not "which categories belong to seller Y." Likely Owner-only editable regardless of seller scope.
- Seller Staff limited? Undetermined.
- Injection point:
AdminCategoryListFiltersalready hassearch/visibility/includeDeletedand the gateway is already DI-token- swappable (ADMIN_CATEGORIES_GATEWAY) — but the facade'sloadList()currently calls the gateway with a hardcoded{search:'', visibility:'all', includeDeleted:true}and does all real filtering client-side infilteredCategories()/visibleTreeRows()to preserve tree parent/child chains. AsellerIdfilter passed to the gateway would be silently bypassed unless this hardcoded call is updated too. - Assumes global ownership:
loadDashboardStats()sums the whole category tree. - Classification: Needs scope — gateway/interface layer is Ready, the facade's full-fetch-then-client-filter pattern is the actual gap.
Orders
- Owner sees everything? Yes.
- Seller sees only their own? Not modeled at all —
AdminOrderItemhas no seller/vendor attribution field; a multi-vendor order (one order, items from several sellers) has no representation today. This is the concrete blocker behind the Unified-vs-Split-Orders open question inSeller-Management.md§6. - Seller Staff limited? Undetermined.
- Injection point:
AdminOrderListFiltershassearch/status/page/ pageSize— asellerIdfield is syntactically cheap to add, but filtering by it means nothing until orders/order-items carry seller attribution in the data model. - Assumes global ownership:
loadDashboardStats()fetchespageSize:100000and sums orders/revenue/customers globally; this same full-fetch feeds Customers, Transactions, and Analytics (see below), compounding the single-owner assumption across four modules. - Classification: Needs API change — the data-model gap (per-item seller attribution, unified-vs-split decision) is the real blocker, not the filter object.
Customers
- Owner sees everything? Yes.
- Seller sees only their own? Not possible today — "customer" is a derived aggregate grouping the full order list by email in-memory; there is no first-class Customers gateway to add a filter to at all.
- Seller Staff limited? Undetermined.
- Injection point: none exists yet. Either (a) derive from an
already-scoped Orders call once Orders itself supports
sellerId, or (b) introduce a first-classAdminCustomersGateway—BACKEND.mdalready recommends the latter independent of Seller Management. - Assumes global ownership:
buildCustomers()groups the entire unscoped order list; hardcodedpageSize:100000fetch, "search" is client-side only. - Classification: Needs API change.
Users
- Owner sees everything? Yes.
- Seller sees only their own? Not modeled —
loadUsers()is bare no-arg, no filters object exists to extend. - Seller Staff limited? This is where the answer will actually live —
AdminUseralready carries anAdminUserScopefield ('marketplace' | 'office'in seed data) and anAdminRolewith a permission-array shape. This is the closest existing hook to the futureSellerPermissionRolevocabulary (marketplaceOwner/seller/sellerStaff/platformAdmin,Seller-Management.md§5) — but today it's used for labels only (roleLabel/permissionLabelin the page component), nothing gates actions or visibility on it anywhere in the app. - Injection point:
loadUsers()needs a parameter added (interface change — no object to extend);AdminUserScopeis the natural place a seller-scope value would eventually live. - Assumes global ownership:
loadAll()fetches every user unconditionally, no per-seller user set exists. - Classification: Needs API change (data fetch) + Needs permissions (this module is the eventual home of the Marketplace Owner / Seller / Seller Staff / Platform Admin distinction — right now it's purely informational).
Analytics
- Owner sees everything? Yes.
- Seller sees only their own? Not possible today — every number (revenue, top products, health, recommendations) is summed across the entire orders+products+categories+reviews corpus with no per-owner dimension anywhere, and there's no gateway of its own to add a filter to (it composes five other gateways/facades directly).
- Seller Staff limited? Undetermined.
- Injection point: none today — depends entirely on Orders/Products/
Categories/Moderation each supporting
sellerIdfirst, then every one of this facade's ~6 nested subscribe calls would need the field passed through, plus every aggregate builder (buildSeries,buildTopProducts,buildCustomerAnalytics) reworked to partition by seller instead of summing globally. - Assumes global ownership: the strongest case in the audit — literally every displayed number.
- Classification: Needs API change —
BACKEND.mdalready independently flags this as the last domain to get a real backend; Seller Management scoping compounds on top of that, not ahead of it.
Reviews / Moderation
- Owner sees everything? Yes.
- Seller sees only their own? Reviews carry
productId/productName— a seller would see reviews on their own products, which means joining through product ownership (once Products hassellerId), not a direct seller field on the review itself. Reports (loadReports()) has no filters object at all today, separate code path from reviews. - Seller Staff limited? Undetermined.
- Injection point:
AdminReviewListFilters(search/status/rating/page/pageSize) — cheap field add, correctness depends on a product-ownership join.loadReports()needs a signature change first (no params exist). - Assumes global ownership:
loadDashboardStats()sums the full review queue withpageSize:100000, no per-product/per-seller split. - Classification: Needs scope (reviews, small, blocked on Products) + Needs API change (reports, no params today).
Media
- Owner sees everything? Yes.
- Seller sees only their own? Not modeled —
MediaAssethas no uploader/owner field at all (filename/mimeType/size/tags/folderonly); "folder" is the closest existing scoping primitive, used generically today. - Seller Staff limited? Undetermined.
- Injection point:
MediaListParams(page/pageSize/search/folder/ kind/sort) is already a rich optional-params object — asellerIdfield is a cheap addition, and the repository is already DI-token-bound (MediaRepositoryabstract class, mock vs API swap already established).MediaAssetitself needs an owner field added for the filter to mean anything. - Assumes global ownership: none beyond the missing owner field itself.
- Classification: Needs scope (small — best-positioned module in the audit alongside Categories).
CMS / Static Pages
- Owner sees everything? Yes — and likely always will.
- Seller sees only their own? Conceptually doesn't apply. Static pages
(legal, about, etc.) are inherently marketplace-wide; there is no
per-seller "static page" concept in the domain, and no fetch method
exists to add a filter to in the first place — this module reads/writes
BootstrapConfig.staticPagesin-memory, no gateway, no HTTP call (confirmed byBACKEND.md: "has no backend call today"). - Seller Staff limited? Not applicable.
- Injection point: none — would require inventing an entirely new data source, not adding a filter to an existing one.
- Assumes global ownership: the whole module's premise, but appropriately so — this is marketplace-wide content by nature.
- Classification: Ready — no scoping work belongs here; flag if product strategy later decides sellers need their own static pages, which is a new capability, not a gap in this one.
Builder / Project Editor
- Owner sees everything? Yes — the entire module edits one global
BootstrapConfigdocument. - Seller sees only their own? Not modeled, and not a filter question at
all — there is no
load*/list*gateway method anywhere in this module to extend;save()/publish()/resetDraft()all operate on the single in-memory config object, with no backend write path yet either (BACKEND.md: "no client write call exists today"). - Seller Staff limited? Not applicable at this structural level.
- Injection point: none exists. A seller-scoped builder (per-seller storefront layout/branding) would be a different product concept, not an extension of this module's current single-document model.
- Assumes global ownership: total and structural — the single strongest one-owner assumption in the codebase.
- Classification: Needs API change — biggest structural gap in the audit if seller-level storefront customization is ever wanted; this is new work, not scoping.
Settings
No route exists — admin-nav.model.ts marks it comingSoon: true, no
component backs it. Skipped, nothing to audit.
Monitoring
- Owner sees everything? Yes.
- Seller sees only their own? Likely shouldn't — events/queues/webhooks (logins, API health, queue depth) are platform-operational data. A seller has no legitimate reason to see other users' login events or system queue depth regardless of any future scoping.
- Seller Staff limited? Same reasoning — this looks like a Marketplace Owner / Platform Admin-only surface once roles exist, not something a Seller role should reach at all.
- Injection point:
AdminMonitoringEventFilters(category/search) exists for events;loadQueues()/loadWebhooks()are bare no-arg. - Assumes global ownership: appropriately so — this is genuinely system-wide data.
- Classification: Needs permissions — the open question is route-level visibility per role, not data filtering.
Transactions
- Owner sees everything? Yes.
- Seller sees only their own? Not modeled — transactions are derived 1:1 from the same unscoped global order list as Customers, inheriting the same seller-attribution gap.
- Seller Staff limited? Undetermined.
- Injection point:
AdminTransactionListFilters(search/status/type/page/pageSize) exists — cheap field syntactically, but real correctness is blocked on Orders resolving per-item seller attribution first. - Assumes global ownership: derives wholesale from
AdminOrdersLocalGateway, same pattern as Customers. - Classification: Needs scope (small syntactically, blocked on Orders' Needs API change classification for real correctness).
Summary table
| Module | Classification |
|---|---|
| Dashboard | Needs API change |
| Products | Needs scope |
| Categories | Needs scope |
| Orders | Needs API change |
| Customers | Needs API change |
| Users | Needs API change + Needs permissions |
| Analytics | Needs API change |
| Reviews / Moderation | Needs scope (reviews) + Needs API change (reports) |
| Media | Needs scope |
| CMS / Static Pages | Ready (not applicable) |
| Builder / Project Editor | Needs API change |
| Settings | N/A — no route |
| Monitoring | Needs permissions |
| Transactions | Needs scope (blocked on Orders) |
Nothing in this repo is currently classified "Ready" for actual seller data scoping — CMS/Static Pages is "Ready" only in the sense that it correctly needs no scoping at all. Categories and Media are the best-positioned modules for a future scope field (filters object + DI seam either fully or mostly in place already). Orders is the load-bearing blocker — Customers, Transactions, and half of Analytics all derive from it, so its data-model gap (no per-item seller attribution, unified-vs-split undecided) should be resolved before scoping any of its three dependents.
Cross-cutting findings
- No admin module anywhere does role-based hiding of buttons or data
today. Users is the only module with a role concept in its data
(
AdminRole, permission arrays) and even there it's label-only. - Only 3 of 13 audited gateways are DI-token-swappable today (Categories, Dashboard-metrics, Media) — everything else needs that seam added before any scoping work, independent of Seller Management.
- The heaviest single dependency chain: Orders → Customers, Transactions, and Analytics all derive from the same unscoped, full-fetch order list. Fixing Orders' data model is the one change with the largest downstream effect.
- Two modules are structurally not about data scoping at all: CMS/ Static Pages (marketplace-wide by nature) and Builder/Project Editor (single global document, no query surface) — seller-level work here would be new product surface, not an extension.
- No "if seller" conditional exists anywhere in the codebase — this audit deliberately did not introduce any. Every injection point above is described as a parameter/field addition to an existing method or interface, never a runtime branch.