Files
sales-trainer/docs/engineering-log/2026-08-15-s4-2-schema-foundation.md

7.9 KiB

S4.2 — Relational schema foundation

Date: 2026-08-15 Status: implementation + local verification complete; dependency lock wired; offline-dialect remediation verified; code/schema/dependency review passed; PostgreSQL runtime/cutover blocked

Scope actually changed

  • backend/requirements.txt — pinned direct runtime/test dependencies, including SQLAlchemy, Alembic, and psycopg PostgreSQL driver.
  • backend/requirements.lock.txt — uv-generated transitive lock with hashes; Docker and backend CI install this file with --require-hashes.
  • backend/app/config.py — optional DATABASE_URL configuration.
  • backend/app/db.py — engine/session helpers with SQLite foreign-key enforcement.
  • backend/app/models/entities.py and backend/app/models/__init__.py — tenant-safe relational entities for organizations, users, groups, personas, sessions, messages, and audit events.
  • backend/alembic.ini, backend/migrations/env.py, and backend/migrations/versions/0001_initial_schema.py / 0002_audit_actor_tenant_check.py — portable migration wiring and audit tenant hardening.
  • backend/tests/test_db_schema.py — schema, tenant-FK, one-shot uniqueness, and migration up/down contract tests.
  • Generated frontend/dist/ artifacts removed from the worktree; the repository policy is source-only frontend plus reproducible Docker/CI builds.

Tests written first

  • test_schema_contains_core_tables_and_tenant_keys: failed before implementation because app.db/Alembic were unavailable; passes after implementation.
  • test_constraints_preserve_tenant_links_and_attempt_uniqueness: initially exposed SQLite FK fixture configuration and ORM insert ordering; after fixing the fixture it passed, then a RED regression was added for cross-org audit actors.
  • Cross-org audit actor regression: failed before the composite actor/org FK; passes after the model and migration were updated together.
  • Partial-null audit actor regression: failed before ck_audit_actor_requires_org; passes after the check constraint was added to ORM metadata and migration 0002.
  • Raw SQL default regression: failed against Base.metadata.create_all before model server_default values were aligned with Alembic; passes after alignment.
  • test_alembic_migration_up_and_down: failed before Alembic was installed; passes after the migration foundation was added.
  • Root-CWD migration regression: failed with CWD-relative script_location; passes after using %(here)s paths in alembic.ini.

Verification evidence

  • cd backend && ./.venv/bin/python -m pytest tests/test_db_schema.py -q8 passed after the offline unsupported-dialect regression was added.
  • cd /Users/kunthawat/Gitea/Sales Trainer && backend/.venv/bin/python -m pytest -q209 passed after the offline guard remediation.
  • Clean Python 3.11 temporary venv installed backend/requirements.lock.txt with pip --require-hashes; full backend suite → 195 passed in 38.95s.
  • uv regenerated the lock from pinned requirements: 42 packages, 810 lines; normalized package-entry comparison was identical.
  • Dockerfile and .gitea/workflows/ci.yml now install the hash-locked requirements file.
  • cd backend && for script in scripts/test_*.py; do ./.venv/bin/python "$script"; done12 executable scripts passed.
  • Temporary SQLite alembic upgrade head + alembic check + alembic downgrade base → passed after the JSON-parity and audit actor FK changes.
  • Alembic upgrade/check/downgrade → passed from both repository-root and backend/ CWDs, including migration 0002.
  • Bundled schema parity probe found only the expected alembic_version bookkeeping table difference; MySQL partial-index weakening is now prevented on both online and offline Alembic paths by the shared dialect guard and regression tests.
  • python -m compileall -q app migrations tests → passed.
  • git diff --check → passed.
  • Added-line static security scan → added_line_findings=[].
  • ruff and mypy were not installed in the local environment; both were skipped and not represented as passing.
  • Full-stack contract audit → API contract clean; no committed/generated frontend/dist remains, so the Docker/CI build path is unambiguous.

Security/data notes

  • Secrets exposed: no; scans returned no private keys, token literals, shell execution, or unsafe deserialization patterns in added lines.
  • Production data touched: no; all schema tests use temporary SQLite databases.
  • Migration/rollback: schema migration up/down verified on temporary SQLite. PostgreSQL execution, importer, parity checks, rollback rehearsal, and runtime repository cutover remain unverified.
  • pip check still reports a pre-existing shared-venv conflict: alibabacloud-tea-openapi 0.4.4 requires cryptography<47, while the environment has cryptography 50.0.0. This is not caused by the S4.2 requirements and was not changed.

Review findings resolved locally

  • Nullable audit tenant link: fixed with ck_audit_actor_requires_org in ORM metadata and migration 0002.
  • CWD-relative Alembic paths: fixed with %(here)s/migrations and %(here)s prepend_sys_path; config URL now delegates to database_url().
  • ORM/migration default drift: model columns now carry matching server defaults while retaining ORM defaults.
  • Dependency resolution drift: direct requirements are pinned and all transitive artifacts are hash-locked; Docker/CI no longer resolve an unbounded requirements file.
  • Unsupported partial-index dialect: shared validate_database_url() is used by create_db_engine() and Alembic offline URL resolution, so MySQL is rejected before connection or SQL rendering and cannot silently weaken non-preview attempt uniqueness.

Remaining blockers

  • The earlier reviewer deleg_e672880a returned valid five-key JSON with passed=true for the schema/dependency packet; its final closure was held after the later exact-current offline-path finding from deleg_c2e728d8.
  • The offline-path blocker is remediated and verified locally; fresh exact-current reviewer deleg_40e8edf9 returned valid five-key JSON with passed=true, empty blocking arrays, and five non-blocking suggestions.
  • Docker is unavailable on this Mac; no image build or container smoke was claimed.
  • PostgreSQL service, JSON importer, repository wiring, Redis/DB rate-limit store, real-provider QA, and production operation remain pending.
  • Non-blocking reviewer hardening suggestions remain: immutable CI action/base-image pinning, isolated CI-only credentials, preserving audit attribution semantics on actor deletion, and broader upgraded-database parity assertions.

Exact next action

  1. Continue S4.3 one aggregate at a time; do not delete JSON stores or wire production cutover until importer/parity/rollback gates pass.
  2. Resolve the remaining legacy security and S4.3 independent-review gates against the exact current tree.
  3. Keep PostgreSQL, Docker, Redis, real-provider QA, and restricted live-operation gates explicitly separate from local approval.

Independent review verdict

  • deleg_e672880a — valid schema/dependency review; passed=true; security_concerns=[]; logic_errors=[]; combined with the post-remediation review below.
  • deleg_c2e728d8 — valid exact-current review; passed=false for the offline unsupported-dialect path; remediation is recorded in 2026-08-15-s4-3-offline-dialect-remediation.md.
  • deleg_40e8edf9 — fresh exact-current post-remediation review; passed=true; security_concerns=[]; logic_errors=[].
  • Non-blocking suggestions: add explicit before-commit and immutable-field regression assertions, document global username/email uniqueness intent, map duplicate-key errors, and run live PostgreSQL smoke before cutover.
  • This closes the code/schema/dependency review scope only. It is not PostgreSQL, Docker, production cutover, or deployment approval.

Files explicitly not to touch for this packet

  • .env
  • credential helpers or production configuration
  • unrelated product/runtime routes
  • JSON stores and production data