Documentation
PlugOne Master Ownership Manual
Complete A-to-Z owner reference: operate, troubleshoot, connect, secure, back up, tear down, and rebuild.
PlugOne Master Ownership Manual
Business Visibility Center · plugone.us
Printable Hard Copy
1. Complete System Overview
PlugOne is the platform at plugone.us. It hosts the Business Visibility Center (BVC) — an operator console for managing business identity, visibility, and verifiable facts.
Core concepts
- Entity — a business under PlugOne control (slug + display name + status).
- Dossier — the per-entity page (codex entries + history).
- Codex entry — a versioned fact about an entity (key + JSON value).
- Command — every state-changing action; runs through a single server pipeline and is logged.
- Command log — append-only audit of every command attempt (success or failure).
- Roles — admin, moderator (operator), viewer. First signup auto-promoted to admin.
- QR / registry — public dossier surface (Phase 2; the dossier slug is the route key).
- PlugStack shell — the BVC chrome at /bvc with left-nav modules.
Public vs private surfaces
- Public: /, /help, /operators-manual.
- Auth-required: /bvc, /bvc/entities, /bvc/entities/$id, /bvc/commands, /admin, /dashboard.
- Anonymous: /auth (signup, sign-in, reset).
2. Full Route Map
| Path | Purpose | Role | Component | Tables | Failure |
|---|---|---|---|---|---|
| / | Landing page | Public | src/routes/index.tsx | — | Render error → check root route |
| /auth | Sign in / sign up / reset | Public | src/routes/auth.tsx | auth.users, profiles, user_roles (via trigger) | Email rate limit, invalid creds |
| /help | Help & quick links | Public | src/routes/help.tsx | — | — |
| /operators-manual | This manual | Public | src/routes/operators-manual.tsx | — | — |
| /dashboard | High-level status | Authed | src/routes/dashboard.tsx | entities | Blank if no entities |
| /admin | Admin tools / dossier edit | Admin | src/routes/admin.tsx | entities, codex_entries | RLS denial if not admin |
| /bvc | BVC shell (redirects to /bvc/entities) | Authed | src/routes/bvc.tsx | — | Redirect to /auth if no session |
| /bvc/entities | Entity registry + create form | Operator+ | src/routes/bvc.entities.tsx | entities | Duplicate slug, validation |
| /bvc/entities/$id | Dossier (codex + history) | Operator+ | src/routes/bvc.entities.$id.tsx | entities, codex_entries | 404 on bad id |
| /bvc/commands | Command log viewer | Admin | src/routes/bvc.commands.tsx | command_log | Empty if admin RLS not granted |
All /bvc/* routes use a beforeLoad gate that redirects to /auth?redirect=<path> if no session.
3. Full Database Map
entities
One row per business under management.
| Column | Type | Required | Notes |
|---|---|---|---|
| id | uuid | yes | PK, default gen_random_uuid() |
| slug | text | yes | URL-safe key, lowercase a–z 0–9 -, unique recommended |
| display_name | text | yes | Human-readable name |
| status | text | yes | default 'active' |
| created_by | uuid | no | auth user id |
| created_at | timestamptz | yes | default now() |
| updated_at | timestamptz | yes | default now() |
RLS: SELECT/INSERT/UPDATE allowed for admin or operator. DELETE not allowed.
codex_entries
Versioned facts about an entity. New entry for the same key supersedes the previous one.
| Column | Type | Required | Notes |
|---|---|---|---|
| id | uuid | yes | PK |
| entity_id | uuid | yes | References entities.id (logical FK) |
| key | text | yes | e.g. 'name', 'address', 'visibility' |
| value | jsonb | yes | Up to 32KB; arbitrary JSON |
| version | int | yes | Auto-increments per (entity_id,key) |
| superseded_by | uuid | no | Set when a newer version exists |
| created_by | uuid | no | auth user id |
| created_at | timestamptz | yes | default now() |
RLS: SELECT/INSERT for admin or operator. UPDATE/DELETE not allowed (versioning by insert).
command_log
Append-only audit of every command pipeline call.
| Column | Type | Required | Notes |
|---|---|---|---|
| id | uuid | yes | PK |
| actor_id | uuid | no | Who ran the command |
| entity_id | uuid | no | Affected entity if any |
| command | text | yes | e.g. 'entity.create' |
| payload | jsonb | yes | Command input |
| result | jsonb | yes | Command output |
| ok | bool | yes | default true |
| created_at | timestamptz | yes | default now() |
RLS: SELECT admin only. Inserts done by service role from server fn.
profiles
| Column | Type | Required | Notes |
|---|---|---|---|
| id | uuid | yes | = auth.users.id |
| text | no | Set by handle_new_user trigger | |
| display_name | text | no | Optional |
| created_at | timestamptz | yes | default now() |
RLS: SELECT/UPDATE own row; admin reads all.
user_roles
| Column | Type | Required | Notes |
|---|---|---|---|
| id | uuid | yes | PK |
| user_id | uuid | yes | auth user id |
| role | app_role | yes | enum: admin | moderator | viewer (operator alias used in code) |
| created_at | timestamptz | yes | default now() |
RLS: read own; admin manages all. Used by has_role(uid, role) security-definer function in every other table's policies.
Example rows
entities:
{ id: "...", slug: "thedonuthouse", display_name: "The Donut House", status: "active" }
codex_entries:
{ entity_id: "...", key: "address", value: {"street":"123 Main"}, version: 1 }
{ entity_id: "...", key: "address", value: {"street":"456 Oak"}, version: 2, superseded_by: null }
command_log:
{ command: "entity.create", payload: {slug:"x"}, result: {id:"..."}, ok: true }4. Authentication System
Flows
- Signup: /auth → email + password → trigger
handle_new_usercreates profile + role (admin if first user, viewer otherwise) → session returned (auto-confirm on) → redirect to /bvc/entities or ?redirect. - Sign in: email + password → session → redirect.
- Reset: /auth (Forgot password) → email link to /auth → new password.
- Email confirmation: Currently auto-confirm = true. Disable in Cloud → Auth → Email if you want manual verification (requires SMTP).
- Session persistence: localStorage via Supabase browser client; restored on every page load.
- Redirect logic: /auth accepts
?redirect=/safe/path; only relative paths allowed.
Recovering a locked-out admin
- Try password reset at /auth (Forgot password).
- If the reset email never arrives, open Cloud → Auth → Users, find the user, click "Send password recovery".
- If the role row is missing, run a one-time migration:
INSERT INTO public.user_roles (user_id, role) VALUES ('<auth-user-id>', 'admin') ON CONFLICT DO NOTHING; - If completely locked out, create a new user via Cloud → Auth → Users → Add user, then promote them with the SQL above.
5. Permission / Role System
| Role | Read | Write | Notes |
|---|---|---|---|
| admin | Everything (entities, codex, command_log, profiles, roles) | Everything; manages roles | First signup auto-promoted |
| moderator (operator) | entities, codex_entries | Insert/update entities; insert codex | Day-to-day operator |
| viewer | Own profile + own role only | None | Default for non-first signups |
| entity owner | Implicit via created_by | Inherits operator/admin write rights | No separate ACL row yet |
Ownership inheritance
Today, ownership is defined by entities.created_by. Operators and admins can edit any entity. To restrict, tighten the RLS policy to created_by = auth.uid() OR has_role(auth.uid(),'admin').
Transferring ownership
-- Admin SQL
UPDATE public.entities SET created_by = '<new-owner-uid>' WHERE id = '<entity-id>';Preventing unauthorized access
- Never bypass the
runCommandserver fn from the client. - Never expose
SUPABASE_SERVICE_ROLE_KEYin browser code. - Audit /bvc/commands periodically for unexpected actors.
6. Entity + Dossier Workflow
- Create entity: /bvc/entities → enter slug (lowercase a–z 0–9 -) and display name → Run command → routes to dossier.
- Add codex entries: on the dossier, enter key + JSON value. Each key is versioned automatically.
- Publish: add a
visibilitycodex entry:{"public": true}. - Verify: confirm new row appears with version incremented and previous row's
superseded_bypopulated. - QR / registry: the entity slug is the routing key for the future public dossier URL (Phase 2).
- Failure recovery: if create fails, the form's red text names the field; for slug collision, choose a unique slug.
Slug rules: 1–64 chars, only a–z 0–9 -. No spaces, no uppercase.
7. Command System
Every mutation flows through runCommand at src/server/commands.functions.ts. Validation runs first; the result and the failure (if any) are written to command_log.
entity.create
- Payload:
{ slug: string, display_name: string } - Validation: slug 1–64 lowercase a–z 0–9 -, name 1–200 chars.
- Success:
{ ok: true, entity_id, result: { id, slug, display_name, status } } - Failure:
{ ok: false, result: { error: "..." } }(e.g. duplicate slug, RLS denial) - Logged: actor_id, entity_id (on success), payload, result, ok.
// example
runCommand({ data: { command: "entity.create",
payload: { slug: "thedonuthouse", display_name: "The Donut House" } } })codex.add
- Payload:
{ entity_id: uuid, key: string, value: any } - Validation: key 1–128, value JSON ≤ 32KB.
- Behavior: inserts new row with version = previous + 1, updates previous row's
superseded_by. - Success: returns the inserted row.
- Failure:
{ ok: false, result: { error: "..." } }
runCommand({ data: { command: "codex.add",
payload: { entity_id: "<uuid>", key: "address",
value: { street: "123 Main", city: "Springfield" } } } })Unknown commands
Return { ok: false, result: { error: "Unknown command: ..." } } and are still logged.
8. Edge Functions / Server Actions
runCommand
- File:
src/server/commands.functions.ts - Type: TanStack
createServerFn(POST), wrapped byrequireSupabaseAuthmiddleware. - Auth: bearer token from session; rejected with 401 otherwise.
- Input:
{ command: string, payload: object } - Output:
{ ok: boolean, entity_id: uuid|null, result: object } - Tables: entities, codex_entries, command_log.
- Errors:
"Unauthorized"(no session), Zod validation errors, RLS denial inresult.error. - Debug: check browser network tab for the POST body and response; cross-reference /bvc/commands for the logged row.
dispatch
src/server/commands.server.ts — pure dispatcher; add a new command by registering a handler in the switch and a Zod schema in commands.functions.ts.
requireSupabaseAuth middleware
src/integrations/supabase/auth-middleware.ts — validates JWT, attaches { supabase, userId, claims } to context.
supabaseAdmin
src/integrations/supabase/client.server.ts — service-role client used only by the command-log insert. Never imported from client code.
9. Environment Variables
| Name | Where | Purpose | If missing |
|---|---|---|---|
| VITE_SUPABASE_URL | Browser + .env | Cloud project URL for browser client | Auth + DB calls fail |
| VITE_SUPABASE_PUBLISHABLE_KEY | Browser + .env | Anon key for browser client | Auth + DB calls fail |
| VITE_SUPABASE_PROJECT_ID | Browser + .env | Project ref id | Some helpers misroute |
| SUPABASE_URL | Server-only | URL for server client | Server fns fail to init |
| SUPABASE_PUBLISHABLE_KEY | Server-only | Anon key for server-side authed client | requireSupabaseAuth fails |
| SUPABASE_SERVICE_ROLE_KEY | Server-only secret | Bypass-RLS admin client (command_log insert) | Command log writes fail |
| LOVABLE_API_KEY | Server-only secret | Lovable AI Gateway access | AI calls fail (none currently) |
| SUPABASE_DB_URL | Server-only | Direct Postgres URL (admin/migrations) | Direct DB scripts fail |
Configured in: Lovable Cloud → Secrets (server) and the auto-generated .env (browser). Never edit .env by hand; never expose SUPABASE_SERVICE_ROLE_KEY in browser code.
Rotating safely
- For anon/publishable keys: Cloud → API Keys → Rotate; .env updates automatically; redeploy.
- For service role: Cloud → API Keys → Rotate service role; update the secret; redeploy server fns.
- For LOVABLE_API_KEY: use the dedicated rotate tool (do not delete + add).
10. Cloud (Supabase) Control Manual
- Auth settings: Cloud → Users → Auth Settings (gear). Toggle email confirmation, HIBP, providers.
- Tables: Cloud → Database → Tables.
- RLS policies: Cloud → Database → Tables → row "Auth Policies".
- Edge Functions: Cloud → Edge Functions (none deployed today; PlugOne uses TanStack server fns instead).
- Logs: Cloud → Logs → Postgres / Auth / Edge.
- Storage: Cloud → Storage → Buckets (none today).
- Inspect failed inserts: Logs → Postgres → filter by
ERROR; cross-check command_log row with ok=false. - Inspect auth users: Cloud → Users.
- Inspect command logs: open /bvc/commands as admin, or query
SELECT * FROM command_log ORDER BY created_at DESC LIMIT 100. - Common repairs: missing role row → see §4; RLS denial → confirm
has_rolematches; trigger missing → re-run migrations.
11. Cloudflare / Domain Connection
plugone.us is connected via Lovable's custom domain feature.
DNS records
A @ 185.158.133.1
A www 185.158.133.1
TXT _lovable lovable_verify=<token from Lovable>- SSL: provisioned automatically by Lovable (Let's Encrypt). No action required.
- Redirects: set Primary domain in Project Settings → Domains; the other redirects to it.
- Subdomains: type the full subdomain in the connect dialog; add its own A or CNAME.
- Cloudflare proxy: if proxied, enable "Domain uses Cloudflare" in Advanced (uses CNAME verification).
- Caching: keep default; do not cache HTML aggressively or auth flows break.
- Do not change: A records pointing to 185.158.133.1, the _lovable TXT record, the Primary designation.
12. Deployment / Redeployment
- Frontend changes: click Publish → Update in Lovable to push to plugone.us. Preview URL updates automatically on every save.
- Backend changes (server fns, migrations, edge fns): deploy automatically — no manual step.
- Test before deploy: use the Preview URL (id-preview--*.lovable.app) before clicking Update.
- Rollback: open the Lovable history panel, find a known-good version, click Restore. Note: restoring before Cloud was added does not remove Cloud.
- Failed deployment recovery: read the build error in the Lovable chat; fix the named file; rebuild auto-runs.
- Cloning: use Lovable "Remix" to clone the project for a staging copy. Each remix gets its own Cloud project.
13. Backup / Export / Restore
- Database: Cloud → Database → Backups (managed daily backups). Manual:
pg_dump $SUPABASE_DB_URL > backup.sql. - Auth users: Cloud → Users exports as CSV. Note: password hashes do not export; users must reset on restore unless a full PG dump is restored.
- Dossier / codex export:
COPY (SELECT e.slug, c.key, c.value, c.version, c.created_at FROM entities e JOIN codex_entries c ON c.entity_id = e.id) TO STDOUT WITH CSV HEADER; - Storage: Cloud → Storage → bucket → Download (none today).
- Command log:
COPY command_log TO STDOUT WITH CSV HEADER; - Full restore: create a new Cloud project, run the SQL dump, re-create auth users (or restore via PG dump including
auth.*), redeploy frontend, point domain.
14. Troubleshooting Table
| Problem | What it means | Cause | Fix |
|---|---|---|---|
| Invalid login credentials | Email or password does not match an account. | Typo, wrong password, or account never created. | Re-enter carefully or use 'Forgot password?' on /auth. |
| User already exists | Account with that email already exists. | Previous signup with same email. | Switch to Sign in. Use Forgot password if needed. |
| Email rate limit exceeded (429) | Too many auth emails sent recently. | Multiple signup/reset attempts in a short window. | Wait 60 seconds. If persistent, confirm SMTP is not over quota in Cloud → Auth. |
| Stuck on /auth after sign in | Sign-in succeeds but redirect loops. | Blocked cookies or invalid redirect param. | Allow cookies; visit /auth?redirect=/bvc/entities directly. |
| 'undefined' is not valid JSON | Legacy command pipeline parse error. | Old build cached in browser. | Hard-refresh (Ctrl/Cmd+Shift+R). Already fixed in current build. |
| Command failed | Server rejected entity.create / codex.add. | Validation, duplicate slug, or signed out. | Read red error text under form; fix named field and retry. |
| permission denied for table | RLS rejected the read or write. | Role too low or not entity owner. | Sign in as admin/operator. First user is auto-admin. |
| Entity not appearing in registry | Created entity missing from list. | Silent creation failure or different user. | Reload /bvc/entities; confirm session; retry with unique slug. |
| Dossier won't open | Click on entity does not load page. | Network blip or missing id. | Return to /bvc/entities and click again. Sign out/in if persistent. |
| Dashboard blank | Modules show empty placeholders. | No business data entered yet. | Open /admin, fill dossier fields, Save. |
| Save button does nothing | No persistence on click. | Validation error or expired session. | Check red error; re-sign-in at /auth. |
| Locked out admin | Cannot access admin areas. | Lost password or role lost. | Reset password via /auth. If role lost, see §4 — Auth Recovery. |
| Edge function 401 | Server function returned Unauthorized. | Missing or expired bearer token. | Sign in again; if SSR, ensure beforeLoad gate exists. |
| Domain not resolving | plugone.us shows error or wrong site. | DNS misconfig or SSL pending. | See §11 — verify A records 185.158.133.1, wait up to 72h. |
15. The Donut House Walkthrough
- Open /auth → Sign up with owner email and password (≥6 chars). First user becomes admin.
- You land on /bvc/entities. Enter slug
thedonuthouse, nameThe Donut House. Click Run command. - The dossier opens at /bvc/entities/<new-id>.
- Add codex entry: key
address, value{"street":"123 Main","city":"Springfield"}. - Add codex entry: key
hours, value{"mon":"6-2","tue":"6-2"}. - Publish: key
visibility, value{"public": true}. - Open /bvc/commands (admin) — confirm three logged commands all
ok = true.
16. Operator Checklist
Intake
- Collect business name, slug, address, hours, contact.
- Decide visibility (public / private / draft).
Verification
- Confirm slug uniqueness on /bvc/entities.
- Confirm dossier shows latest version of every key.
- Confirm command_log entry exists for every change.
Publishing
- Add
visibilitycodex entry. - Test public surface (Phase 2 QR/dossier link).
- Print or PDF this manual after every major change for audit.
End of manual. Use the browser print dialog to save as PDF or print a hard copy.