Platform Admin Operations Guide
This guide covers everything needed to access, operate, and extend the Platform Admin portal — the internal tooling used by the AssetGuard product team to manage master content that is shared across all tenants.
Audience: Product owners and developers with access to the service-account-file.json (for seeding scripts) and server access to run pnpm scripts. This is not tenant-facing.
What is the Platform Admin?
The Platform Admin is a separate layer of the application running at /platform-admin/*. It is completely isolated from the tenant RBAC system. A user with the owner role inside a tenant has no access here — and a platform admin has no special powers inside any tenant.
| Capability | Tenant Admin / Owner | Platform Admin |
|---|---|---|
| Manage their own tenant's policies | ✓ | — |
| Approve / reject tenant policies | ✓ | — |
| Author master policy templates (all tenants) | — | ✓ |
| Publish template versions and notify tenants | — | ✓ |
| Define template recommendation rules (NAICS × jurisdiction × flags) | — | ✓ |
The distinction is enforced at two levels:
- API level — every
/api/platform-admin/*route is wrapped inwithPlatformAdmin()(src/middleware/authMiddleware.ts), which verifies the Better Auth session and checks for theplatformAdmin: truefield on the Better Auth user record in Postgres. A missing or falsy field returns403. - UI level — the layout at src/app/platform-admin/layout.tsx checks the session on mount and redirects any visitor without the field to
/platform-admin/loginbefore the page renders.
Prerequisites
- Access to the service-account-file.json (for seeding scripts) and server access to run pnpm scripts.
- A Better Auth user account (email + password registered through the app or created via
auth.api.signUpEmail) that will act as the super-admin. This can be a dedicated account (recommended for production) or an existing developer account (acceptable for development). - The
service-account-file.jsonin the project root (required by the seeding scripts and the admin-grant script).
Admin Portals and Claims
All internal admin portals share a single login page (/platform-admin/login) and a common hub at /platform-admin. After sign-in the user lands on the hub, which shows only the areas their account can access.
| Portal | Custom claim | Hub card | Underlying route |
|---|---|---|---|
| Platform Admin — Policy Templates | platform_admin: true |
Policy Templates | /platform-admin/policy-templates |
| Platform Admin — Template Rules | platform_admin: true |
Template Rules | /platform-admin/template-rules |
| Platform Admin — Risk Questions | platform_admin: true |
Risk Questions | /platform-admin/risk-questions |
| HIPAA Admin | hipaa_admin: true |
HIPAA Admin | /hipaa-admin |
A single account can hold any combination of claims. The set:admin script (recommended) grants all claims at once. The individual set:platform-admin and set:hipaa-admin scripts remain available for targeted grants.
Granting Admin Access
Step 1 — Ensure the user account exists
The account must already exist in Better Auth (Postgres) before the field can be set. Register normally through the app at /register.
Step 2 — Run the claim script
From the project root (requires service-account-file.json):
Recommended — grant all admin claims at once:
pnpm set:admin <email>
Targeted grants (when you need to add only one claim):
pnpm set:platform-admin <email> # policy templates + rules only
pnpm set:hipaa-admin <email> # HIPAA admin only
Expected output (set:admin):
✓ Granted: platformAdmin, hipaaAdmin on admin@yourcompany.com
The user must sign out and sign back in for the changes to take effect.
How it works: Each script runs a direct Postgres UPDATE on the
usertable:UPDATE "user" SET "platformAdmin" = true WHERE email = $1. Running multiple scripts is safe — each sets only its own field.Adding a new portal: To add a new admin area in future, add its claim key to the
ADMIN_CLAIMSobject insrc/scripts/set-admin.tsand add a corresponding entry toADMIN_AREASinsrc/app/platform-admin/page.tsx.
Step 3 — Sign in
Navigate to /platform-admin/login. The login page is shared across all portals:
- Accepts email + password via Better Auth.
- After sign-in, the Better Auth session is validated on the server on each request.
- If neither
platformAdminnorhipaaAdminis set, signs the user back out and shows "This account does not have admin portal access." - If
hipaa_adminis set, establishes an httpOnly session cookie for the HIPAA API routes. - Redirects to
/platform-admin(the hub), which shows only the cards matching the account's claims.
Navigating to
/hipaa-admin/loginautomatically redirects to/platform-admin/login.
Revoking Admin Access
Via the script (recommended)
There is currently no revoke:platform-admin script. To revoke access, run a direct Postgres update:
UPDATE "user" SET "platformAdmin" = false WHERE email = 'admin@example.com';
Or add a revoke script that mirrors set-platform-admin.ts but sets the field to false.
The change takes effect on the user's next request (the session re-validates the user record from Postgres on every call).
Available Operations
Policy Templates (/platform-admin/policy-templates)
What they are
Master templates are the canonical, product-team-owned source of policy content. They define the expected structure — sections, required vs. optional — for a class of policies (e.g. "Data Retention Policy", "Acceptable Use Policy").
Tenants do not write policies from scratch. When a Manager starts a new policy, the platform surface the most relevant template via the Smart Template Discovery engine. The tenant then creates a policy from that template, which copies all sections in as a starting point with a sourceTemplateId + sourceVersion back-link.
When to create a template
Create a new master template whenever:
- A new regulatory obligation, industry standard, or best-practice policy type needs to be covered across tenants.
- A legal or compliance update requires a new canonical baseline (e.g. a change in a state's data privacy requirements).
- Product identifies a gap — tenant companies are creating ad-hoc policies for the same topic that would benefit from standardisation.
Do not create a template for content that is tenant-specific (custom workflows, internal procedures). Templates represent the minimum required or recommended standard; tenant-specific customisation happens after they adopt the template.
Procedure: Creating a template
- Navigate to Policy Templates in the sidebar and click New Template.
- Fill in:
- Title — descriptive name visible to tenants (e.g. "Remote Work Security Policy").
- Category — groups templates in the tenant UI (e.g. "Data Privacy", "Physical Security", "HR").
- Click Create to land on the editor.
- Add sections using the Add Section button. Each section has:
- Title — the section heading (e.g. "Scope", "Acceptable Use", "Enforcement").
- Body — rich text (TipTap editor). Use
{{COMPANY_NAME}},{{EFFECTIVE_DATE}},{{JURISDICTION}}placeholders where tenant-specific values should be injected. - Optional toggle — optional sections can be omitted when the tenant adopts the template. Required sections are always copied.
- Drag sections to set display order.
- Save at any point — the template stays in
draftstatus and is not visible to tenants.
Drafting tip: Write section bodies in the second person addressing the tenant's employees ("You are responsible for…"). Avoid naming the tenant company directly — use
{{COMPANY_NAME}}instead.
Procedure: Publishing a template
Publishing makes the template visible to tenants and the recommendation engine, and notifies all tenants currently using an older version of this template.
- Open the template and review all sections.
- Click Publish New Version.
- Write changelog notes — these are shown to tenant Admins/Owners in the update notice. Be specific ("Added a new section on multi-factor authentication requirements following NIST SP 800-63B").
- Confirm. The system:
- Increments the
versioncounter. - Appends a
TemplateChangelogEntry({ version, notes, publishedAt, publishedBy }). - Sets
status: 'published'. - Queries all tenant policies linked via
sourceTemplateId. - Skips policies already at
sourceVersion >= newVersion. - For each eligible policy (
published,expiring, orapprovedstatus):- Sets
templateUpdateAvailable: true. - Creates a
template_update_noticesdocument (pending). - Sends an in-app notification to all Admins/Owners of that tenant.
- Sends an email if SMTP is configured.
- Sets
- Invalidates the global rule cache.
The toast confirms: "Template v4 published. 12 tenant policies notified."
Cascade scope: Only
published,expiring, andapprovedtenant policies receive update notices. Draft and archived tenant policies are skipped. Tenants can choose to accept the update (merging new sections) or dismiss it from their policy detail page.
Example: Remote Work Security Policy template
The following shows a complete, ready-to-publish template you can create as-is.
Title: Remote Work Security Policy
Category: IT Security
Sections (in order):
1. Purpose (required)
This policy establishes the security requirements that all employees of
{{COMPANY_NAME}}must follow when working remotely. It is effective as of{{EFFECTIVE_DATE}}.
2. Scope (required)
This policy applies to all employees, contractors, and third-party personnel of
{{COMPANY_NAME}}who access company systems, data, or networks from any location outside a company-operated office.
3. Approved Devices (required)
Only devices approved by
{{COMPANY_NAME}}'s IT department may be used to access company systems remotely. Personal devices must be enrolled in the company's mobile device management (MDM) solution before use.All approved devices must have: - Up-to-date operating system and security patches - Approved endpoint protection software installed and active - Full-disk encryption enabled - Screen lock configured to activate after no more than 5 minutes of inactivity
4. Network Security (required)
Employees must use the company-provided VPN when accessing internal systems. Use of public or unsecured Wi-Fi networks without VPN is prohibited.
Home networks used for remote work must be secured with WPA2 or WPA3 encryption. Default router credentials must be changed.
5. Data Handling (required)
Company data must not be stored on personal devices or unapproved cloud storage services. All work files must be saved to
{{COMPANY_NAME}}-approved storage locations.Sensitive or confidential documents must not be printed in unsecured home environments unless expressly authorised by a manager.
6. Reporting Security Incidents (required)
Any suspected security incident — including lost or stolen devices, unauthorised access attempts, or phishing emails — must be reported to the IT Security team immediately via the incident reporting system or by email to the security contact on file.
7. Acceptable Use During Remote Work (optional)
Company systems and internet access must be used primarily for business purposes. Employees should ensure that family members or other household occupants cannot observe or access company screens, documents, or data.
Changelog notes for first publish:
Initial release of the Remote Work Security Policy template. Covers device approval, network security, data handling, and incident reporting obligations for remote employees.
Canonical template catalog (db/policy-templates-catalog.ts)
The file db/policy-templates-catalog.ts is the single source of truth for the pre-built template library. It exports a typed POLICY_CATALOG array where each entry bundles a complete template definition (title, category, HTML sections with {{PLACEHOLDER}} tokens) together with its pre-wired rules.
Do not edit template content directly in Firestore — the catalog file is the authoritative record. Edits made only in Firestore will be lost if pnpm seed:templates is re-run against a wiped database.
Templates in the catalog
| # | Title | Category | Default rules |
|---|---|---|---|
| 1 | Code of Conduct | HR Policies | any · w:30 |
| 2 | Payroll Policy | HR Policies | any · w:20 |
| 3 | Equal Employment Opportunity Policy | HR Policies | NAICS 62 required w:70; any w:20 |
| 4 | BYOD Policy | IT Security | remote_workers w:40; byod_devices w:60 |
| 5 | Remote Work Security Policy | IT Security | remote_workers w:30; NAICS 54 + US-CA + processes_pii required w:70 |
| 6 | AI Acceptable Use Policy | IT Security | any · w:25 |
| 7 | IT Security Policy | IT Security | any · w:40 |
| 8 | HR Policy | HR Policies | any · w:20 |
| 9 | Privacy and Data Protection Policy | Privacy | processes_pii w:50; US-CA + processes_pii required w:80 |
| 10 | Physical Security Policy | Operations | NAICS 32 required w:60; NAICS 33 required w:60 |
| 11 | Health and Safety Policy | Health & Safety | NAICS 32 required w:90; NAICS 33 required w:90; NAICS 62 w:50 |
| 12 | Drug and Alcohol Policy | HR Policies | NAICS 32 w:70; NAICS 33 w:70; NAICS 62 w:40 |
Seeding the catalog
pnpm seed:templates
This script reads POLICY_CATALOG, skips any template whose title already exists in Firestore (idempotent), creates each template as published at version 1, and writes all associated rules. Output:
Seeding policy templates...
✓ Created "Code of Conduct" (tmpl_abc123)
↷ Skipping "Payroll Policy" (already exists)
...
✓ Done. Created 10 templates (2 already existed). Created 28 rules.
Note: Restart the Next.js server to clear the rule engine cache.
Adding a new template to the catalog
- Add a new
CatalogEntryto thePOLICY_CATALOGarray indb/policy-templates-catalog.ts. Follow the existing structure:template(title, category, sections, placeholders) andrules(one object per matching rule). - Run
pnpm seed:templates— the script will create only the new entry and skip all existing ones. - Alternatively, create the template manually via the Platform Admin portal at
/platform-admin/policy-templatesand define its rules at/platform-admin/template-rules. Update the catalog file to reflect the final content so it remains the authoritative source.
Procedure: Archiving a template
Archive a template when it is superseded or no longer applicable (e.g. a regulation was repealed, or it has been replaced by a new template with a different ID).
- Open the template → Archive (three-dot menu or edit page).
- The template disappears from tenant recommendations. No new adoption is possible.
- Existing tenant policies retain their
sourceTemplateIdlink but receive no further update notices. - Archiving does not invalidate or delete existing tenant policies.
Template statuses
| Status | Meaning |
|---|---|
draft |
Being authored. Not visible to tenants or the recommendation engine. |
published |
Live. Eligible for tenant recommendations and update notices. |
archived |
Retired. No longer recommended. Existing tenant policies are unaffected. |
Section fields
| Field | Required | Notes |
|---|---|---|
title |
✓ | Short heading shown in the section list |
body |
✓ | Rich text (TipTap). Supports {{PLACEHOLDER}} tokens |
isOptional |
— | Default false. Optional sections can be dropped at adoption time |
order |
auto | Set by drag order; stored as integer |
id |
auto | Stable UUID — used to diff sections when a new version is published |
Template Rules (/platform-admin/template-rules)
What they are
Template rules power the Smart Template Discovery feature. When a tenant's Manager opens the "New Policy" flow, the recommendation engine evaluates every active rule against the tenant's profile and surfaces the matching templates, ranked by weight, with a brief rationale. Rules with isRequired: true are shown with a red "Required" badge.
Without rules, tenants see all templates unsorted. Rules are what make the feature genuinely useful: a healthcare company in California sees HIPAA-adjacent templates first, a logistics company sees vehicle and driver policy templates, and so on.
When to create a rule
Create a rule whenever:
- A template is legally required for a specific industry, state, or operational profile (e.g. HIPAA Notice of Privacy Practices is required for any NAICS 62xx company).
- A template is highly relevant but not strictly required (e.g. a Remote Work Security Policy is useful for any company with
remote_workersflag). - You want to ensure a template surfaces prominently even when many others match (set a high weight).
A single template can have multiple rules (one per industry, one per state, etc.). The engine OR-combines isRequired across all matching rules for the same template.
Rule fields
| Field | Purpose |
|---|---|
| Template | Which master template this rule promotes. Only published templates are listed. |
| Industry (NAICS) | 2-digit NAICS sector code (e.g. 62 for Health Care). Leave blank to match any industry. |
| Jurisdiction | ISO 3166-2 US state code (e.g. US-CA). Leave blank to match any state. |
| Business Flags | One or more operational flags (e.g. remote_workers, processes_pii, fleet_vehicles). All selected flags must be present on the tenant (AND logic). Leave empty to match any tenant. |
| Weight | Integer, higher = surfaced first. Default 10. Use 50–100 for legally required templates, 10–30 for recommended. |
| Required by law | Marks the template as legally required; shown with a red "Required" badge in the tenant UI. |
| Active | Inactive rules are ignored by the engine without being deleted. Use this to temporarily suspend a rule (e.g. while a template is being revised). |
| Reason / Rationale | Free text shown to the tenant's Manager in the recommendation panel explaining why the template is recommended. |
Matching logic
A rule matches a tenant when all of the following are true:
(industryCode IS NULL OR tenant.naicsCode starts-with rule.industryCode)
AND
(jurisdiction IS NULL OR tenant.jurisdiction == rule.jurisdiction)
AND
(rule.businessFlags ⊆ tenant.businessFlags)
When multiple rules point at the same template, the rule with the highest weight determines the displayed weight, and isRequired is OR-ed across all matching rules (so if any matching rule marks it required, it appears required).
Procedure: Creating a rule
- Navigate to Template Rules in the sidebar and click New Rule.
- Select the target Template from the dropdown (only
publishedtemplates are listed). - Fill in the matching criteria — leave any field blank to apply the rule more broadly.
- Set a Weight appropriate to how prominently the template should appear.
- Check Required by law if the template is legally mandated for matching tenants.
- Write a Reason that will appear in the tenant's recommendation panel (e.g. "California law requires all employers to maintain a written data privacy policy (CCPA §1798.100)").
- Save. The rule is active immediately — no deployment needed. The rule cache is invalidated on save so the next tenant recommendation request picks up the change.
Procedure: Testing a rule
There is currently no UI dry-run tool. To verify a rule before it goes live:
- Create the rule with Active = off.
- In your development environment, call
GET /api/policies/recommendwith anAuthorizationheader for a test tenant whose profile matches the rule criteria. - Verify the expected template appears in the response with the correct
weightandisRequiredvalue. - Toggle Active = on when satisfied.
Example: Rules for the Remote Work Security Policy template
The following shows two complementary rules that surface the "Remote Work Security Policy" template (created in the Policy Templates example above) to the right tenants.
Rule 1 — Broad recommendation for any company with remote employees
| Field | Value |
|---|---|
| Template | Remote Work Security Policy |
| Industry (NAICS) | (blank — any industry) |
| Jurisdiction | (blank — any state) |
| Business Flags | remote_workers |
| Weight | 30 |
| Required by law | No |
| Active | Yes |
| Reason | Companies with remote employees should maintain a formal remote work security policy covering device standards, network security, and data handling expectations. |
This rule ensures the template surfaces for any tenant whose profile includes the remote_workers flag, regardless of industry or location. The moderate weight (30) keeps it visible without crowding out higher-priority, legally required templates.
Rule 2 — Required for Professional Services companies in California handling PII
| Field | Value |
|---|---|
| Template | Remote Work Security Policy |
| Industry (NAICS) | 54 (Professional, Scientific, and Technical Services) |
| Jurisdiction | US-CA |
| Business Flags | processes_pii, remote_workers |
| Weight | 70 |
| Required by law | Yes |
| Active | Yes |
| Reason | California employers in professional services who process personal information and operate distributed teams must maintain documented security practices governing remote access. This satisfies obligations under CCPA (Cal. Civ. Code §1798.81.5) and California's Information Security Practices Act. |
This rule targets a narrower, higher-risk cohort. The high weight (70) and isRequired: true flag mean these tenants see the template at the top of their recommendation list with a red "Required" badge. Because Rule 1 also matches when this tenant has remote_workers set, the engine deduplicates to a single result and applies the higher weight (70) and OR-ed isRequired (true) from Rule 2.
What the tenant sees
When a NAICS 54 tenant in California with flags [processes_pii, remote_workers] opens the New Policy flow, the recommendation panel shows:
🔴 Required Remote Work Security Policy weight 70
"California employers in professional services who process …"
Recommended Data Privacy and Information Security Policy weight 55
"For companies that process personal data …"
A NAICS 72 (Hospitality) tenant in Texas with only the remote_workers flag sees:
Recommended Remote Work Security Policy weight 30
"Companies with remote employees should maintain …"
Cache behaviour
Rules are cached globally (all tenants read from the same cached rule list) with a per-tenant recommendation cache keyed by (tenantId, profileHash). Any create / update / delete to a rule:
- Invalidates the global rule cache (
template-rulestag). - Invalidates the per-tenant recommendation cache for the next request.
Propagation is effectively instant for new tenant sessions. Existing in-flight requests use the old cache snapshot.
Authentication Architecture
Better Auth user (email + password)
│
▼
POST /api/auth/sign-in (Better Auth)
│
▼
Session cookie set (httpOnly, Secure, SameSite=Strict)
│
├─ no platformAdmin/hipaaAdmin field ─→ signOut() + show error
│
├─ hipaaAdmin === true ─→ POST /api/hipaa/auth/login-admin
│ (issues httpOnly HIPAA session cookie)
│
└─ any admin field ─→ redirect to /platform-admin (hub)
│
▼
hub shows cards for each field
the account holds
│
▼
layout.tsx re-checks on every
navigation via auth.api.getSession;
requires platformAdmin OR hipaaAdmin
│
▼
API routes re-verify via
withPlatformAdmin() on every request
Sign-out: "Sign out" button in sidebar calls the Better Auth sign-out
endpoint + clears hipaa-admin-token cookie, then redirects
to /platform-admin/login
The field is verified independently at three points (login page, layout guard, API middleware) so there is no single point of bypass.
HIPAA Admin Portal
The HIPAA Admin Portal is a separate administration interface for managing companies, participants, and assessment results within the HIPAA module. It is accessible at /hipaa-admin and uses its own login credentials, independent from the main PRIAMtiv administrator accounts.
Risk Question Library
The Risk Question Library portal (/platform-admin/risk-questions) is the authoritative interface for managing all risk assessment questions, per-industry weight multipliers, and the Firestore question DAG that drives assessments.
Overview
| Firestore collection | Purpose |
|---|---|
risk_question_library |
One document per question (isActive, category, prompt, options, …) |
risk_industry_weights |
One document per NAICS code — category weight multipliers |
question_dags/risk-assessment-v1 |
Compiled DAG read by the assessment engine at runtime |
_meta/risk_seed |
Timestamp and count of the last successful DAG seed |
Changes to risk_question_library or risk_industry_weights do not take effect in assessments until you run a seed (see below). Seeding is a deliberate, audited action — you can edit questions safely without immediately affecting in-progress assessments.
Browsing and Filtering Questions
- Sign in at
/platform-admin/loginand open Risk Questions from the hub. - The Questions tab shows all active questions by default.
- Use the search box to filter by question ID or prompt text.
- Use the Category dropdown to narrow to a single risk category.
- Toggle Active only / All (incl. inactive) to see deactivated questions (displayed at 50 % opacity).
Creating a Question
- Click New Question (top right).
- Supply a unique ID in
q_snake_caseformat (e.g.q_cyber_mfa_policy). - Select Category and Type (
multiple_choice,boolean, orscale). - Write a Prompt (10–500 characters).
- Set a Weight (0.1–2.0). The default
1.0gives the question average influence on the category score. - For non-boolean questions, add at least two Options. Each option needs a text label and a score (0–100). Scores map to how risky the answer is (0 = lowest risk, 100 = highest risk).
- Optionally set Likelihood and Impact hints for the risk matrix view.
- Click NAICS codes to exclude them (highlighted in red = question will be hidden for that industry).
- Click Create Question. The question is saved to
risk_question_libraryimmediately but does not affect assessments until you seed.
Editing a Question
Click the pencil icon on any row. The editor opens pre-filled. Changes are validated on save. The question ID cannot be changed after creation.
Deactivating a Question
Click the power button on any active question. Confirm the prompt. The question's isActive flag is set to false. Deactivated questions no longer appear in assessments started after the next seed.
Industry Weight Profiles (Industry Weights tab)
The weight table shows one row per NAICS code and one column per risk category. Each cell is a multiplier applied to every question in that category when scoring a tenant with that NAICS code.
- Green ≤ 0.7 — category is de-emphasised for this industry
- Grey 0.8–1.1 — near-neutral weight
- Amber 1.2–1.9 — elevated importance
- Red ≥ 2.0 — high importance (e.g. Safety for Manufacturing)
Edit any cell value and click the Save button (enabled when the row is dirty) to persist the change. Valid range: 0.1–3.0. Weight profile changes take effect at the next seed.
Seed Control (Seed Control tab)
Seeding compiles the active question library into the DAG that the assessment engine reads. It is a two-step process:
- Validate Library — runs
validateLibraryacross all active questions and displays: - Errors (blocking): invalid IDs, missing fields, out-of-range scores, duplicate IDs. Must be resolved before seeding.
-
Warnings (non-blocking): duplicate prompts within a category, NAICS codes without weight profiles.
-
Seed to Firestore — enabled only after a successful validation. Writes
question_dags/risk-assessment-v1and updates_meta/risk_seed. - If tenants have in-progress assessments, a warning is shown: those tenants will complete their current assessment against the old question set and only see the new questions on their next assessment.
Best practice: run Validate before every seed, even if you believe the library is clean. The validator catches structural issues that would silently produce incorrect scores.
CLI Seeding
The pnpm seed:dags command runs the same logic as the UI seed button. It reads active questions from Firestore (not JSON files), validates, and writes the DAG. Use the CLI when seeding from a CI/CD pipeline or after a bulk import.
pnpm seed:dags
Security Considerations
- Dedicated account: Use a dedicated Firebase account for platform admin in production (e.g.
platform@yourcompany.com) rather than a developer's personal account. This makes access auditable and revocable without affecting anyone's normal tenant access. - Claim propagation delay: Firebase custom claims propagate to existing tokens within ~1 hour. The force-refresh in the login page (
getIdTokenResult(true)) bypasses this delay at sign-in time. If a claim is revoked, the user's existing session remains valid for up to 1 hour — sign them out via the Firebase console if immediate revocation is required. - Service account file: The
set-platform-adminscript requiresservice-account-file.json. This file is in.gitignoreand must never be committed. Restrict access to it as you would a production secret. - No tenant data access: Platform admin API routes do not query or modify any tenant-scoped collections (policies, users, incidents). There is no path through the platform admin portal to read or modify a specific tenant's data.
Adding New Platform Admin Features
When new product-team tooling is needed (e.g. tenant provisioning, feature flag management, usage dashboards), follow this pattern:
- Claim (if it's a new portal with its own access boundary): Add the claim key to
ADMIN_CLAIMSinsrc/scripts/set-admin.ts. Theset:adminscript will grant it automatically going forward. - API route: Create under
src/app/api/platform-admin/<feature>/route.ts. Wrap the handler withwithPlatformAdmin(). - Page: Create under
src/app/platform-admin/<feature>/page.tsx. The existinglayout.tsxautomatically protects it — no additional auth code needed. - Hub card: Add an entry to
ADMIN_AREASinsrc/app/platform-admin/page.tsxso the new area appears on the dashboard for accounts with the right claim. - Document here: Add a new section to this file describing the feature, its purpose, and any operational procedures.
- Update
API_REFERENCE.md: Add the new endpoints under the "Platform Admin" section.
Related Documents
- Policy Management Swimlane — role-by-role process flow (Platform Admin → Tenant → Employee)
- API Reference — Platform Admin endpoints
- HOW_TO_USE.md — Policy Management (tenant side)
- RBAC Matrix — tenant role permissions (platform admin is outside this matrix)
- db/policy-templates-catalog.ts — canonical source for all pre-built templates and rules
- src/scripts/seed-policy-templates.ts — idempotent bulk import script (
pnpm seed:templates)