Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
77c2767
fix: sync PostgresSchema.sql with V043 (drop username column)
ramseur Jun 25, 2026
9904de3
Split PostgresSchema.sql into standalone Postgres and Supabase paths
ramseur Jun 25, 2026
ea5fa8e
Move PostgresSchema.sql to db/postgres/ for consistent db layout
ramseur Jun 25, 2026
eeabea3
Update persistence.md for V043, db/ layout, and admin schema customiz…
ramseur Jun 25, 2026
29abb20
Remove PostgresSchema.sql from src/ (moved to db/postgres/ in prior c…
ramseur Jun 25, 2026
cca9b52
Add Phase 127: Supabase Row Level Security to roadmap
ramseur Jun 25, 2026
76377cf
Make API key optional for Ollama and LM Studio on provider add form
ramseur Jun 26, 2026
aa4c0da
Fix model list not loading when switching providers on Settings page
ramseur Jun 26, 2026
159c8a2
Fix model list not loading for Ollama on web and desktop
ramseur Jun 26, 2026
899ebe5
Pre-select personal workspace when adding a provider (web and desktop)
ramseur Jun 26, 2026
d787f45
docs(roadmap): audit pass — mark Phase 123 done, update migration cou…
ramseur Jun 29, 2026
1b96ec8
docs(roadmap): add v1.3/v1.4 focus table rows; fix test count to 2,208
ramseur Jun 29, 2026
549f557
docs(roadmap): add Phase 128; mark Phase 114/74/128 as v1.5 focus
ramseur Jun 29, 2026
c402e9c
docs(roadmap): add Phase 126 to v1.5 focus wave
ramseur Jun 29, 2026
853d20e
feat(knowledge): Phase 114 — enrich all 32 built-in skill description…
ramseur Jun 29, 2026
60f83d4
docs(roadmap): add Phase 129 — Missions → Workflows rename + UX review
ramseur Jun 29, 2026
d8c772a
feat(phase-128): Code generation quality gates — Parts A–D
ramseur Jul 1, 2026
f93339e
feat(phase-128e): CodeValidateTool — structural quality gates for cod…
ramseur Jul 1, 2026
5f04ce8
docs(roadmap): mark Phase 128 done, add Phase 129 dual-path idea, fle…
ramseur Jul 1, 2026
eebccf2
fix(web): route provider setup deep-link to /admin/providers, hide fo…
ramseur Jul 22, 2026
fbca744
refactor(artifacts): projects-only storage layout, add workspace memb…
ramseur Jul 23, 2026
3be063f
feat(phase-126): chat conversation UX — collapsed work strips, answer…
ramseur Jul 23, 2026
a3b5ce9
Add web app user-flow documentation with per-screen screenshots
claude Aug 2, 2026
6030692
Merge remote-tracking branch 'upstream/development' into claude/test-…
claude Aug 3, 2026
d3662cb
Re-capture user flow post-merge: dark/light side-by-side screenshots
claude Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
-- Sovrant PostgreSQL schema (Supabase-compatible).
-- Mirrors V001–V042 SQLite migrations. Safe to run multiple times (idempotent).
-- Sovrant standalone PostgreSQL schema.
-- Mirrors V001–V043 SQLite migrations. Safe to run multiple times (idempotent).
-- For Supabase deployments use supabase/migrations/ instead (includes GoTrue auth triggers).
-- Timestamps are stored as TEXT (ISO 8601) for wire-compatibility with SQLite stores.
-- BYTEA used for encrypted blobs (credentials table).
-- V035/V037 (built-in knowledge seed data) are handled by the app at startup, not here.
Expand All @@ -11,16 +12,14 @@

CREATE TABLE IF NOT EXISTS users (
user_id TEXT PRIMARY KEY,
username TEXT NOT NULL,
email TEXT,
role TEXT NOT NULL DEFAULT 'user',
team TEXT,
status TEXT NOT NULL DEFAULT 'active',
password_hash TEXT,
created_at TEXT NOT NULL DEFAULT (to_char(NOW() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')),
updated_at TEXT NOT NULL DEFAULT (to_char(NOW() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')),
CONSTRAINT users_username_unique UNIQUE (username),
CONSTRAINT users_email_unique UNIQUE (email)
CONSTRAINT users_email_unique UNIQUE (email)
);

CREATE TABLE IF NOT EXISTS workspaces (
Expand Down Expand Up @@ -759,9 +758,13 @@ CREATE INDEX IF NOT EXISTS ix_session_summaries_owner ON session_summaries(owner
CREATE INDEX IF NOT EXISTS ix_learned_patterns_owner ON learned_patterns(owner_user_id);
CREATE INDEX IF NOT EXISTS ix_instincts_owner ON instincts(owner_user_id);

-- ── V043 Drop username column ─────────────────────────────────────────────────
-- For instances initialized before V043. Fresh installs never had this column.
-- V043's PK rewrite (usr_{hex} → email) applies to SQLite standalone only.

ALTER TABLE public.users DROP COLUMN IF EXISTS username;

-- ── Schema version tracking ───────────────────────────────────────────────────
-- Records the last successfully applied schema version so the admin UI
-- can show "Up to date" vs "Needs initialization".

CREATE TABLE IF NOT EXISTS sovrant_schema_version (
id INTEGER PRIMARY KEY DEFAULT 1,
Expand All @@ -771,127 +774,5 @@ CREATE TABLE IF NOT EXISTS sovrant_schema_version (
);

INSERT INTO sovrant_schema_version (id, version)
VALUES (1, 42)
VALUES (1, 43)
ON CONFLICT (id) DO UPDATE SET version = EXCLUDED.version, applied_at = EXCLUDED.applied_at;

-- ════════════════════════════════════════════════════════════════════════════════
-- SUPABASE AUTH EXTENSION
-- Run this section ONLY on Supabase-hosted deployments, AFTER the schema above.
-- Standalone PostgreSQL deployments skip this entire section.
--
-- Why: Supabase Auth stores identities in auth.users (UUID PK, managed by GoTrue).
-- The app uses public.users (TEXT PK) as the FK anchor for all domain tables.
-- These triggers keep public.users in sync automatically so every FK constraint
-- continues to work without any application code changes.
--
-- user_id = auth.users.id::TEXT — UUID string, compatible with TEXT PK on all
-- existing FK columns (workspaces, workspace_members, api_tokens, user_roles,
-- project_members, password_reset_tokens).
-- ════════════════════════════════════════════════════════════════════════════════

-- ── Mirror trigger: auth.users INSERT → public.users INSERT ──────────────────

CREATE OR REPLACE FUNCTION public.handle_auth_user_created()
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
DECLARE
_role TEXT;
BEGIN
-- Read sovrant_role from app_metadata (service-role only, user cannot self-set).
-- Whitelist: only 'admin' is elevated; anything else (missing, null, unknown) → 'user'.
_role := CASE
WHEN NEW.raw_app_meta_data->>'sovrant_role' = 'admin' THEN 'admin'
ELSE 'user'
END;

INSERT INTO public.users (user_id, username, email, role, status, created_at, updated_at)
VALUES (
NEW.id::TEXT,
COALESCE(NEW.raw_user_meta_data->>'username', split_part(NEW.email, '@', 1)),
NEW.email,
_role,
'active',
to_char(NOW() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
to_char(NOW() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
)
ON CONFLICT (user_id) DO NOTHING;
RETURN NEW;
END;
$$;

CREATE OR REPLACE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_auth_user_created();

-- ── Mirror trigger: auth.users email/role UPDATE → public.users UPDATE ───────
-- Fires on email changes AND raw_app_meta_data changes so that setting
-- sovrant_role in app_metadata immediately syncs to public.users.role.

CREATE OR REPLACE FUNCTION public.handle_auth_user_updated()
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
DECLARE
_role TEXT;
BEGIN
IF NEW.raw_app_meta_data IS DISTINCT FROM OLD.raw_app_meta_data THEN
_role := CASE
WHEN NEW.raw_app_meta_data->>'sovrant_role' = 'admin' THEN 'admin'
ELSE 'user'
END;
UPDATE public.users
SET email = NEW.email,
role = _role,
updated_at = to_char(NOW() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
WHERE user_id = NEW.id::TEXT;
ELSE
UPDATE public.users
SET email = NEW.email,
updated_at = to_char(NOW() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
WHERE user_id = NEW.id::TEXT;
END IF;
RETURN NEW;
END;
$$;

CREATE OR REPLACE TRIGGER on_auth_user_updated
AFTER UPDATE OF email, raw_app_meta_data ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_auth_user_updated();

-- ── Notes for Supabase deployments ───────────────────────────────────────────
-- • public.users.password_hash is always NULL for Supabase-authenticated users.
-- GoTrue owns password management; the column is inert and kept for schema
-- parity with SQLite / standalone Postgres deployments.
-- • password_reset_tokens is also inert — Supabase handles password resets.
-- The table is retained so the schema remains identical across all modes.
-- • svt_* api_tokens still work for CLI / headless API access in Supabase mode.
-- Only interactive UI sessions use JWTs; machine tokens use the token table.
-- • First admin: create the user in the Supabase dashboard (Auth → Users).
-- The mirror trigger fires automatically (role defaults to 'user'). Elevate to
-- admin by setting sovrant_role in app_metadata (service-role only):
-- UPDATE auth.users
-- SET raw_app_meta_data = jsonb_set(
-- COALESCE(raw_app_meta_data, '{}'), '{sovrant_role}', '"admin"')
-- WHERE email = 'you@example.com';
-- The on_auth_user_updated trigger fires and syncs role = 'admin' to
-- public.users automatically. Never UPDATE public.users.role directly.

-- ── Row-Level Security (recommended for Supabase deployments) ────────────────
-- Uncomment to enforce the owner_user_id privacy model at the database layer
-- rather than relying solely on application-layer query filters. Required if
-- any Supabase Edge Functions, dashboard queries, or service-role clients need
-- to respect per-user memory privacy without going through the Sovrant API.

-- ALTER TABLE workspace_memory ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE session_summaries ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE learned_patterns ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE instincts ENABLE ROW LEVEL SECURITY;

-- CREATE POLICY workspace_memory_owner ON workspace_memory
-- USING (is_private = 0 OR owner_user_id = auth.uid()::text);

-- CREATE POLICY session_summaries_owner ON session_summaries
-- USING (owner_user_id = '' OR owner_user_id = auth.uid()::text);

-- CREATE POLICY learned_patterns_owner ON learned_patterns
-- USING (owner_user_id = '' OR owner_user_id = auth.uid()::text);

-- CREATE POLICY instincts_owner ON instincts
-- USING (owner_user_id = '' OR owner_user_id = auth.uid()::text);
40 changes: 40 additions & 0 deletions db/supabase/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Supabase CLI project configuration.
# Run migrations from this directory: cd db/supabase && supabase db push
# Or link to a remote project: supabase link --project-ref <ref>

[api]
enabled = true
port = 54321
schemas = ["public", "storage", "graphql_public"]
extra_search_path = ["public", "extensions"]
max_rows = 1000

[db]
port = 54322
shadow_port = 54320
major_version = 15

[studio]
enabled = true
port = 54323

[inbucket]
enabled = true
port = 54324

[storage]
enabled = true

[auth]
enabled = true
# Sovrant uses public.users as the domain anchor.
# The GoTrue auth.users mirror triggers are in the initial migration.
site_url = "http://localhost:5100"
additional_redirect_urls = ["http://localhost:5100"]
jwt_expiry = 3600
enable_signup = true

[auth.email]
enable_signup = true
double_confirm_changes = true
enable_confirmations = false
Loading