Skip to main content

Structure (Migration)

These SQL migration files define the core database structure for Vircadia World. They are executed in numeric order to set up the PostgreSQL database schema.

Environment Variables

  • VRCA_CLI_SERVICE_POSTGRES_MIGRATION_DIR: Overrides the default migration files (as seen below) with a custom set of files

1. Base

1_BASE.sql
-- ============================================================================
-- 1. CORE SECURITY AND ROLE MANAGEMENT
-- ============================================================================

-- Create Agent Proxy Role with hard-coded password
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'vircadia_agent_proxy') THEN
EXECUTE 'CREATE ROLE vircadia_agent_proxy LOGIN PASSWORD ''CHANGE_ME!'' NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOREPLICATION';
END IF;
END
$$;

-- Then revoke everything
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM PUBLIC;
REVOKE ALL ON ALL SEQUENCES IN SCHEMA public FROM PUBLIC;
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM PUBLIC;

-- Grant usage on schema
GRANT USAGE ON SCHEMA public TO vircadia_agent_proxy;

-- Only grant specific permissions needed
GRANT EXECUTE ON FUNCTION uuid_generate_v4() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION digest(bytea, text) TO vircadia_agent_proxy;

2. Auth

2_AUTH.sql
-- ============================================================================
-- 1. SCHEMA CREATION
-- ============================================================================
CREATE SCHEMA IF NOT EXISTS auth;

-- ============================================================================
-- 2. CORE AUTHENTICATION FUNCTIONS
-- ============================================================================
-- Super Admin Check Function
CREATE OR REPLACE FUNCTION auth.is_system_agent()
RETURNS boolean AS $$
BEGIN
RETURN session_user = 'vircadia';
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Proxy Agent Check Function
CREATE OR REPLACE FUNCTION auth.is_proxy_agent()
RETURNS boolean AS $$
BEGIN
RETURN session_user = 'vircadia_agent_proxy';
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- System Agent ID Function
CREATE OR REPLACE FUNCTION auth.get_system_agent_id()
RETURNS UUID AS $$
BEGIN
RETURN '00000000-0000-0000-0000-000000000000'::UUID;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Current Agent ID Function
CREATE OR REPLACE FUNCTION auth.current_agent_id()
RETURNS UUID AS $$
BEGIN
-- First check if user is super admin
IF auth.is_system_agent() THEN
RETURN auth.get_system_agent_id();
END IF;

-- Check if setting exists and is not empty/null
IF current_setting('app.current_agent_id', true) IS NULL OR
TRIM(current_setting('app.current_agent_id', true)) = '' OR
TRIM(current_setting('app.current_agent_id', true)) = 'NULL' THEN
RAISE EXCEPTION 'No agent ID set in context';
END IF;

-- Validate UUID length
IF LENGTH(TRIM(current_setting('app.current_agent_id', true))) != 36 THEN
RAISE EXCEPTION 'Invalid UUID format: incorrect length';
END IF;

-- Try to cast to UUID, raise exception if invalid
BEGIN
RETURN TRIM(current_setting('app.current_agent_id', true))::UUID;
EXCEPTION WHEN OTHERS THEN
RAISE EXCEPTION 'Invalid UUID format: %', current_setting('app.current_agent_id', true);
END;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- ============================================================================
-- 3. BASE TEMPLATES
-- ============================================================================
-- Audit Template Table
CREATE TABLE auth._template (
general__created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
general__created_by UUID DEFAULT auth.current_agent_id(),
general__updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
general__updated_by UUID DEFAULT auth.current_agent_id()
);

-- ============================================================================
-- 4. BASE TABLES
-- ============================================================================
-- Agent Profiles Table
CREATE TABLE auth.agent_profiles (
general__agent_profile_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile__username TEXT UNIQUE,
auth__email TEXT UNIQUE,
auth__is_admin BOOLEAN NOT NULL DEFAULT FALSE,
auth__is_anon BOOLEAN NOT NULL DEFAULT FALSE,
profile__last_seen_at TIMESTAMPTZ
) INHERITS (auth._template);
ALTER TABLE auth.agent_profiles ENABLE ROW LEVEL SECURITY;

-- Sessions Table
CREATE TABLE auth.agent_sessions (
general__session_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
auth__agent_id UUID NOT NULL REFERENCES auth.agent_profiles(general__agent_profile_id) ON DELETE CASCADE,
auth__provider_name TEXT NOT NULL,
session__started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
session__last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
session__expires_at TIMESTAMPTZ NOT NULL,
session__jwt TEXT,
session__is_active BOOLEAN NOT NULL DEFAULT TRUE
) INHERITS (auth._template);
ALTER TABLE auth.agent_sessions ENABLE ROW LEVEL SECURITY;

-- Auth Provider Configurations Table
CREATE TABLE auth.auth_providers (
provider__name TEXT PRIMARY KEY, -- Provider identifier (e.g., 'google', 'github')
provider__display_name TEXT NOT NULL, -- Human-readable name
provider__enabled BOOLEAN NOT NULL DEFAULT false,
provider__client_id TEXT, -- OAuth client ID
provider__client_secret TEXT, -- OAuth client secret
provider__auth_url TEXT, -- OAuth authorization endpoint
provider__token_url TEXT, -- OAuth token endpoint
provider__userinfo_url TEXT, -- OAuth userinfo endpoint
provider__scope TEXT[], -- Required OAuth scopes
provider__metadata JSONB, -- Additional provider-specific configuration
provider__icon_url TEXT, -- URL to provider's icon
provider__jwt_secret TEXT NOT NULL, -- JWT signing secret for this provider
provider__session_max_per_agent INTEGER NOT NULL DEFAULT 1,
provider__session_duration_jwt_string TEXT NOT NULL DEFAULT '24h',
provider__session_duration_ms BIGINT NOT NULL DEFAULT 86400000,
provider__session_max_age_ms BIGINT NOT NULL DEFAULT 86400000,
provider__session_inactive_expiry_ms BIGINT NOT NULL DEFAULT 3600000
) INHERITS (auth._template);
ALTER TABLE auth.auth_providers ENABLE ROW LEVEL SECURITY;

-- Auth Providers Association Table
CREATE TABLE auth.agent_auth_providers (
-- Core fields
auth__agent_id UUID NOT NULL REFERENCES auth.agent_profiles(general__agent_profile_id) ON DELETE CASCADE,
auth__provider_name TEXT NOT NULL REFERENCES auth.auth_providers(provider__name) ON DELETE RESTRICT,

-- Provider-specific identifiers
auth__provider_uid TEXT NOT NULL, -- Provider's unique ID for the user
auth__provider_email TEXT, -- Email from the provider

-- OAuth tokens
auth__access_token TEXT, -- Current access token
auth__refresh_token TEXT, -- Refresh token (if available)
auth__token_expires_at TIMESTAMPTZ, -- When the access token expires

-- Account status
auth__is_verified BOOLEAN NOT NULL DEFAULT FALSE, -- Has email been verified
auth__last_login_at TIMESTAMPTZ, -- Track last successful login

-- Additional data
auth__metadata JSONB, -- Provider-specific data/claims

-- Constraints
PRIMARY KEY (auth__agent_id, auth__provider_name),
UNIQUE (auth__provider_name, auth__provider_uid)
) INHERITS (auth._template);
ALTER TABLE auth.agent_auth_providers ENABLE ROW LEVEL SECURITY;
ALTER TABLE auth.agent_sessions ADD CONSTRAINT agent_sessions_auth__provider_name_fkey
FOREIGN KEY (auth__provider_name) REFERENCES auth.auth_providers(provider__name) ON DELETE CASCADE;

-- Sync Groups Table
CREATE TABLE auth.sync_groups (
general__sync_group TEXT PRIMARY KEY,
general__description TEXT,

server__tick__rate_ms INTEGER NOT NULL,
server__tick__max_tick_count_buffer INTEGER NOT NULL,

client__render_delay_ms INTEGER NOT NULL,
client__max_prediction_time_ms INTEGER NOT NULL,
client__poll__rate_ms INTEGER NOT NULL,

network__packet_timing_variance_ms INTEGER NOT NULL
) INHERITS (auth._template);
ALTER TABLE auth.sync_groups ENABLE ROW LEVEL SECURITY;

-- Sync Group Roles Table
CREATE TABLE auth.agent_sync_group_roles (
auth__agent_id UUID NOT NULL REFERENCES auth.agent_profiles(general__agent_profile_id) ON DELETE CASCADE,
group__sync TEXT NOT NULL REFERENCES auth.sync_groups(general__sync_group) ON DELETE CASCADE,
permissions__can_read BOOLEAN NOT NULL DEFAULT true,
permissions__can_insert BOOLEAN NOT NULL DEFAULT false,
permissions__can_update BOOLEAN NOT NULL DEFAULT false,
permissions__can_delete BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (auth__agent_id, group__sync)
) INHERITS (auth._template);
ALTER TABLE auth.agent_sync_group_roles ENABLE ROW LEVEL SECURITY;

-- ============================================================================
-- 5. UTILITY AND TRIGGER FUNCTIONS
-- ============================================================================
-- Audit Column Update Function
CREATE OR REPLACE FUNCTION auth.update_audit_columns()
RETURNS TRIGGER AS $$
BEGIN
NEW.general__updated_at = CURRENT_TIMESTAMP;
NEW.general__updated_by = auth.current_agent_id();
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Session Cleanup Functions
CREATE OR REPLACE FUNCTION auth.cleanup_old_sessions()
RETURNS trigger AS $$
BEGIN
-- Delete expired sessions based on provider settings
DELETE FROM auth.agent_sessions AS s
USING auth.auth_providers AS p
WHERE s.auth__provider_name = p.provider__name
AND (
-- Manual invalidation checks
NOT s.session__is_active
OR s.session__expires_at < NOW()
-- Provider-based timeout checks
OR s.session__started_at < (NOW() - (p.provider__session_max_age_ms || ' milliseconds')::INTERVAL)
OR s.session__last_seen_at < (NOW() - (p.provider__session_inactive_expiry_ms || ' milliseconds')::INTERVAL)
);

RETURN NULL;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE OR REPLACE FUNCTION auth.enforce_session_limit()
RETURNS TRIGGER AS $$
DECLARE
v_current_sessions INTEGER;
v_provider_record auth.auth_providers%ROWTYPE;
v_oldest_session RECORD;
BEGIN
-- Check that the provider exists and is enabled (already partly enforced by FK)
SELECT * INTO v_provider_record
FROM auth.auth_providers
WHERE provider__name = NEW.auth__provider_name;

-- Count active sessions
SELECT COUNT(*) INTO v_current_sessions
FROM auth.agent_sessions
WHERE auth__agent_id = NEW.auth__agent_id
AND auth__provider_name = NEW.auth__provider_name
AND session__is_active = true
AND session__expires_at > NOW();

IF v_current_sessions > v_provider_record.provider__session_max_per_agent THEN
-- Deactivate oldest session if limit reached
SELECT general__session_id
INTO v_oldest_session
FROM auth.agent_sessions
WHERE auth__agent_id = NEW.auth__agent_id
AND auth__provider_name = NEW.auth__provider_name
AND session__is_active = true
ORDER BY session__started_at ASC
LIMIT 1;

IF FOUND THEN
UPDATE auth.agent_sessions
SET session__is_active = false,
session__expires_at = NOW()
WHERE general__session_id = v_oldest_session.general__session_id;
END IF;
END IF;

RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- View Refresh Functions
CREATE OR REPLACE FUNCTION auth.refresh_active_sessions_view_trigger()
RETURNS trigger AS $$
BEGIN
REFRESH MATERIALIZED VIEW auth.active_sync_group_sessions;
RETURN NULL;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Trigger to update profile's last seen time based on session activity
CREATE OR REPLACE FUNCTION auth.update_profile_last_seen()
RETURNS TRIGGER AS $$
BEGIN
-- Update the agent's profile last seen timestamp if the session timestamp is newer
UPDATE auth.agent_profiles
SET profile__last_seen_at = NEW.session__last_seen_at
WHERE general__agent_profile_id = NEW.auth__agent_id
AND (profile__last_seen_at IS NULL
OR profile__last_seen_at < NEW.session__last_seen_at);

RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- ============================================================================
-- 6. AUTHENTICATION FUNCTIONS
-- ============================================================================
-- Non-system agent Status Functions
CREATE OR REPLACE FUNCTION auth.is_anon_agent()
RETURNS boolean AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM auth.agent_profiles AS ap
WHERE ap.general__agent_profile_id = auth.current_agent_id()
AND ap.auth__is_anon = true
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE OR REPLACE FUNCTION auth.is_admin_agent()
RETURNS boolean AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM auth.agent_profiles AS ap
WHERE ap.general__agent_profile_id = auth.current_agent_id()
AND ap.auth__is_admin = true
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- ============================================================================
-- 7. SESSION MANAGEMENT FUNCTIONS
-- ============================================================================
CREATE OR REPLACE FUNCTION auth.validate_session_id(
p_session_id UUID,
p_session_token TEXT DEFAULT NULL
) RETURNS UUID AS $$
DECLARE
v_session RECORD;
BEGIN
SELECT *
INTO v_session
FROM auth.agent_sessions
WHERE general__session_id = p_session_id;

IF NOT FOUND THEN
RAISE EXCEPTION 'Session not found for id: %', p_session_id;
END IF;

IF NOT v_session.session__is_active THEN
RAISE EXCEPTION 'Session % is inactive', p_session_id;
END IF;

IF v_session.session__expires_at < NOW() THEN
RAISE EXCEPTION 'Session % has expired on %', p_session_id, v_session.session__expires_at;
END IF;

IF p_session_token IS NOT NULL AND v_session.session__jwt != p_session_token THEN
RAISE EXCEPTION 'Session token mismatch for session id: %', p_session_id;
END IF;

RETURN v_session.auth__agent_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE OR REPLACE FUNCTION auth.set_agent_context_from_agent_id(p_agent_id UUID)
RETURNS void AS $$
BEGIN
-- Only allow the vircadia_agent_proxy role to set the agent context
IF NOT auth.is_proxy_agent() THEN
RAISE EXCEPTION 'Only the proxy agent can set the agent context';
END IF;

-- If the agent ID does not exist, raise an exception
IF NOT EXISTS (
SELECT 1
FROM auth.agent_profiles
WHERE general__agent_profile_id = p_agent_id
) THEN
RAISE EXCEPTION 'Agent ID % does not exist', p_agent_id;
END IF;

-- Prevent changing the context if it has already been set
IF current_setting('app.current_agent_id', true) IS NOT NULL
AND TRIM(current_setting('app.current_agent_id', true)) <> ''
AND TRIM(current_setting('app.current_agent_id', true)) <> 'NULL' THEN
RAISE EXCEPTION 'Agent context already set, a new transaction must be created';
END IF;

-- Set the validated agent ID for the session (transaction-local)
PERFORM set_config('app.current_agent_id', p_agent_id::text, true);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Session Heartbeat Function
CREATE OR REPLACE FUNCTION auth.update_session_heartbeat_from_session_id(
p_session_id UUID
)
RETURNS void AS $$
DECLARE
v_agent_id UUID;
BEGIN
-- Check if session exists and get agent ID
SELECT auth__agent_id INTO v_agent_id
FROM auth.agent_sessions
WHERE general__session_id = p_session_id
AND session__is_active = true
AND session__expires_at > NOW();

IF NOT FOUND THEN
RAISE EXCEPTION 'Session not found for id: %', p_session_id;
END IF;

-- Check permissions (user's own session, admin, or system)
IF v_agent_id != auth.current_agent_id()
AND NOT auth.is_admin_agent()
AND NOT auth.is_system_agent() THEN
RAISE EXCEPTION 'Insufficient permissions to update session: %', p_session_id;
END IF;

-- Update the last seen timestamp
UPDATE auth.agent_sessions
SET session__last_seen_at = NOW()
WHERE general__session_id = p_session_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Function to invalidate a session
CREATE OR REPLACE FUNCTION auth.invalidate_session_from_session_id(
p_session_id UUID
)
RETURNS void AS $$
DECLARE
v_agent_id UUID;
BEGIN
-- Check if session exists and get agent ID
SELECT auth__agent_id INTO v_agent_id
FROM auth.agent_sessions
WHERE general__session_id = p_session_id
AND session__is_active = true
AND session__expires_at > NOW();

IF NOT FOUND THEN
RAISE EXCEPTION 'Session not found for id: %', p_session_id;
END IF;

-- Check permissions (user's own session, admin, or system)
IF v_agent_id != auth.current_agent_id()
AND NOT auth.is_admin_agent()
AND NOT auth.is_system_agent() THEN
RAISE EXCEPTION 'Insufficient permissions to invalidate session: %', p_session_id;
END IF;

-- Update the session to be inactive
UPDATE auth.agent_sessions
SET session__is_active = false,
session__expires_at = NOW()
WHERE general__session_id = p_session_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;


-- ============================================================================
-- 8. MATERIALIZED VIEWS AND RELATED FUNCTIONS
-- ============================================================================
-- Active Sessions View
CREATE MATERIALIZED VIEW IF NOT EXISTS auth.active_sync_group_sessions AS
SELECT DISTINCT
s.general__session_id,
s.auth__agent_id,
s.session__started_at,
s.session__last_seen_at,
s.session__expires_at,
s.session__is_active,
r.group__sync,
r.permissions__can_read,
r.permissions__can_insert,
r.permissions__can_update,
r.permissions__can_delete,
ap.auth__is_admin,
ap.auth__is_anon
FROM auth.agent_sessions s
JOIN auth.agent_profiles ap ON s.auth__agent_id = ap.general__agent_profile_id
LEFT JOIN auth.agent_sync_group_roles r ON s.auth__agent_id = r.auth__agent_id
WHERE
s.session__is_active = true
AND s.session__expires_at > NOW()
WITH DATA;

-- Create index for better performance
CREATE UNIQUE INDEX active_sync_group_sessions_session_group
ON auth.active_sync_group_sessions (general__session_id, group__sync);

-- Additional indexes for materialized view
CREATE INDEX idx_active_sync_group_sessions_lookup
ON auth.active_sync_group_sessions (group__sync);

-- ============================================================================
-- 9. INDEXES
-- ============================================================================
-- Agent Profile Indexes
CREATE INDEX idx_agent_profiles_email ON auth.agent_profiles(auth__email);

-- Agent Session Indexes
CREATE INDEX idx_agent_sessions_auth__agent_id ON auth.agent_sessions(auth__agent_id);
CREATE INDEX idx_agent_sessions_auth__provider_name ON auth.agent_sessions(auth__provider_name);
CREATE INDEX idx_agent_sessions_active_lookup ON auth.agent_sessions
(session__is_active, session__expires_at)
WHERE session__is_active = true;
CREATE INDEX idx_agent_sessions_validation ON auth.agent_sessions
(general__session_id, session__is_active, session__expires_at)
WHERE session__is_active = true;
CREATE INDEX idx_agent_sessions_last_seen ON auth.agent_sessions(session__last_seen_at)
WHERE session__is_active = true;

-- ============================================================================
-- 10. TRIGGERS
-- ============================================================================
-- Session Management Triggers
CREATE TRIGGER trigger_cleanup
AFTER INSERT OR UPDATE ON auth.agent_sessions
FOR EACH STATEMENT
EXECUTE FUNCTION auth.cleanup_old_sessions();

CREATE TRIGGER trigger_enforce_max_sessions
AFTER INSERT ON auth.agent_sessions
FOR EACH ROW
EXECUTE FUNCTION auth.enforce_session_limit();

CREATE TRIGGER refresh_active_sessions_view_on_session_change
AFTER INSERT OR UPDATE OR DELETE ON auth.agent_sessions
FOR EACH STATEMENT
EXECUTE FUNCTION auth.refresh_active_sessions_view_trigger();

CREATE TRIGGER refresh_active_sessions_view_on_role_change
AFTER INSERT OR UPDATE OR DELETE ON auth.agent_sync_group_roles
FOR EACH STATEMENT
EXECUTE FUNCTION auth.refresh_active_sessions_view_trigger();

-- Audit Trail Triggers
CREATE TRIGGER update_agent_profile_timestamps
BEFORE UPDATE ON auth.agent_profiles
FOR EACH ROW
EXECUTE FUNCTION auth.update_audit_columns();

CREATE TRIGGER update_agent_auth_providers_updated_at
BEFORE UPDATE ON auth.agent_auth_providers
FOR EACH ROW
EXECUTE FUNCTION auth.update_audit_columns();

CREATE TRIGGER update_agent_sessions_updated_at
BEFORE UPDATE ON auth.agent_sessions
FOR EACH ROW
EXECUTE FUNCTION auth.update_audit_columns();

CREATE TRIGGER update_sync_groups_updated_at
BEFORE UPDATE ON auth.sync_groups
FOR EACH ROW
EXECUTE FUNCTION auth.update_audit_columns();

CREATE TRIGGER update_agent_sync_group_roles_updated_at
BEFORE UPDATE ON auth.agent_sync_group_roles
FOR EACH ROW
EXECUTE FUNCTION auth.update_audit_columns();

CREATE TRIGGER update_profile_last_seen_on_session_activity
AFTER UPDATE OF session__last_seen_at ON auth.agent_sessions
FOR EACH ROW
EXECUTE FUNCTION auth.update_profile_last_seen();

CREATE TRIGGER update_profile_last_seen_on_session_creation
AFTER INSERT ON auth.agent_sessions
FOR EACH ROW
EXECUTE FUNCTION auth.update_profile_last_seen();

-- ============================================================================
-- 11. INITIAL DATA
-- ============================================================================
-- System Agent Profile
INSERT INTO auth.agent_profiles
(general__agent_profile_id, profile__username, auth__email)
VALUES
(auth.get_system_agent_id(), 'admin', 'system@internal')
ON CONFLICT (general__agent_profile_id) DO NOTHING;

-- Default Sync Groups
INSERT INTO auth.sync_groups (
general__sync_group,
general__description,
server__tick__rate_ms,
server__tick__max_tick_count_buffer,
client__render_delay_ms,
client__max_prediction_time_ms,
client__poll__rate_ms,
network__packet_timing_variance_ms
) VALUES
-- Public zone
('public.REALTIME', 'Public realtime entities', 100, 50, 50, 100, 100, 25),
('public.NORMAL', 'Public normal-priority entities', 200, 20, 100, 150, 200, 50),
('public.BACKGROUND', 'Public background entities', 1000, 10, 200, 300, 1000, 100),
('public.STATIC', 'Public static entities', 5000, 5, 500, 1000, 5000, 250);

-- Add system provider to auth_providers table if not exists
INSERT INTO auth.auth_providers (
provider__name,
provider__display_name,
provider__enabled,
provider__jwt_secret,
provider__session_max_per_agent,
provider__session_duration_jwt_string,
provider__session_duration_ms,
provider__session_max_age_ms,
provider__session_inactive_expiry_ms
) VALUES (
'system',
'System Authentication',
true,
'CHANGE_ME!',
100,
'24h',
86400000,
86400000,
3600000
) ON CONFLICT (provider__name) DO NOTHING;

-- Add anonymous provider to auth_providers table if not exists
INSERT INTO auth.auth_providers (
provider__name,
provider__display_name,
provider__enabled,
provider__jwt_secret,
provider__session_max_per_agent,
provider__session_duration_jwt_string,
provider__session_duration_ms,
provider__session_max_age_ms,
provider__session_inactive_expiry_ms
) VALUES (
'anon',
'Anonymous Authentication',
true,
'CHANGE_ME!',
1,
'24h',
86400000,
86400000,
3600000
) ON CONFLICT (provider__name) DO NOTHING;

-- ============================================================================
-- 12. PERMISSIONS
-- ============================================================================
CREATE POLICY agent_view_own_profile ON auth.agent_profiles
FOR SELECT
TO PUBLIC
USING (
general__agent_profile_id = auth.current_agent_id() -- Agents can view their own profile
OR auth.is_admin_agent() -- Admins can view all profiles
OR auth.is_system_agent() -- System agent can view all profiles
);

CREATE POLICY agent_update_own_profile ON auth.agent_profiles
FOR UPDATE
TO PUBLIC
USING (
general__agent_profile_id = auth.current_agent_id() -- Agents can update their own profile
OR auth.is_admin_agent() -- Admins can update all profiles
OR auth.is_system_agent() -- System agent can update all profiles
);

-- Sync Group Policies
CREATE POLICY "Allow viewing sync groups" ON auth.sync_groups
FOR SELECT
TO PUBLIC
USING (true);

CREATE POLICY "Allow admin sync group modifications" ON auth.sync_groups
FOR ALL
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
);

-- Sync Group Role Policies
CREATE POLICY "Allow viewing sync group roles" ON auth.agent_sync_group_roles
FOR SELECT
TO PUBLIC
USING (true);

CREATE POLICY "Allow admin sync group role modifications" ON auth.agent_sync_group_roles
FOR ALL
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
);

-- Agent Auth Providers Policies
CREATE POLICY "Users can view their own provider connections" ON auth.agent_auth_providers
FOR SELECT
TO PUBLIC
USING (
auth__agent_id = auth.current_agent_id()
OR auth.is_admin_agent()
OR auth.is_system_agent()
);

CREATE POLICY "Only admins can manage provider connections" ON auth.agent_auth_providers
FOR ALL
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
);

CREATE POLICY "Only admins can manage auth providers" ON auth.auth_providers
FOR ALL
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
);

-- SELECT policy: Regular users can view their own sessions, admins/system can view all
CREATE POLICY "Sessions SELECT permissions" ON auth.agent_sessions
FOR SELECT
TO PUBLIC
USING (
auth__agent_id = auth.current_agent_id()
OR auth.is_admin_agent()
OR auth.is_system_agent()
);

-- INSERT policy: Regular users can only create their own sessions, admins/system can create any
CREATE POLICY "Sessions INSERT permissions" ON auth.agent_sessions
FOR INSERT
TO PUBLIC
WITH CHECK (
auth.is_admin_agent()
OR auth.is_system_agent()
);

-- UPDATE policy: Regular users can only update their own sessions, admins/system can update any
CREATE POLICY "Sessions UPDATE permissions" ON auth.agent_sessions
FOR UPDATE
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
);

-- DELETE policy: Regular users can only delete their own sessions, admins/system can delete any
CREATE POLICY "Sessions DELETE permissions" ON auth.agent_sessions
FOR DELETE
TO PUBLIC
USING (
auth__agent_id = auth.current_agent_id()
OR auth.is_admin_agent()
OR auth.is_system_agent()
);

-- Revoke all permissions first
REVOKE ALL ON ALL TABLES IN SCHEMA auth FROM PUBLIC;
REVOKE ALL ON ALL SEQUENCES IN SCHEMA auth FROM PUBLIC;
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA auth FROM PUBLIC;
REVOKE ALL ON SCHEMA auth FROM PUBLIC;

-- Grant usage on schema
GRANT USAGE ON SCHEMA auth TO vircadia_agent_proxy;

-- Grant table permissions
GRANT SELECT, INSERT, UPDATE, DELETE ON auth.agent_profiles TO vircadia_agent_proxy;
GRANT SELECT, INSERT, UPDATE, DELETE ON auth.auth_providers TO vircadia_agent_proxy;
GRANT SELECT, INSERT, UPDATE, DELETE ON auth.agent_auth_providers TO vircadia_agent_proxy;
GRANT SELECT, INSERT, UPDATE, DELETE ON auth.sync_groups TO vircadia_agent_proxy;
GRANT SELECT, INSERT, UPDATE, DELETE ON auth.agent_sync_group_roles TO vircadia_agent_proxy;
GRANT SELECT, INSERT, UPDATE, DELETE ON auth.agent_sessions TO vircadia_agent_proxy;

-- Grant view permissions
GRANT SELECT ON auth.active_sync_group_sessions TO vircadia_agent_proxy;

-- Grant function permissions with explicit parameter types
GRANT EXECUTE ON FUNCTION auth.is_anon_agent() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.is_admin_agent() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.is_system_agent() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.is_proxy_agent() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.current_agent_id() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.get_system_agent_id() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.validate_session_id(UUID, TEXT) TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.set_agent_context_from_agent_id(UUID) TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.refresh_active_sessions_view_trigger() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.update_audit_columns() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.cleanup_old_sessions() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.enforce_session_limit() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.update_session_heartbeat_from_session_id(UUID) TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.update_profile_last_seen() TO vircadia_agent_proxy;
GRANT EXECUTE ON FUNCTION auth.invalidate_session_from_session_id(UUID) TO vircadia_agent_proxy;

3. Configuration

3_CONFIG.sql
-- ============================================================================
-- 1. SCHEMA CREATION AND INITIAL PERMISSIONS
-- ============================================================================
CREATE SCHEMA IF NOT EXISTS config;

-- ============================================================================
-- 2. TYPES
-- ============================================================================
CREATE TYPE config.operation_enum AS ENUM ('INSERT', 'UPDATE', 'DELETE');


-- ============================================================================
-- 3. CONFIGURATION TABLES
-- ============================================================================
-- Entity Configuration
CREATE TABLE config.entity_config (
entity_config__script_compilation_timeout_ms INTEGER NOT NULL
);

-- Network Configuration
CREATE TABLE config.network_config (
network_config__max_latency_ms INTEGER NOT NULL,
network_config__warning_latency_ms INTEGER NOT NULL,
network_config__consecutive_warnings_before_kick INTEGER NOT NULL,
network_config__measurement_window_ticks INTEGER NOT NULL,
network_config__packet_loss_threshold_percent INTEGER NOT NULL
);

-- Authentication Configuration
CREATE TABLE config.auth_config (
auth_config__session_cleanup_interval BIGINT NOT NULL,
auth_config__heartbeat_interval_ms INTEGER NOT NULL
);

-- Database Version Configuration
CREATE TABLE config.database_config (
database_config__major_version INTEGER NOT NULL,
database_config__minor_version INTEGER NOT NULL,
database_config__patch_version INTEGER NOT NULL,
database_config__setup_timestamp TIMESTAMP NOT NULL
);


-- ============================================================================
-- 4. SEED TRACKING
-- ============================================================================
CREATE TABLE config.seeds (
general__hash TEXT PRIMARY KEY, -- MD5 hash of the seed content
general__name TEXT NOT NULL, -- Seed filename for reference
general__executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);


-- ============================================================================
-- 5. INITIAL DATA
-- ============================================================================
-- Network Configuration
INSERT INTO config.network_config (
network_config__max_latency_ms,
network_config__warning_latency_ms,
network_config__consecutive_warnings_before_kick,
network_config__measurement_window_ticks,
network_config__packet_loss_threshold_percent
) VALUES (500, 200, 50, 100, 5);

-- Authentication Configuration
INSERT INTO config.auth_config (
auth_config__session_cleanup_interval,
auth_config__heartbeat_interval_ms
) VALUES (
3600000,
3000
);

-- Database Version Configuration
INSERT INTO config.database_config (
database_config__major_version,
database_config__minor_version,
database_config__patch_version,
database_config__setup_timestamp
) VALUES (1, 0, 0, CURRENT_TIMESTAMP);


-- ============================================================================
-- 6. CONFIG SCHEMA PERMISSIONS
-- ============================================================================
-- Revoke All Permissions
REVOKE ALL ON ALL TABLES IN SCHEMA config FROM PUBLIC;
REVOKE ALL ON ALL SEQUENCES IN SCHEMA config FROM PUBLIC;
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA config FROM PUBLIC;
REVOKE ALL ON SCHEMA config FROM PUBLIC;

-- Grant Usage on Schema
GRANT USAGE ON SCHEMA config TO vircadia_agent_proxy;

-- Grant Specific Permissions
GRANT SELECT ON config.entity_config TO vircadia_agent_proxy;
GRANT SELECT ON config.network_config TO vircadia_agent_proxy;
GRANT SELECT ON config.auth_config TO vircadia_agent_proxy;
GRANT SELECT ON config.database_config TO vircadia_agent_proxy;

4. Entity

4_ENTITY.sql
-- ============================================================================
-- 1. SCHEMA CREATION AND INITIAL PERMISSIONS
-- ============================================================================
CREATE SCHEMA IF NOT EXISTS entity;

REVOKE ALL ON SCHEMA entity FROM vircadia_agent_proxy;
GRANT USAGE ON SCHEMA entity TO vircadia_agent_proxy;

-- ============================================================================
-- 3. BASE TEMPLATES
-- ============================================================================
-- Audit Template Table
CREATE TABLE entity._template (
general__created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
general__created_by UUID NOT NULL DEFAULT auth.current_agent_id(),
general__updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
general__updated_by UUID NOT NULL DEFAULT auth.current_agent_id()
);


-- ============================================================================
-- 4. CORE TABLES
-- ============================================================================

-- 4.2 ENTITY ASSETS TABLE
-- ============================================================================
CREATE TABLE entity.entity_assets (
general__asset_file_name TEXT PRIMARY KEY,
group__sync TEXT NOT NULL REFERENCES auth.sync_groups(general__sync_group) DEFAULT 'public.NORMAL',
CONSTRAINT fk_entity_assets_sync_group FOREIGN KEY (group__sync) REFERENCES auth.sync_groups(general__sync_group),

asset__data__bytea BYTEA, -- Store asset binaries (GLBs, textures, etc.) as bytea
asset__mime_type TEXT DEFAULT NULL,

asset__data__bytea_updated_at timestamptz DEFAULT now()
) INHERITS (entity._template);

ALTER TABLE entity.entity_assets ENABLE ROW LEVEL SECURITY;


-- 4.3 ENTITIES TABLE
-- ============================================================================
CREATE TABLE entity.entities (
general__entity_name TEXT PRIMARY KEY,
general__semantic_version TEXT NOT NULL DEFAULT '1.0.0',
general__initialized_at TIMESTAMPTZ DEFAULT NULL,
general__initialized_by UUID DEFAULT NULL,
meta__data JSONB NOT NULL DEFAULT '{}'::jsonb,
group__sync TEXT NOT NULL REFERENCES auth.sync_groups(general__sync_group) DEFAULT 'public.NORMAL',
group__load_priority INTEGER NOT NULL DEFAULT 1,

CONSTRAINT fk_entities_sync_group FOREIGN KEY (group__sync) REFERENCES auth.sync_groups(general__sync_group),

meta_data_updated_at timestamptz NOT NULL DEFAULT now()
) INHERITS (entity._template);

CREATE INDEX idx_entities_load_priority ON entity.entities(group__load_priority) WHERE group__load_priority IS NOT NULL;
CREATE INDEX idx_entities_created_at ON entity.entities(general__created_at);
CREATE INDEX idx_entities_updated_at ON entity.entities(general__updated_at);
CREATE INDEX idx_entities_semantic_version ON entity.entities(general__semantic_version);

ALTER TABLE entity.entities ENABLE ROW LEVEL SECURITY;


-- ============================================================================
-- 5. FUNCTIONS
-- ============================================================================

-- 5.1 CORE UTILITY FUNCTIONS
-- ============================================================================

-- Audit Column Update Function
CREATE OR REPLACE FUNCTION entity.update_audit_columns()
RETURNS TRIGGER AS $$
BEGIN
NEW.general__updated_at = CURRENT_TIMESTAMP;
NEW.general__updated_by = auth.current_agent_id();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;


-- ============================================================================
-- 6. TRIGGERS
-- ============================================================================

-- 6.2 ENTITY ASSET TRIGGERS
-- ============================================================================

-- Update audit columns trigger for entity_assets
CREATE TRIGGER update_audit_columns
BEFORE UPDATE ON entity.entity_assets
FOR EACH ROW
EXECUTE FUNCTION entity.update_audit_columns();


-- 6.3 ENTITY TRIGGERS
-- ============================================================================

-- Trigger for updating audit columns
CREATE TRIGGER update_audit_columns
BEFORE UPDATE ON entity.entities
FOR EACH ROW
EXECUTE FUNCTION entity.update_audit_columns();


-- ============================================================================
-- 7. POLICIES AND PERMISSIONS
-- ============================================================================

-- 7.1 INITIAL REVOCATIONS
-- ============================================================================
-- Revoke all permissions from PUBLIC and vircadia_agent_proxy (to start with a clean slate)
REVOKE ALL ON ALL TABLES IN SCHEMA entity FROM PUBLIC, vircadia_agent_proxy;
REVOKE ALL ON ALL SEQUENCES IN SCHEMA entity FROM PUBLIC, vircadia_agent_proxy;
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA entity FROM PUBLIC, vircadia_agent_proxy;
REVOKE ALL ON ALL PROCEDURES IN SCHEMA entity FROM PUBLIC, vircadia_agent_proxy;
REVOKE ALL ON ALL ROUTINES IN SCHEMA entity FROM PUBLIC, vircadia_agent_proxy;


-- 7.3 ENTITY ASSET POLICIES
-- ============================================================================
-- Grant table permissions to vircadia_agent_proxy for entity_assets
GRANT SELECT, INSERT, UPDATE, DELETE ON entity.entity_assets TO vircadia_agent_proxy;

-- Policy: allow insert only if the agent is a member of the asset's sync group with insert permission
CREATE POLICY "Group can insert entity assets" ON entity.entity_assets
FOR INSERT
TO PUBLIC
WITH CHECK (
auth.is_admin_agent()
OR auth.is_system_agent()
OR EXISTS (
SELECT 1
FROM auth.agent_sync_group_roles AS ar
WHERE ar.auth__agent_id = auth.current_agent_id()
AND ar.group__sync = entity.entity_assets.group__sync
AND ar.permissions__can_insert = true
)
);

-- Policy: allow view only if the agent is a member of the asset's sync group with view permission
CREATE POLICY "Group can view entity assets" ON entity.entity_assets
FOR SELECT
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
OR EXISTS (
SELECT 1
FROM auth.agent_sync_group_roles AS ar
WHERE ar.auth__agent_id = auth.current_agent_id()
AND ar.group__sync = entity.entity_assets.group__sync
AND ar.permissions__can_read = true
)
);

-- Policy: allow update only if the agent is a member of the asset's sync group with update permission
CREATE POLICY "Group can update entity assets" ON entity.entity_assets
FOR UPDATE
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
OR EXISTS (
SELECT 1
FROM auth.agent_sync_group_roles AS ar
WHERE ar.auth__agent_id = auth.current_agent_id()
AND ar.group__sync = entity.entity_assets.group__sync
AND ar.permissions__can_update = true
)
);

-- Policy: allow delete only if the agent is a member of the asset's sync group with delete permission
CREATE POLICY "Group can delete entity assets" ON entity.entity_assets
FOR DELETE
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
OR EXISTS (
SELECT 1
FROM auth.agent_sync_group_roles AS ar
WHERE ar.auth__agent_id = auth.current_agent_id()
AND ar.group__sync = entity.entity_assets.group__sync
AND ar.permissions__can_delete = true
)
);


-- 7.4 ENTITY POLICIES
-- ============================================================================
-- Grant table permissions to vircadia_agent_proxy for entities
GRANT SELECT, INSERT, UPDATE, DELETE ON entity.entities TO vircadia_agent_proxy;

CREATE POLICY "entities_read_policy" ON entity.entities
FOR SELECT
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
OR EXISTS (
SELECT 1
FROM auth.active_sync_group_sessions sess
WHERE sess.auth__agent_id = auth.current_agent_id()
AND sess.group__sync = entity.entities.group__sync
AND sess.permissions__can_read = true
)
);

CREATE POLICY "entities_update_policy" ON entity.entities
FOR UPDATE
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
OR EXISTS (
SELECT 1
FROM auth.active_sync_group_sessions sess
WHERE sess.auth__agent_id = auth.current_agent_id()
AND sess.group__sync = entity.entities.group__sync
AND sess.permissions__can_update = true
)
);

CREATE POLICY "entities_insert_policy" ON entity.entities
FOR INSERT
TO PUBLIC
WITH CHECK (
auth.is_admin_agent()
OR auth.is_system_agent()
OR EXISTS (
SELECT 1
FROM auth.active_sync_group_sessions sess
WHERE sess.auth__agent_id = auth.current_agent_id()
AND sess.group__sync = entity.entities.group__sync
AND sess.permissions__can_insert = true
)
);

CREATE POLICY "entities_delete_policy" ON entity.entities
FOR DELETE
TO PUBLIC
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
OR EXISTS (
SELECT 1
FROM auth.active_sync_group_sessions sess
WHERE sess.auth__agent_id = auth.current_agent_id()
AND sess.group__sync = entity.entities.group__sync
AND sess.permissions__can_delete = true
)
);

-- ============================================================================
-- TRIGGERS TO UPDATE TIMESTAMPS WHEN SPECIFIC COLUMNS CHANGE
-- ============================================================================

-- 1. Trigger for entity.entities
CREATE OR REPLACE FUNCTION entity.update_entity_timestamps()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
IF NEW.meta__data IS DISTINCT FROM OLD.meta__data THEN
NEW.meta_data_updated_at = now();
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_entity_timestamps
BEFORE UPDATE ON entity.entities
FOR EACH ROW EXECUTE FUNCTION entity.update_entity_timestamps();

-- 3. Trigger for entity.entity_assets
CREATE OR REPLACE FUNCTION entity.update_asset_timestamps()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
IF NEW.asset__data__bytea IS DISTINCT FROM OLD.asset__data__bytea THEN
NEW.asset__data__bytea_updated_at = now();
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_asset_timestamps
BEFORE UPDATE ON entity.entity_assets
FOR EACH ROW EXECUTE FUNCTION entity.update_asset_timestamps();

-- ============================================================================
-- INDEXES FOR TIMESTAMP-BASED QUERIES
-- ============================================================================

-- 1. Index for entity changes
CREATE INDEX idx_entity_timestamp_changes ON entity.entities
(group__sync,
GREATEST(
meta_data_updated_at,
general__updated_at
))
INCLUDE (general__entity_name);

-- 3. Composite index for asset changes
CREATE INDEX idx_asset_timestamp_changes ON entity.entity_assets
(group__sync,
GREATEST(
general__updated_at
))
INCLUDE (general__asset_file_name);

5. Tick

5_TICK.sql
-- ============================================================================
-- 1. SCHEMA CREATION AND INITIAL PERMISSIONS
-- ============================================================================
CREATE SCHEMA IF NOT EXISTS tick;

-- Initial revocations (we'll grant specific permissions at the end)
REVOKE ALL ON SCHEMA tick FROM PUBLIC, vircadia_agent_proxy;
GRANT USAGE ON SCHEMA tick TO vircadia_agent_proxy;

-- ============================================================================
-- 2. BASE TABLES
-- ============================================================================

-- 2.1 WORLD TICKS TABLE
CREATE TABLE tick.world_ticks (
general__tick_id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
tick__number bigint NOT NULL,
group__sync TEXT NOT NULL REFERENCES auth.sync_groups(general__sync_group),
tick__start_time timestamptz NOT NULL,
tick__end_time timestamptz NOT NULL,
tick__duration_ms double precision NOT NULL,
tick__entity_states_processed int NOT NULL,
tick__is_delayed boolean NOT NULL,
tick__headroom_ms double precision,
tick__time_since_last_tick_ms double precision,

-- DB-specific metrics
tick__db__start_time timestamptz,
tick__db__end_time timestamptz,
tick__db__duration_ms double precision,
tick__db__is_delayed boolean,

-- Manager-specific metrics
tick__manager__start_time timestamptz,
tick__manager__end_time timestamptz,
tick__manager__duration_ms double precision,
tick__manager__is_delayed boolean,

-- Add unique constraint for sync_group + tick number combination
UNIQUE (group__sync, tick__number)
);

-- 2.2 ENTITY STATES TABLE
CREATE TABLE tick.entity_states (
LIKE entity.entities INCLUDING DEFAULTS EXCLUDING CONSTRAINTS,

-- Additional metadata for state tracking
general__tick_id uuid NOT NULL,
general__entity_state_id uuid DEFAULT uuid_generate_v4(),

-- Override the primary key to allow multiple states per entity
CONSTRAINT entity_states_pkey PRIMARY KEY (general__entity_state_id),

-- Add foreign key constraint for sync_group
CONSTRAINT entity_states_sync_group_fkey FOREIGN KEY (group__sync)
REFERENCES auth.sync_groups(general__sync_group),

-- Add foreign key constraint to world_ticks with cascade delete
CONSTRAINT entity_states_tick_fkey FOREIGN KEY (general__tick_id)
REFERENCES tick.world_ticks(general__tick_id) ON DELETE CASCADE
);

-- ============================================================================
-- 3. INDEXES
-- ============================================================================

-- 3.1 WORLD TICKS INDEXES
CREATE INDEX idx_world_ticks_sync_number ON tick.world_ticks (group__sync, tick__number DESC);
CREATE INDEX idx_world_ticks_sync_time ON tick.world_ticks (group__sync, tick__start_time DESC);

-- 3.2 ENTITY STATES INDEXES
CREATE INDEX entity_states_lookup_idx ON tick.entity_states (general__entity_name, general__tick_id);
CREATE INDEX entity_states_tick_idx ON tick.entity_states (general__tick_id);
CREATE INDEX entity_states_sync_group_tick_idx ON tick.entity_states (group__sync, general__tick_id DESC);
CREATE INDEX idx_entity_states_sync_tick_lookup ON tick.entity_states (group__sync, general__tick_id, general__entity_name);
CREATE INDEX idx_entity_states_sync_tick ON tick.entity_states (group__sync, general__tick_id);

-- Fast lookups of entity states by tick and entity ID
CREATE INDEX idx_entity_states_tick_entity_name ON tick.entity_states (general__tick_id, general__entity_name);

-- Optimized index for finding latest ticks by sync group with covering columns
CREATE INDEX idx_world_ticks_sync_number_covering ON tick.world_ticks
(group__sync, tick__number DESC)
INCLUDE (general__tick_id, tick__start_time);

-- Fast timestamp comparisons for entity changes
CREATE INDEX idx_entity_states_updated_at ON tick.entity_states
(group__sync, general__updated_at DESC)
INCLUDE (general__entity_name);

-- Space-efficient BRIN index for time-series data
CREATE INDEX idx_world_ticks_time_brin ON tick.world_ticks USING BRIN (tick__start_time);

-- Composite index for tick + sync group lookup patterns
CREATE INDEX idx_entity_states_sync_tick_composite ON tick.entity_states
(group__sync, general__tick_id)
INCLUDE (general__entity_name, meta__data);

-- ============================================================================
-- 4. FUNCTIONS
-- ============================================================================

-- 4.1 TICK CAPTURE FUNCTION - Updated to include timestamp tracking columns
CREATE OR REPLACE FUNCTION tick.capture_tick_state(
p_sync_group text
) RETURNS TABLE (
general__tick_id uuid,
tick__number bigint,
group__sync text,
tick__start_time timestamptz,
tick__end_time timestamptz,
tick__duration_ms double precision,
tick__entity_states_processed int,
tick__is_delayed boolean,
tick__headroom_ms double precision,
tick__time_since_last_tick_ms double precision,
tick__db__start_time timestamptz,
tick__db__end_time timestamptz,
tick__db__duration_ms double precision,
tick__db__is_delayed boolean
) AS $$
DECLARE
v_start_time timestamptz;
v_last_tick_time timestamptz;
v_tick_number bigint;
v_entity_states_processed int;
v_end_time timestamptz;
v_duration_ms double precision;
v_headroom_ms double precision;
v_is_delayed boolean;
v_time_since_last_tick_ms double precision;
v_tick_id uuid;
v_max_tick_count_buffer integer;
v_db_start_time timestamptz;
v_db_end_time timestamptz;
v_db_duration_ms double precision;
v_db_is_delayed boolean;
BEGIN
-- Initialize timing variables (no global lock)
v_start_time := clock_timestamp();
v_db_start_time := v_start_time; -- Database processing starts now

-- Get max tick count buffer from sync group config
SELECT server__tick__max_tick_count_buffer
INTO v_max_tick_count_buffer
FROM auth.sync_groups
WHERE general__sync_group = p_sync_group;

-- Shorter transaction for tick number acquisition
BEGIN
-- Get last tick information - lock only what we need
SELECT
wt.tick__start_time,
wt.tick__number
INTO
v_last_tick_time,
v_tick_number
FROM tick.world_ticks wt
WHERE wt.group__sync = p_sync_group
ORDER BY wt.tick__number DESC
LIMIT 1
FOR UPDATE;

IF v_tick_number IS NULL THEN
v_tick_number := 1;
ELSE
v_tick_number := v_tick_number + 1;
END IF;
END;

-- Calculate time since last tick
IF v_last_tick_time IS NOT NULL THEN
v_time_since_last_tick_ms := EXTRACT(EPOCH FROM (v_start_time - v_last_tick_time)) * 1000;
END IF;

-- Clean up in a separate transaction using a more targeted approach
BEGIN
DELETE FROM tick.world_ticks wt
WHERE wt.group__sync = p_sync_group
AND wt.general__tick_id IN (
SELECT wt2.general__tick_id
FROM tick.world_ticks wt2
WHERE wt2.group__sync = p_sync_group
AND (
SELECT COUNT(*)
FROM tick.world_ticks wt3
WHERE wt3.group__sync = wt2.group__sync
AND wt3.tick__number > wt2.tick__number
) >= v_max_tick_count_buffer
);
END;

-- Insert new tick record (initial)
v_tick_id := uuid_generate_v4();
INSERT INTO tick.world_ticks (
general__tick_id,
tick__number,
group__sync,
tick__start_time,
tick__end_time,
tick__duration_ms,
tick__entity_states_processed,
tick__is_delayed,
tick__headroom_ms,
tick__time_since_last_tick_ms,
tick__db__start_time,
tick__db__end_time,
tick__db__duration_ms,
tick__db__is_delayed
) VALUES (
v_tick_id,
v_tick_number,
p_sync_group,
v_start_time,
clock_timestamp(),
0,
0,
false,
0,
v_time_since_last_tick_ms,
v_db_start_time,
null, -- Will update at the end
0,
false
);

-- Capture entity states (now including timestamp columns)
WITH entity_snapshot AS (
INSERT INTO tick.entity_states (
general__entity_name,
general__semantic_version,
group__load_priority,
general__initialized_at,
general__initialized_by,
meta__data,
group__sync,
general__created_at,
general__created_by,
general__updated_at,
general__updated_by,
general__tick_id,
-- Include the timestamp columns from the source table if they exist
meta_data_updated_at
)
SELECT
e.general__entity_name,
e.general__semantic_version,
e.group__load_priority,
e.general__initialized_at,
e.general__initialized_by,
e.meta__data,
e.group__sync,
e.general__created_at,
e.general__created_by,
e.general__updated_at,
e.general__updated_by,
v_tick_id,
-- Copy timestamp columns if they exist in the source table
e.meta_data_updated_at
FROM entity.entities e
WHERE e.group__sync = p_sync_group
RETURNING 1
)
SELECT COUNT(*) INTO v_entity_states_processed FROM entity_snapshot;

-- Calculate tick duration, delay & headroom
v_end_time := clock_timestamp();
v_duration_ms := EXTRACT(EPOCH FROM (v_end_time - v_start_time)) * 1000;

-- Calculate DB-specific metrics
v_db_end_time := v_end_time;
v_db_duration_ms := EXTRACT(EPOCH FROM (v_db_end_time - v_db_start_time)) * 1000;

SELECT
v_duration_ms > sg.server__tick__rate_ms AS is_delayed,
sg.server__tick__rate_ms - v_duration_ms AS headroom_ms,
v_db_duration_ms > sg.server__tick__rate_ms AS db_is_delayed
INTO v_is_delayed, v_headroom_ms, v_db_is_delayed
FROM auth.sync_groups sg
WHERE sg.general__sync_group = p_sync_group;

-- Update tick record with final metrics
UPDATE tick.world_ticks wt
SET
tick__end_time = v_end_time,
tick__duration_ms = v_duration_ms,
tick__entity_states_processed = v_entity_states_processed,
tick__is_delayed = v_is_delayed,
tick__headroom_ms = v_headroom_ms,
tick__db__end_time = v_db_end_time,
tick__db__duration_ms = v_db_duration_ms,
tick__db__is_delayed = v_db_is_delayed
WHERE wt.general__tick_id = v_tick_id;

-- Send notification that a tick has been captured
PERFORM pg_notify(
'tick_captured',
json_build_object(
'syncGroup', p_sync_group,
'tickId', v_tick_id,
'tickNumber', v_tick_number
)::text
);

-- Return the captured tick record
RETURN QUERY
SELECT
v_tick_id,
v_tick_number,
p_sync_group,
v_start_time,
v_end_time,
v_duration_ms,
v_entity_states_processed,
v_is_delayed,
v_headroom_ms,
v_time_since_last_tick_ms,
v_db_start_time,
v_db_end_time,
v_db_duration_ms,
v_db_is_delayed;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- ============================================================================
-- 5. TRIGGERS
-- ============================================================================

-- 5.1 ENABLE ROW LEVEL SECURITY ON ALL TABLES
ALTER TABLE tick.world_ticks ENABLE ROW LEVEL SECURITY;
ALTER TABLE tick.entity_states ENABLE ROW LEVEL SECURITY;

-- ============================================================================
-- 6. POLICIES
-- ============================================================================

-- 6.1 WORLD TICKS POLICIES
CREATE POLICY "world_ticks_read_policy" ON tick.world_ticks
FOR SELECT
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
);

CREATE POLICY "world_ticks_update_policy" ON tick.world_ticks
FOR UPDATE
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
);

CREATE POLICY "world_ticks_insert_policy" ON tick.world_ticks
FOR INSERT
WITH CHECK (
auth.is_admin_agent()
OR auth.is_system_agent()
);

CREATE POLICY "world_ticks_delete_policy" ON tick.world_ticks
FOR DELETE
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
);

-- 6.2 ENTITY STATES POLICIES
CREATE POLICY "entity_states_read_policy" ON tick.entity_states
FOR SELECT
USING (
auth.is_admin_agent()
OR auth.is_system_agent()
OR EXISTS (
SELECT 1
FROM auth.active_sync_group_sessions sess
WHERE sess.auth__agent_id = auth.current_agent_id()
AND sess.group__sync = tick.entity_states.group__sync
)
);

CREATE POLICY "entity_states_update_policy" ON tick.entity_states
FOR UPDATE
USING (auth.is_admin_agent());

CREATE POLICY "entity_states_insert_policy" ON tick.entity_states
FOR INSERT
WITH CHECK (auth.is_admin_agent());

CREATE POLICY "entity_states_delete_policy" ON tick.entity_states
FOR DELETE
USING (auth.is_admin_agent());

-- ============================================================================
-- 7. PERMISSIONS
-- ============================================================================

-- Revoke all permissions first
REVOKE ALL ON SCHEMA tick FROM PUBLIC, vircadia_agent_proxy;

-- Grant schema usage
GRANT USAGE ON SCHEMA tick TO vircadia_agent_proxy;

-- Grant table permissions
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA tick TO vircadia_agent_proxy;

-- Grant function permissions
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA tick TO vircadia_agent_proxy;