Skip to content

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:

  1. API level — every /api/platform-admin/* route is wrapped in withPlatformAdmin() (src/middleware/authMiddleware.ts), which verifies the Better Auth session and checks for the platformAdmin: true field on the Better Auth user record in Postgres. A missing or falsy field returns 403.
  2. 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/login before 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.json in 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 user table: 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_CLAIMS object in src/scripts/set-admin.ts and add a corresponding entry to ADMIN_AREAS in src/app/platform-admin/page.tsx.

Step 3 — Sign in

Navigate to /platform-admin/login. The login page is shared across all portals:

  1. Accepts email + password via Better Auth.
  2. After sign-in, the Better Auth session is validated on the server on each request.
  3. If neither platformAdmin nor hipaaAdmin is set, signs the user back out and shows "This account does not have admin portal access."
  4. If hipaa_admin is set, establishes an httpOnly session cookie for the HIPAA API routes.
  5. Redirects to /platform-admin (the hub), which shows only the cards matching the account's claims.

Navigating to /hipaa-admin/login automatically redirects to /platform-admin/login.


Revoking Admin Access

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

  1. Navigate to Policy Templates in the sidebar and click New Template.
  2. Fill in:
  3. Title — descriptive name visible to tenants (e.g. "Remote Work Security Policy").
  4. Category — groups templates in the tenant UI (e.g. "Data Privacy", "Physical Security", "HR").
  5. Click Create to land on the editor.
  6. Add sections using the Add Section button. Each section has:
  7. Title — the section heading (e.g. "Scope", "Acceptable Use", "Enforcement").
  8. Body — rich text (TipTap editor). Use {{COMPANY_NAME}}, {{EFFECTIVE_DATE}}, {{JURISDICTION}} placeholders where tenant-specific values should be injected.
  9. Optional toggle — optional sections can be omitted when the tenant adopts the template. Required sections are always copied.
  10. Drag sections to set display order.
  11. Save at any point — the template stays in draft status 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.

  1. Open the template and review all sections.
  2. Click Publish New Version.
  3. 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").
  4. Confirm. The system:
  5. Increments the version counter.
  6. Appends a TemplateChangelogEntry ({ version, notes, publishedAt, publishedBy }).
  7. Sets status: 'published'.
  8. Queries all tenant policies linked via sourceTemplateId.
  9. Skips policies already at sourceVersion >= newVersion.
  10. For each eligible policy (published, expiring, or approved status):
    • Sets templateUpdateAvailable: true.
    • Creates a template_update_notices document (pending).
    • Sends an in-app notification to all Admins/Owners of that tenant.
    • Sends an email if SMTP is configured.
  11. Invalidates the global rule cache.

The toast confirms: "Template v4 published. 12 tenant policies notified."

Cascade scope: Only published, expiring, and approved tenant 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
  1. Add a new CatalogEntry to the POLICY_CATALOG array in db/policy-templates-catalog.ts. Follow the existing structure: template (title, category, sections, placeholders) and rules (one object per matching rule).
  2. Run pnpm seed:templates — the script will create only the new entry and skip all existing ones.
  3. Alternatively, create the template manually via the Platform Admin portal at /platform-admin/policy-templates and 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 sourceTemplateId link 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_workers flag).
  • 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

  1. Navigate to Template Rules in the sidebar and click New Rule.
  2. Select the target Template from the dropdown (only published templates are listed).
  3. Fill in the matching criteria — leave any field blank to apply the rule more broadly.
  4. Set a Weight appropriate to how prominently the template should appear.
  5. Check Required by law if the template is legally mandated for matching tenants.
  6. 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)").
  7. 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:

  1. Create the rule with Active = off.
  2. In your development environment, call GET /api/policies/recommend with an Authorization header for a test tenant whose profile matches the rule criteria.
  3. Verify the expected template appears in the response with the correct weight and isRequired value.
  4. 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:

  1. Invalidates the global rule cache (template-rules tag).
  2. 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

  1. Sign in at /platform-admin/login and open Risk Questions from the hub.
  2. The Questions tab shows all active questions by default.
  3. Use the search box to filter by question ID or prompt text.
  4. Use the Category dropdown to narrow to a single risk category.
  5. Toggle Active only / All (incl. inactive) to see deactivated questions (displayed at 50 % opacity).

Creating a Question

  1. Click New Question (top right).
  2. Supply a unique ID in q_snake_case format (e.g. q_cyber_mfa_policy).
  3. Select Category and Type (multiple_choice, boolean, or scale).
  4. Write a Prompt (10–500 characters).
  5. Set a Weight (0.1–2.0). The default 1.0 gives the question average influence on the category score.
  6. 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).
  7. Optionally set Likelihood and Impact hints for the risk matrix view.
  8. Click NAICS codes to exclude them (highlighted in red = question will be hidden for that industry).
  9. Click Create Question. The question is saved to risk_question_library immediately 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:

  1. Validate Library — runs validateLibrary across all active questions and displays:
  2. Errors (blocking): invalid IDs, missing fields, out-of-range scores, duplicate IDs. Must be resolved before seeding.
  3. Warnings (non-blocking): duplicate prompts within a category, NAICS codes without weight profiles.

  4. Seed to Firestore — enabled only after a successful validation. Writes question_dags/risk-assessment-v1 and updates _meta/risk_seed.

  5. 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-admin script requires service-account-file.json. This file is in .gitignore and 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:

  1. Claim (if it's a new portal with its own access boundary): Add the claim key to ADMIN_CLAIMS in src/scripts/set-admin.ts. The set:admin script will grant it automatically going forward.
  2. API route: Create under src/app/api/platform-admin/<feature>/route.ts. Wrap the handler with withPlatformAdmin().
  3. Page: Create under src/app/platform-admin/<feature>/page.tsx. The existing layout.tsx automatically protects it — no additional auth code needed.
  4. Hub card: Add an entry to ADMIN_AREAS in src/app/platform-admin/page.tsx so the new area appears on the dashboard for accounts with the right claim.
  5. Document here: Add a new section to this file describing the feature, its purpose, and any operational procedures.
  6. Update API_REFERENCE.md: Add the new endpoints under the "Platform Admin" section.