docs(l1): Phase 1 acceptance validation report

Full backend suite (1325/1325 passing, xdist) + L1-specific tests
(57/57) + L1 RLS tests (8/8) + frontend build (tsc clean, vite clean)
+ migration roundtrip results. Per-line checklist against spec §15.
Known Phase 2/3 items explicitly deferred per plan scope section.

fix(test): RLS fixture users INSERT missing NOT NULL columns
  test_l1_rls.py and test_rls_isolation.py seeded users without the
  five NOT NULL columns added in prior migrations (is_super_admin,
  is_team_admin, is_service_account, must_change_password, timezone).
  Also adds DROP SCHEMA before alembic upgrade in _ensure_rls_schema
  to prevent DuplicateTable errors when create_all tables are present.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 16:07:23 -04:00
parent 6937bcaabd
commit 10b5d4e9b0
3 changed files with 330 additions and 14 deletions

View File

@@ -23,6 +23,7 @@ from pathlib import Path
from urllib.parse import unquote, urlsplit
import asyncpg
import psycopg2
import pytest
import pytest_asyncio
@@ -80,7 +81,22 @@ def _ensure_rls_schema():
public schema using Base.metadata.create_all, which does not enable RLS
or create DB roles. This fixture re-runs 'alembic upgrade head' so that
the full migration-managed schema (including RLS policies) is in place.
We drop and recreate the public schema first so that any tables left behind
by a prior create_all-based test_db run don't conflict with alembic's
migration tracking.
"""
# Drop and recreate the schema to ensure a clean slate for alembic.
admin_dsn = dict(
host=_DB_HOST, port=_DB_PORT, dbname=_DB_NAME,
user=_ADMIN_USER, password=_ADMIN_PASSWORD,
)
with psycopg2.connect(**admin_dsn) as conn:
conn.autocommit = True
with conn.cursor() as cur:
cur.execute("DROP SCHEMA public CASCADE")
cur.execute("CREATE SCHEMA public")
backend_dir = Path(__file__).parent.parent
env = os.environ.copy()
env["DATABASE_URL"] = _DATABASE_TEST_URL
@@ -131,15 +147,18 @@ async def seed_rls_test_data(admin_conn):
user_b_id = str(uuid.uuid4())
await admin_conn.execute(f"""
INSERT INTO users (
id, email, password_hash, name, role, is_active, account_id,
account_role, created_at
id, email, password_hash, name, role,
is_super_admin, is_team_admin, is_service_account, must_change_password,
is_active, account_id, account_role, timezone, created_at
) VALUES
('{user_a_id}', 'rls-user-a@example.com',
'placeholder', 'RLS User A', 'engineer', TRUE,
'{ACCOUNT_A_ID}', 'engineer', NOW()),
'placeholder', 'RLS User A', 'engineer',
FALSE, FALSE, FALSE, FALSE,
TRUE, '{ACCOUNT_A_ID}', 'engineer', 'UTC', NOW()),
('{user_b_id}', 'rls-user-b@example.com',
'placeholder', 'RLS User B', 'engineer', TRUE,
'{ACCOUNT_B_ID}', 'engineer', NOW())
'placeholder', 'RLS User B', 'engineer',
FALSE, FALSE, FALSE, FALSE,
TRUE, '{ACCOUNT_B_ID}', 'engineer', 'UTC', NOW())
ON CONFLICT (email) DO NOTHING
""")