Three Portals. Two Databases. One Admin.
The OA portal ecosystem consists of three separate applications, each serving a different audience. A Client Portal for business owners, an Employee Portal for OA internal staff, and an Admin Portal that Brad uses to orchestrate both systems.
All four AI brains (Sterling, Claude, ChatGPT 5.5, reconciliation) converged on the same core architecture: two completely separate D1 databases, managed auth via Clerk, tenant-scoped client data, and one admin orchestrator bridging both systems.
Employee DB and Client DB are completely separate D1 databases. A breach of the client surface physically cannot reach payroll, health insurance, or HR data.
One shared client database with
company_id on every table. Every query goes through TenantScopedDB middleware. Cross-client data leaks are structurally impossible.Free up to 10K MAU, SOC 2 certified. JWT-based identity. Authentication is not authorization... server-side enforcement on every request.
Client Portal and Employee Portal have different navigation, data sources, permission models, and evolution trajectories. They're separate products operated by the same company.
Each portal gets its own subdomain on outsourceaccess.com. No pages.dev URLs for production. Cloudflare Workers handle routing.
BINDING_CLIENT to oa-client-db and BINDING_EMPLOYEE to oa-employee-db. Routes: /api/admin/client/* hits client DB, /api/admin/employee/* hits employee DB.What Business Owners See
Each OA client company gets a login where the business owner (and optionally their team) can view their VA's performance, satisfaction scores, industry resources, billing history, and submit feedback. Every client only sees their own data, enforced by TenantScopedDB middleware.
| URL | Status | What It Is | Password |
|---|---|---|---|
clients.outsourceaccess.com |
Demo | Current deployed project (SSV hardcoded demo data) | Custom email/password auth |
client.outsourceaccess.com |
No DNS | Desired singular subdomain (needs CNAME record) | -- |
oa-ecosystem.pages.dev/client-demo |
Demo | Active Armor Pest Control static mockup | oademo2026 (on main ecosystem page) |
oa-client-demo.pages.dev |
Demo | Acme Roofing & Construction static mockup | None (static page) |
client.outsourceaccess.com (singular). Currently only clients.outsourceaccess.com (plural) has a CNAME record. Need to add the singular CNAME on OA Cloudflare account.
Internal OA Staff Dashboard
Role-based views for OA's ~500 staff. Each role sees exactly what they need: AMs see their client portfolio, OMs see team performance, TLs see their VAs, and VAs see daily tasks and time tracking. Sensitive HR, payroll, and benefits data lives exclusively in the employee database.
- Client portfolio overview with health scores
- CSAT trends and satisfaction alerts
- Client feedback and action items
- Strategic advisory document access
- Upsell/cross-sell opportunity tracking
- Team performance dashboards
- TL accountability tracking
- Client escalation queue
- Resource allocation and capacity
- Quality assurance metrics
- VA assignment management
- Daily check-in tracking
- Time Doctor summaries per VA
- Weekly update submission forms
- Performance coaching notes
- Daily task assignments
- Time tracking integration
- Client-specific SOPs and playbooks
- Communication logs
- Training resources
- Company-wide KPI dashboard
- Revenue and retention metrics
- Employee performance rankings
- Client health overview
- Payroll and financial summaries
| URL | Status | What It Is | Notes |
|---|---|---|---|
server.outsourceaccess.com |
Demo | CNAME points to same project as clients.* | Same oa-client-dashboards project |
oa-ceo-dashboard.pages.dev |
Live | CEO business intelligence dashboard (standalone) | Password: OA2026 |
ceo-financial-dashboard.pages.dev |
Live | CEO financial dashboard (standalone) | Password: OA2026 |
server.outsourceaccess.com points to the same Cloudflare Pages project as clients.outsourceaccess.com. Per the architecture decision, it needs a separate Worker with its own D1 binding to the employee database.
Brad's Orchestrator
The admin portal is NOT a third database. It's a single Worker with dual D1 bindings that lets Brad (and authorized OA admins) manage both the client and employee systems from one interface. Think of it as the control room.
/api/admin/client/*/api/admin/employee/*export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname.startsWith('/api/admin/client/'))
return handleClientRoute(request, env.BINDING_CLIENT);
if (url.pathname.startsWith('/api/admin/employee/'))
return handleEmployeeRoute(request, env.BINDING_EMPLOYEE);
}
};
| URL | Status | Notes |
|---|---|---|
admin.outsourceaccess.com |
No DNS | Needs CNAME record + dedicated Worker |
oa-client-dashboards.pages.dev/admin/ |
Demo | Current admin route on shared project (basic user management UI) |
Every data source that feeds into the portal ecosystem, mapped to which portal(s) consume it and how data flows.
Client company records, VA assignments, and contact info sync from HubSpot into oa-client-db. HubSpot remains the source of truth for CRM data. Scheduled sync via Workers Cron.
Productivity data pulled via Time Doctor API. Client-facing summary goes to oa-client-db. Detailed per-VA breakdown goes to oa-employee-db for TL/OM review.
Survey responses land in oa-client-db tagged by company_id. Visible to the client on their dashboard and to the AM on the employee portal.
Payment events (invoice paid, subscription updated) flow into oa-client-db. Clients see billing status. Admin manages plan changes.
Authentication Is Not Authorization
Clerk handles authentication (who are you?). The Workers handle authorization (what can you see?). Separate logins don't prevent data leaks... server-side enforcement on every request, with identity derived from cryptographically signed JWTs, is what prevents leaks.
user_id, role, company_id (for client users), org_id (for employees)| Role | Portal | Can See | Can Edit |
|---|---|---|---|
| Client Owner | Client | Own company VA perf, CSAT, billing, resources | Feedback, settings |
| Account Manager | Employee | Assigned clients' data, CSAT, strategic docs | Client notes, advisory docs |
| Operations Manager | Employee | All TLs under them, team metrics, escalations | Team assignments, escalation responses |
| Team Leader | Employee | Assigned VAs, time tracking, daily check-ins | VA notes, weekly updates |
| Virtual Assistant | Employee | Own tasks, time logs, SOPs | Task status, time entries |
| CEO / Admin | Admin | Everything across both databases | Everything (user creation, role changes, content push) |
class TenantScopedDB {
constructor(db, companyId) {
this.db = db;
this.companyId = companyId;
}
query(sql, ...params) {
// Automatically injects WHERE company_id = ?
const scoped = injectTenantScope(sql, this.companyId);
return this.db.prepare(scoped).bind(...params, this.companyId).all();
}
}
company_id filter. Cross-client data leaks become structurally impossible at the code level.Brad/Admin Creates Client Company
In admin portal, creates a new company record in oa-client-db with company name, industry, plan tier, assigned AM.
Admin Invites Client Owner
Enters client owner's email. Clerk sends an invitation email with a secure signup link to client.outsourceaccess.com.
Client Owner Completes Signup
Client clicks link, sets password via Clerk. Their Clerk user is automatically linked to their company_id in the client DB.
Client Accesses Dashboard
On login, JWT contains their company_id. All queries are automatically scoped. They only see their own VA performance, CSAT, billing.
HR/Admin Creates Employee Record
In admin portal, creates employee in oa-employee-db with name, role (AM/OM/TL/VA), reporting structure, start date.
Admin Assigns Role & Permissions
Sets the employee's role which determines their view on server.outsourceaccess.com. Assigns to specific clients/teams as needed.
Clerk Invitation Sent
Employee receives signup email. Creates account via Clerk. Their Clerk user is linked to their employee record and role.
Employee Logs In
JWT contains role and org_id. Employee portal renders the appropriate role-based view automatically. AMs see clients, TLs see VAs, etc.
Brad Logs In
Brad authenticates at admin.outsourceaccess.com via Clerk. His JWT contains role: "admin" with full privileges.
Admin Worker Validates
Worker verifies JWT, checks admin role. If not admin, returns 403. No client or regular employee can access admin routes.
Dual Database Access
Admin Worker has BINDING_CLIENT and BINDING_EMPLOYEE. Brad can query, create, update, and delete records in either database from one interface.
Every URL in the OA portal ecosystem with current deployment status, login credentials, and purpose.
| URL | DNS | Status | Purpose | Auth |
|---|---|---|---|---|
client.outsourceaccess.com |
Missing | Planned | Client-facing portal (singular, preferred) | Clerk (planned) |
clients.outsourceaccess.com |
Active | Demo | Currently deployed, SSV demo data | Custom email/password (PBKDF2) |
server.outsourceaccess.com |
Active | Demo | Points to same project as clients.* | Same as clients.* |
admin.outsourceaccess.com |
Missing | Planned | Brad's admin orchestrator | Clerk (planned) |
| URL | Account | Status | Purpose | Password |
|---|---|---|---|---|
oa-client-dashboards.pages.dev |
OA Team | Demo | Main portal project (login + dashboard + admin routes) | Custom email/password |
oa-portal-architecture.pages.dev |
Brad Personal | Reference | Four-brain architecture decision document | None |
oa-ceo-dashboard.pages.dev |
Brad Personal | Live | CEO business intelligence dashboard | OA2026 |
ceo-financial-dashboard.pages.dev |
Brad Personal | Live | CEO financial dashboard | OA2026 |
oa-client-demo.pages.dev |
Brad Personal | Demo | Acme Roofing & Construction static mockup | None (static page) |
oa-dash-directory.pages.dev |
OA Team | Live | This page (master architecture map) | None |
| URL | Status | Purpose | Password |
|---|---|---|---|
oa-ecosystem.pages.dev |
Live | Strategic planning tool for managers (NOT a portal dashboard) | OA2026 |
oa-ecosystem.pages.dev/client-demo |
Demo | Active Armor Pest Control static mockup (lives under ecosystem) | oademo2026 (ecosystem gate) |
capcon-dashboard.pages.dev |
External | Built by Alva (Philippines team) using Claude co-work, not Sterling | Unknown |
clients.outsourceaccess.com and server.outsourceaccess.com both point to the same Cloudflare Pages project (oa-client-dashboards) with one D1 database. The architecture requires separate Workers with separate D1 bindings.oa-client-dashboards-db). Architecture requires a separate oa-employee-db for payroll, HR, benefits, and team structure data. Physical separation is the security guarantee.client.outsourceaccess.com (singular) and admin.outsourceaccess.com have no CNAME records. Need to be created on OA Cloudflare.clients.outsourceaccess.com has "Support Services of Virginia" hardcoded. Needs to be replaced with dynamic data pulled from D1 based on the authenticated user's company_id.Sequenced build plan based on the four-brain architecture consensus. Each phase can be built and deployed independently.
oa-employee-db D1 database on OA Cloudflareclient.outsourceaccess.comadmin.outsourceaccess.comclient.outsourceaccess.com (singular) as primary domainserver.outsourceaccess.com to new Workeradmin.outsourceaccess.com