Repository navigation
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## microdata-weight-rules #3979 +/- ##
=========================================================
Coverage ? 48.04%
=========================================================
Files ? 239
Lines ? 14119
Branches ? 2222
=========================================================
Hits ? 6783
Misses ? 6772
Partials ? 564 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
anth-volk
force-pushed
the
sqlalchemy-alembic-database-rule
branch
from
October 20, 2026 14:01
e3c1190 to
2b3f7e7
Compare
anth-volk
added this pull request to stack #3917
October 20, 2026 14:02
anth-volk
force-pushed
the
sqlalchemy-alembic-database-rule
branch
from
October 20, 2026 16:31
2b3f7e7 to
1955a42
Compare
anth-volk
force-pushed
the
sqlalchemy-alembic-database-rule
branch
from
October 20, 2026 18:01
1955a42 to
4b3743c
Compare
anth-volk
force-pushed
the
sqlalchemy-alembic-database-rule
branch
4 times, most recently
from
October 21, 2026 15:49
ed1402d to
1c6f05b
Compare
anth-volk
force-pushed
the
sqlalchemy-alembic-database-rule
branch
3 times, most recently
from
October 21, 2026 22:31
f4677e9 to
6e6c1d1
Compare
…changes Register api.data.sqlalchemy-and-alembic-only as an enforced mechanical rule. tests/unit/data/test_database_access_boundaries.py parses every non-test module under policyengine_api/, scripts/, migrations/ and .github/ (excluding migrations/*/versions/) and fails on: - imports of psycopg, psycopg2, pymysql, MySQLdb, mysql.connector, sqlite3, asyncpg, pg8000, aiosqlite or aiomysql, including through importlib.import_module, and access to the driver connection beneath SQLAlchemy (driver_connection, dbapi_connection, raw_connection()); - create_all, drop_all, create or drop on a table, index or sequence, SQLAlchemy DDL constructs, Alembic operations outside migrations/, and string literals other than docstrings that begin a CREATE, ALTER or DROP of a table, type, schema, database, index, sequence, view or extension, or a TRUNCATE. The only allowed findings are in catalog publication staging: the COPY step through the psycopg connection beneath a SQLAlchemy Connection, and STAGING_METADATA.create_all for temporary tables. A separate test checks that every staging table compiles to CREATE TEMPORARY TABLE ... ON COMMIT DROP. The comparison fails both when a finding is undeclared and when a declaration is no longer used. The proof fixtures reproduce two past cases from this repository: the direct psycopg.connect calls in the removed Stage 12 provisioning script and the runtime V1Base.metadata.create_all in the local bootstrap. The rule is explained in docs/engineering/skills/alembic-migrations.md, which also states that Redis is out of scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
force-pushed
the
sqlalchemy-alembic-database-rule
branch
from
October 22, 2026 17:43
6e6c1d1 to
6eb793d
Compare
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3978
Summary
Stack: this PR is the top of stack #3917, based on #3938 (
microdata-weight-rules). It was first opened againstmasterand then moved onto the stack, so it lands after the ruff work.Adds an enforced rule,
api.data.sqlalchemy-and-alembic-only:It is a mechanical-tier rule in the "Runtime persistence and route boundaries" category, run by the existing
pr.ymltestjob (make test).How the guard finds violations
tests/unit/data/test_database_access_boundaries.pywalks every Python module underpolicyengine_api/,scripts/,migrations/and.github/. It skips tests (anytestsdirectory) and the generated revisions undermigrations/*/versions/, and parses each module withast, so comments and docstrings are never read as imports, calls or SQL. It walks the tree the same way theapi.data.runtime-sql-confinedguard does (test_runtime_sql_boundaries.py): collect the offenders, then compare them with the expected set. That guard matches facade-call substrings in the raw source and has no SQL-keyword matching to reuse, so this one checks parsed string literals instead.test_non_test_code_reaches_database_drivers_only_where_declared):psycopg,psycopg2,pymysql,MySQLdb,mysql.connector,sqlite3,asyncpg,pg8000,aiosqliteoraiomysql, includingimportlib.import_moduleand__import__calls with a constant name;driver_connectionanddbapi_connectionattributes, andraw_connection()calls.test_non_test_code_changes_schema_only_through_alembic):create_all()anddrop_all();create()ordrop()on a table, index or sequence. That means a name the module assigns fromTable(...),Index(...)orSequence(...), a loop variable over those or overmetadata.tables/sorted_tables,Model.__table__,metadata.tables[...], or any call that passescheckfirst=.URL.create(...)and other unrelatedcreatemethods are not flagged;DDL,CreateTable,DropTable,CreateIndex,DropIndex,CreateSchema,DropSchema,CreateSequence,DropSequence,AddConstraint,DropConstraint);op.*calls,alembic.op/alembic.operationsimports) outsidemigrations/;CREATE,ALTERorDROPof a table, type, schema, database, index, sequence, view or extension, or aTRUNCATE, starts the string, a line in it, or a statement after;. Matching ignores case. Requiring the start of a statement keeps messages such as "failed to create table rows" from matching.Each test compares the findings, as (module, construct) pairs, with a declared set. It fails on any finding that isn't declared, and on any declaration that nothing in the code uses any more, so the declared exceptions can't grow or go stale unnoticed.
Declared exceptions
Both are in
policyengine_api/data/v2/catalog/publication_staging.py:import psycopg.sql,import psycopg.types.json.Jsonbandattribute .driver_connection. Catalog publication streams rows with PostgresCOPY ... FROM STDIN, and SQLAlchemy has no COPY API.copy_rowsuses the psycopg connection beneath a SQLAlchemyConnectionthat SQLAlchemy opened and still manages, so the transaction stays SQLAlchemy's.call STAGING_METADATA.create_all(). The tables are declaredTEMPORARYwithON COMMIT DROP, so Postgres drops them when the publishing transaction commits, and they are not persistent schema.test_publication_staging_tables_are_temporary_and_dropped_at_commitcompiles every table onSTAGING_METADATAfor Postgres and requiresCREATE TEMPORARY TABLE ... ON COMMIT DROP.No other violations exist, either on
masteror at the top of the stack (#3938), so the guard passes without a baseline.Out of scope
Redis (
policyengine_api/runtime_cache/) is a key-value cache, not a relational database. The rule and its documentation say so.Proof
rules/fixtures/api.data.sqlalchemy-and-alembic-only/bad_database_access.pyreproduces two past cases from this repository: the directpsycopg.connect(...)calls in the Stage 12 provisioning script removed in d8bd721, and the runtimeV1Base.metadata.create_all(engine)from the local bootstrap added in 6c78041.good_database_access.pyqueries through a SQLAlchemy session and changes no schema.test_rule_proof_sqlalchemy_and_alembic_onlychecks that the bad fixture yields exactlyimport psycopgandcall V1Base.metadata.create_all(), and that the good fixture yields nothing.[rule.proof]fields point at both fixtures and the test, andrules.mdnow lists the rule as proven.Related rules it complements
api.data.runtime-sql-confined(raw SQL stays in the data-access layer);api.services.no-direct-queries(services delegate persistence);api.v2.single-orm-layer(v2 uses SQLModel only);api.migration.alembic-v1-qualifiedandapi.migration.alembic-v2-qualified(CI applies the revisions to disposable MySQL and Postgres databases);api.migration.alembic-autogenerated-only(doc-only: revisions are autogenerated);api.startup.no-import-side-effects(importing and starting the application opens no connections).None of them forbade a direct driver connection, or a runtime schema change, outside the raw-SQL boundary.
Other changes
docs/engineering/skills/alembic-migrations.md(the registryrationale), and rule citations in two existing paragraphs of that doc.AGENTS.mdpointer to that doc now also covers "code that connects to a database".docs/generated/rules.mdand theAGENTS.mdRules block.This PR adds no inline suppressions and changes no application code.
Testing
After moving onto #3938 (head
2b3f7e77e):docs/generated/rules.md, which was regenerated.ruff format --check .andruff check .(0.15.12) are clean.python -m scripts.rules guard --base-ref origin/microdata-weight-rulespass.uv lock --checkpasses.pytest tests/unit/data/test_database_access_boundaries.py: 4 passed.CI completeruns, and it fails as designed. Its full run starts once the PRs below it merge.Before the move, against
master:All with the Python 3.12 environment from
uv sync --frozen --extra dev --python 3.12:ruff format --check .: 598 files already formattedruff check .: all checks passedpython scripts/run_quality_guards.py: migration-contracts, rules-registry and ci-scripts passedpython -m scripts.rules guard --base-ref origin/master: passed (notes that the rule is new)python -m unittest discover -s scripts/rules/tests -t .: 80 tests OKpytest tests/unit/data/test_database_access_boundaries.py: 4 passedpytest tests/unit: 2690 passedI also ran throwaway scripts, not committed, against synthetic sources. They confirmed that the guard reports each construct listed above, ignores
URL.create, prose, docstrings and SQLAlchemy dialect imports, and fails when a declared exception is removed from or added to the declared set. The temporary-table test fails for a staging table withoutTEMPORARYand for one withoutON COMMIT DROP.🤖 Generated with Claude Code