forge-data / core database spine (CT 109, formerly "lifeos")¶
URL: https://mkdocs.justinsforge.com/memory/general/reference_forge_data_lifeos_spine/
CANON UPDATE (2026-06-18) , "lifeos" retired, this is now "the core database" (
core). This Postgres spine (CT 109, forge-data) is the data layer for CreatorTrack (product formerly "The Suite"/"Workspace"), built and previewed atdev.creatortrack.ai. Vocabulary: stop calling it "lifeos"; call it the core database /core. The physical DB namelifeos+ roleslifeos_app/lifeos_admin->coreis a SCHEDULED migration (invisible internal identifiers, downtime risk), NOT done yet, so the current connection strings/roles still readlifeosuntil that migration runs. "workspace" survives only as the internal RLS data-tenant term. Full canon (with tech-stack overview): creator-suite-vision-strategy.
The Postgres data spine for the workspace app + life/business data unification. Build plan: workspace-lifeos-spine-build-plan-2026-06-15. Status (2026-06-15): Phase 0 + 1 COMPLETE; Phase 2 domains BUILT. RLS gate passing. Phase 2 domains (tasks, projects, habits, docs, inbox) live + RLS-enforced. DB spine stays canonical (it is CreatorTrack's data layer); only the old Flask app surface below was retired.
DECOMMISSIONED 2026-07-04 , the old
workspace.justinsforge.comFlask app. Theforge-workspace.serviceuser unit was disabled + its unit file removed, and theworkspace.justinsforge.comDNS record + VR Alliance ingress rule were deleted. That surface was an orphaned read-view over the sharedcore/lifeosPostgres (rendered stale legacyapp.meal_entriesrows). CreatorTrack (forge-suite) is the only live product surface now. The DB spine, RLS core, roles, andforge-workspace-backuptimer (pg_dump -> restic -> gdrive) all stay live. Bot capture CUTOVER LIVE 2026-06-15:WORKSPACE_WRITE_TARGET=postgresset on forge-inbox-capture / forge-inbox-webhook / forge-brain-server (systemd drop-ins/etc/systemd/system/<svc>.service.d/cutover.conf); captures write to the spine, not Notion. No data migrated (Justin chose fresh start); 7 starter habits seeded in Personal. Remaining: deactivate the n8n notion-create/update/query workflows (2.5) + Phase 3 (finance/commerce/context migration). Rollback = set the drop-in flag tonotion, restart the three services.
Venv dependency (load-bearing): the bots run in their own venvs
(~/.forge-venvs/inbox, ~/.forge-venvs/brain-server), NOT system Python.
forge_workspace_capture needs psycopg[binary,pool] installed in BOTH, or the
spine import fails and capture silently falls back to Notion (the cutover no-ops).
Installed 2026-06-16. Any new venv that runs the brain needs psycopg too.
CANON UPDATE (2026-07-01), capture repointed to CreatorTrack.
forge_workspace_capture.py(same bot-facing signatures) now POSTs to the CreatorTrack capture API (/api/captureon :3060, bearerCAPTURE_TOKENin~/.forge-secrets/creatortrack-auth.env, acts as Justin viacore.login()). Data lands as COLLECTION ROWS in the app.nodes engine (system collections: Habits Definitions/Log, Tasks, Projects, Inbox; provisioning inforge-suite/lib/system-apps.ts). The Phase 2 domain tables below (habits./tasks./inbox. schemas) AND the interim app.tasks/app.task_lists are FROZEN ORPHANS, do not extend them. Build plan: creatortrack-lifeos-apps-build-2026-07-01.
Phase 2 domains (migrations 0004-0007)¶
tasks(projects, tasks),habits(habit_defs, habit_logs),docs(docs, doc_blocks nested block model),inbox(items). Each table workspace_id-scoped via core.apply_workspace_rls(). Blueprints:forge_workspace_ui_tasks/_projects/ _habits/_docs/_inbox.py, registered in the app, nav under /w// . forge_workspace_capture.py: canonical spine capture API (create_task/ save_to_inbox/log_habit) with bot brand -> workspace map.- Bot cutover flag
WORKSPACE_WRITE_TARGETinforge_telegram_inbox_brain.py:notion(default) |dual|postgres. To cut over: set the bot service env todual, restartforge-brain-server+forge-inbox-capture, watch captures land in the app, thenpostgres, then retire the n8n notion-create/update/query workflows (keep the Alexa bridge). Rollback = set flag back tonotion. Export Notion to JSON before retiring writes.
Host¶
- CT 109
forge-data, Debian 13, 192.168.86.86, 4 cores / 4GB / 40G. Admin viassh finn 'pct exec 109 -- ...'(no SSH key inside the container by design; app reaches it over TCP only). - Postgres 17, DB
lifeos, UTF8 / C.UTF-8 (the cluster's template DBs are SQL_ASCII;lifeoswas rebuilt from template0 as UTF8 -- never recreate it as SQL_ASCII or psycopg returns text as bytes). pgvector installed AND markedtrusted = trueinvector.controlso the non-superuser admin can create it. - Redis on the same LXC, bound
127.0.0.1 192.168.86.86, password-protected.
Roles (the RLS security model)¶
lifeos_app: the role the Flask app uses. NOSUPERUSER, NOBYPASSRLS, non-owner -> every RLS policy binds to it. Identity is set per request viaset_config('app.user_id', ...)+set_config('app.is_owner', ...).lifeos_admin: owns schemas/tables (bypasses RLS as owner). Runs migrations, seed, ETL, backup only. CREATEDB (for restore-verify). The app must never open an admin connection.pg_hba: only Console (192.168.86.50/32), scram-sha-256.lifeos_appscoped to dblifeos;lifeos_admintoall(needs thepostgresmaint DB for createdb).
RLS contract¶
Policies in data/workspace/migrations/0002_rls.sql. Helpers core.current_user_id(),
core.is_owner() read the GUCs (default deny if unset). core.apply_workspace_rls(regclass)
is the reusable applier every Phase 2+ workspace-scoped table calls once. Core
tables workspaces/memberships/users already have RLS (member sees only their
own; owner sees all).
Scripts (registered tools)¶
scripts/forge_workspace_db.py-- canonical connection helper (app pool + admin/app direct conns +set_identity+app_requestcontext manager).scripts/forge_workspace_migrate.py-- migrations runner overdata/workspace/migrations/NNNN_*.sql, trackscore.schema_migrations. Idempotent.scripts/forge_workspace_seed.py-- seeds Justin (owner) + the 6 workspaces (personal, nova, justinwieb, gus-outdoor, gus-the-bass, sip). Idempotent.scripts/forge_workspace_rls_test.py-- HARD GATE. Proves a scoped member cannot read another workspace's rows (core + domain probe), owner god-view, anonymous-deny, no app-role escape hatch. Exit 0 = pass.scripts/forge_workspace_app.py-- Flask app, formerly served 0.0.0.0:8101 (user serviceforge-workspace.service). RETIRED 2026-07-04 (service disabled, unit file removed, DNS + ingress deleted). Historical detail below. Resolves the Cloudflare Access email header ->core.login()(SECURITY DEFINER, migration 0003) -> per-request identity -> RLS. Dev-email fallback is HARD-GATED to local/Tailscale hosts; the public host with no Access header gets 401 (no owner leak). PWA (manifest + service worker + gradient icon). Routes:/,/w/<slug>,/w/<slug>/<section>(Phase 2 stubs),/health. Design system inscripts/forge_workspace_ui_base.py(sidebar + workspace switcher, lifted from finances).- Cloudflare:
workspace.justinsforge.comDNS + VR Alliance ingress rule DELETED 2026-07-04. (Historically rode tunnel fb20b500 ->localhost:8101behind Access appworkspace.) The VR Alliance tunnel still serves finances. scripts/forge_workspace_backup.sh[--verify-restore] -- pg_dump (custom) -> resticrclone:gdrive:forge-backup(taglifeos-spine), with a round-trip restore-into-throwaway-DB parity check. Nightly viaforge-workspace-backup.timer(user systemd, 03:45). Needs pg-client-17 on Console (installed from PGDG; pg16's pg_dump refuses a v17 server).
Secrets¶
~/.forge-secrets/forge-data.env (chmod 600): PG app + admin creds, Redis creds.