Skip to content

Assign every v2 route and service file to a layer and enforce the v2 layer rules - #4191

Merged
anth-volk merged 16 commits into
masterfrom
v2-architecture-layer-check
Nov 5, 2026
Merged

anth-volk merged 16 commits into
masterfrom
v2-architecture-layer-check

Conversation

@anth-volk

@anth-volk anth-volk commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #4190

Corrected after merge. This PR merged at e19f0d5. The fixes from its last self-review round (0dc5686 and 12889c2) were pushed after the merge, so they aren't in it; they are in #4205. This description now says what merged, and notes where #4205 changes it.

Summary

Adds the v2-layers quality guard. It gives every API v2 route and service file a layer from its file name alone, and checks each layer's imports and calls. It turns three doc-only v2 rules into checked ones and adds three rules. This is step 2.6 of the agent-proof code-quality plan (its AST part), and builds on #4184 (Import Linter, merged).

Eight files whose names gave no layer are renamed to fit. Open pull requests that import them need updating; see below.

What the guard checks

scripts/rules/v2_layers.py (786 lines) assigns the layers, reads exceptions, scans the files, runs the per-file import and call checks and compares findings with the baselines. #4205 moves the per-file checks to a new scripts/rules/v2_layer_checks.py, so that both stay under the 800-line limit. The guard uses the standard library only, like the rest of the rules tool, and scripts/run_quality_guards.py runs the guard in the quality-guards job of pr.yml.

Rule Checks Status
api.v2.files-assigned-to-layers (new) Every .py file under policyengine_api/services/v2 and policyengine_api/fastapi_routes/v2 gets a layer from its file name. A file whose name gives none fails. enforced
api.v2.routes-no-sessions-or-sql (was doc-only) Applies to every module under fastapi_routes/v2.
  • No forbidden imports: SQLAlchemy, SQLModel, a connector, a database_session module, the v1 ORM module policyengine_api.data.orm or policyengine_api.data.v2.database.
  • No SQLAlchemy or SQLModel calls, except its exception classes.
  • No method calls on a session, including a database_session container's read() or transaction().
  • No session factories, including an injected *session_factory.
ratcheted, 4 tolerated
api.v2.services-no-sql (was doc-only) Applies to services.py modules.
  • From SQLAlchemy and SQLModel, they import only the Session type and sqlalchemy.exc.
  • They call no SQLAlchemy or SQLModel function except its exception classes and session factories. This catches and_, or_, func and col, even through import sqlalchemy.exc.
  • No data-access session methods: exec, execute, add, get, get_one, the bulk methods, connection, delete and so on.
  • No FastAPI or Starlette imports.
Services may open sessions, which the rule's statement doesn't forbid.
enforced, 0 today
api.v2.connectors-never-commit (was doc-only) Applies to connector modules and a database_connectors/__init__.py.
  • No commit, rollback, close, begin, begin_nested, reset or invalidate on a session.
  • They open no session: no session factory, no injected session_factory, no container read() or transaction().
  • No imports of the v1 ORM, the v2 database or a database_session module.
enforced, 0 today
api.v2.session-modules-no-data-access (new) database_session.py modules sit directly in their resource package.
  • No data-access session methods (add, get, merge, exec, execute and so on).
  • No SQL builders.
  • No connector imports.
They open, commit, roll back and close sessions.
enforced, 0 today
api.v2.pure-modules-no-session-calls (new) Applies to validators, transformations, types, process-state modules, package __init__ files, and files under services/v2 that a [[rule.exception]] names.
  • From SQLAlchemy and SQLModel, they import only the Session type.
  • No connector, session, v1 ORM or v2 database imports.
  • No method calls on a session, no SQLAlchemy or SQLModel calls and no session factories.
enforced, 0 today

The fourth doc-only rule, api.v2.connectors-no-cross-calls, became ratcheted in #4184 through Import Linter's independence contract.

Layers by file name (no manifest):

  • Under services/v2/<resource>/:
    • services.py, validators.py, transformations.py and types.py;
    • process_state.py, for data a service keeps for the process;
    • database_session.py, which must sit directly in its resource package, where api.v2.session-modules-no-queries (pytest) also checks it;
    • database_connectors/<creates|reads|updates|deletes>[_name].py.
  • Under fastapi_routes/v2/:
    • routes.py and <name>_routes.py;
    • <name>_models.py, for request, response and document schemas;
    • <name>_support.py, for HTTP code the route modules share.
  • __init__.py anywhere.

The fix message for an unassigned file lists only the names that give a layer in its directory. It doesn't say where each must sit (for example, database_session.py only directly in a resource package); #4205 adds that.

A file that can't be renamed needs a [[rule.exception]] for api.v2.files-assigned-to-layers in rules/registry.toml, with a path, a reason and an expires date. The fix message and v2-code-organization.md ask for these. None is needed today.

  • What merged doesn't limit exceptions further; Make v2 file-layer exceptions expire and cover whole files #4205 fixes these gaps:
    • The registry loader requires expires only when an exception sets neither category nor removal, and the guard doesn't check for it. So an exception that sets either and no expires is applied and never ends.
    • The guard doesn't read match, so an exception with match still covers the whole file.
    • The guard applies an exception for a module in a database_connectors package, though api.v2.crud-checks-cover-every-resource still fails a connector module that isn't named after its operation.
  • An exception with an expires date fails the rules guard once that date passes, through the registry's existing expiry check.
  • An exception chooses no layer. The file gets the route checks under fastapi_routes/v2, or the validator, transformation and types checks under services/v2. The latter don't forbid FastAPI imports, unlike the service checks.
  • The guard fails on an exception for:
    • a missing file;
    • a file whose name already gives it a layer;
    • a glob;
    • a path outside the two directories.

Renamed (8 files), with every import updated:

Before After
services/v2/metadata/selected_catalogs.py process_state.py
fastapi_routes/v2/errors.py errors_support.py
fastapi_routes/v2/user_auth.py user_auth_support.py
fastapi_routes/v2/user_resources.py user_resources_support.py
fastapi_routes/v2/metadata/common.py route_support.py
fastapi_routes/v2/metadata/query_dependencies.py query_support.py
fastapi_routes/v2/metadata/response_cache.py response_cache_support.py
fastapi_routes/v2/metadata/search_file_response.py search_file_support.py

metadata/common.py became route_support.py rather than common_support.py: it runs the metadata reads for the route modules, maps their failures to HTTP, and sets the caching headers.

Effects of the renames:

  • /v2/openapi.json is unchanged.
  • The logger in user_resources_support.py is named after its module, so its name changes from policyengine_api.fastapi_routes.v2.user_resources to ...user_resources_support. No tracked log filter uses the old name.
  • households/document_models.py already matched *_models.py.

How calls are recognised.

  • Session receivers. A method call counts as a session call only when its receiver is named session or <name>_session.
    • These don't count: the .session of a request or websocket (Starlette's session data, for receivers named request, req, websocket, ws or *_request), and HTTP or client sessions such as http_session, requests_session and _client_session.
    • request_session and request_context.session do count.
    • set.add, cache.get, sessions_router.get and session_ids.add don't count.
  • Routes and the pure layers may call no method on a session at all.
  • SQL builders. In routes, services and the pure layers, any call into SQLAlchemy or SQLModel counts, except its exception classes. So does <Model>.__table__.<builder>().
  • Session factories. Any call into policyengine_api.data.orm or policyengine_api.data.v2.database counts, as do SQLAlchemy's Session, sessionmaker and create_engine, and an injected *session_factory (session_factory(), self._session_factory(), session_factory.begin()).
  • Imports. Relative imports and from package import submodule are resolved. Imports made only under TYPE_CHECKING count (decided 2026-11-04).

Tolerated (baseline rules/baselines/v2-layers-routes.txt, 4 entries, one per occurrence).

  • These import sqlalchemy.exc to catch SQLAlchemyError:
    • countries/routes.py
    • households/routes.py
    • policies/routes.py
    • user_resources_support.py
  • I counted these as violations rather than allowing exception imports in routes, which would mean changing the rule's statement. The fix is for the services to raise typed failures.
  • A new finding fails, and so does a baseline entry that no longer occurs.

Limits.

  • A session object bound to a name without session in it isn't recognised.
  • An alias of a model's table (TABLE = Policy.__table__) isn't recognised.
  • A call made through a name re-exported by another module isn't resolved.

Import Linter (#4184) covers the import side transitively.

Open pull requests this affects

I scanned each open PR's head. Once this merges, each needs these changes. The registry baselines can't grow, so each PR must clear its own new findings.

No other open PR adds files under these directories or refers to the renamed modules.

Docs and registry

  • v2-code-organization.md has a "File layers" section on the naming rules, exceptions and how calls are recognised. Its HTTP adapter layout uses the new names, and it cites each rule id where it states the rule.
  • docs/generated/rules.md and AGENTS.md are regenerated.
  • Each rule has proof fixture trees under rules/fixtures/<rule id>/. They include a route that imports a connector, a services file with no layer, and excepted files that get the route or pure-module checks. Make v2 file-layer exceptions expire and cover whole files #4205 adds a route-side file with no layer.

CI and environment variables

  • No workflow changes. The guard runs in the existing "Run quality guards" step, with plain python and no project dependencies. Its proofs run in the existing "Test the rules registry tool" step.
  • The guard reads no environment variable. It runs git ls-files, through the file-length guard's helper, to list tracked files.

Testing

Each change came test first, in a commit that failed before the change that follows it:

Failing commit Change
0a8fbe1 (the guard didn't exist) 0240ece
0af97a1 92c10f8
c1e8e66 5ff9ea6
51005e0 efb919d
a4b2b79 (name-only layers, exceptions and the renamed modules' imports) 36fda35
1780783 (rule ids in proofs, exception guidance) 8b441b8
e8ce312 (the exception guidance asks for an expires date) 9b621b1

0dc5686 and 12889c2 (exceptions expire and cover whole files; connector names; fix-message placement; splitting the guard) were pushed after the merge and are in #4205.

Expectations changed in implementing commits, each because of an intended change:

  • 0240ece: from .. import database_session (not ...) names the resource's session module.
  • 92c10f8: services now also report imports sqlalchemy for import sqlalchemy as sa.
  • 5ff9ea6: pure modules and the pure proof also report their sqlmodel.select import.
  • 36fda35: one import in test_metadata_route_caching.py was reordered, formatting only.
  • 8b441b8: the six bad proof fixtures' expected_findings gained their rule ids, the form 1780783's proofs compare. The test helper's generated exceptions gained the expires date that 1780783's clean-load check requires. Those were test-side failures. The guard change in this commit fixed the two fix-message tests.

e19f0d5 merges master (with #4178's declared-dependencies guard) into this branch. Only scripts/run_quality_guards.py (both guards kept) and the generated docs/generated/rules.md conflicted.

scripts/rules/tests/test_v2_layers.py has 37 tests:

  • the six proofs: each tree gives exactly its expected <rule id>: <path> <detail> findings;
  • name-only assignment, including process_state.py, *_models.py and *_support.py;
  • exceptions: no layer but the route or pure-module checks, and validation of each exception's path (one existing file under the two directories, whose name gives no layer);
  • directory-specific fix messages;
  • every file in the repository has a layer;
  • receiver-aware session calls;
  • SQLAlchemy calls and imports per layer;
  • TYPE_CHECKING, relative and package imports;
  • session factories and containers;
  • __table__ builders;
  • web sessions;
  • baseline counting, stale and duplicate entries.

After the changes:

  • the 37 tests pass on Python 3.12 and 3.14;
  • the 9 unit test files whose imports changed pass (243 tests);
  • the 2 edited integration tests skip here, because V2_MIGRATION_DATABASE_URL isn't set; CI's cross-database job runs them;
  • python scripts/run_quality_guards.py, python -m scripts.rules guard --base-ref origin/master, lint-imports --config pyproject.toml, ruff format --check ., ruff check . and mypy pass.

🤖 Generated with Claude Code

@anth-volk
anth-volk added this pull request to stack #4185 November 4, 2026 16:10
@anth-volk anth-volk changed the title v2 architecture layer check Assign every v2 route and service file to a layer and enforce the v2 layer rules Nov 4, 2026
@codecov

codecov Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.43590% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 88.44%. Comparing base (97b2232) to head (e19f0d5).
⚠️ Report is 27 commits behind head on master.

Files with missing lines Patch % Lines
policyengine_api/services/v2/metadata/services.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           master    #4191       +/-   ##
===========================================
+ Coverage   53.84%   88.44%   +34.59%     
===========================================
  Files         283      297       +14     
  Lines       16502    17960     +1458     
  Branches     2681     2975      +294     
===========================================
+ Hits         8886    15885     +6999     
+ Misses       6876     1347     -5529     
+ Partials      740      728       -12     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@anth-volk
anth-volk force-pushed the v2-architecture-layer-check branch from 80e0f9d to d18175c Compare November 4, 2026 16:57
Base automatically changed from v2-architecture-import-linter to master November 5, 2026 12:08
anth-volk and others added 9 commits November 5, 2026 16:08
scripts/rules/tests/test_v2_layers.py specifies a quality guard over every
.py file under services/v2 and fastapi_routes/v2:

- each file is assigned a layer by its name or by
  rules/v2-layer-manifest.toml, and an unassigned file fails;
- route modules import no SQLAlchemy, SQLModel, connector, session or v2
  database module and call no SQL builder or session method;
- services build no SQL, call no data-access session method and import no
  FastAPI or Starlette;
- connectors don't open, commit, roll back or close a session.

Calls count only on a receiver named like a session (session.add, not
set.add), and SQL builders only when imported from SQLAlchemy or SQLModel.
Imports under TYPE_CHECKING count. Proof fixture trees for the four rules
include a route that imports a connector and a file with no layer.

These fail until scripts/rules/v2_layers.py exists.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the v2-layers quality guard (scripts/rules/v2_layers.py, standard
library only), run by scripts/run_quality_guards.py in the quality-guards
job.

- It discovers every .py file under services/v2 and fastapi_routes/v2 and
  takes its layer from its file name, or from rules/v2-layer-manifest.toml
  for the 9 files with other names. A file with neither fails: new rule
  api.v2.files-assigned-to-layers.
- api.v2.routes-no-sessions-or-sql is ratcheted. Its baseline lists the 4
  route modules that import sqlalchemy.exc to catch SQLAlchemyError.
- api.v2.services-no-sql and api.v2.connectors-never-commit are enforced,
  with no findings today.
- Session calls count only on a receiver named like a session, and SQL
  builders and session factories only when imported from SQLAlchemy,
  SQLModel or the v2 database module. Imports under TYPE_CHECKING count.

Each rule has proof fixture trees, and v2-code-organization.md cites each
rule id. The test fix resolves "from .. import database_session" to the
resource's own session module, as Python does.

Fixes #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Validators, transformations, types, process-state modules and package
  __init__ files touch no session: new rule
  api.v2.pure-modules-no-session-calls, with proof fixtures. A
  database_connectors/__init__.py counts as a connector.
- The repository's own session factories count: the v1 ones in
  policyengine_api.data.orm, build_v2_session_factory, the engines, and a
  database_session container's transaction() in a route.
- Services import nothing from SQLAlchemy or SQLModel but the Session type
  and sqlalchemy.exc, so and_, or_, func and col count, as does a select()
  on a model's __table__.
- A name that only contains "session" (sessions_router, session_ids,
  request.session) is not a session.
- Each occurrence of a finding needs its own baseline entry, and a
  duplicated entry is stale.
- The manifest gives only the layers of the file's directory, never
  "package", and every entry needs a reason.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New rule api.v2.pure-modules-no-session-calls: validators,
  transformations, types, process-state modules and package __init__
  files call no session method, SQL builder or session factory. A
  database_connectors/__init__.py is checked as a connector.
- Session factories are any call into policyengine_api.data.orm or
  policyengine_api.data.v2.database, or SQLAlchemy's Session,
  sessionmaker and create_engine. Routes and connectors may not import
  the v1 ORM module, and a route may not call a database_session
  container's read() or transaction().
- Services import nothing from SQLAlchemy or SQLModel but the Session
  type and sqlalchemy.exc, so and_, or_, func and col are caught, and a
  builder called on a model's __table__ counts.
- A session receiver is named session or <name>_session, and
  request.session and http_session are not sessions, so sessions_router
  and session_ids are not flagged.
- Each occurrence of a finding needs its own baseline line, and a
  duplicated line is reported as no longer applying.
- The manifest may give only the layers of the file's directory, and
  every entry needs a reason.
- v2-code-organization.md has a "File layers" section, which the files
  rule's rationale now links to.

The real code still has exactly the 4 tolerated sqlalchemy.exc imports.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Routes and the pure layers may call no session method at all, so
  get_one, bulk_save_objects, connection and expunge count; services'
  and connectors' lists gain get_one, the bulk methods, reset and
  invalidate.
- A manifest file in a pure or HTTP layer gets that layer's import and
  call checks, including session factories.
- A database_session.py below a resource package has no layer.
- Every call into SQLAlchemy or SQLModel but its exception classes
  counts, so "import sqlalchemy.exc" doesn't open sqlalchemy.and_.
- request.session on any request object, and client or requests
  sessions, are not database sessions.
- Only a builder called on <Model>.__table__ counts, once.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Routes and the pure layers may call no method on a session, so a
  method missing from a list can't slip through. Services' and
  connectors' lists gain get_one, the bulk methods, connection,
  expunge, reset and invalidate.
- The pure layers also get import checks (nothing from SQLAlchemy or
  SQLModel but the Session type; no connector, session, v1 ORM or v2
  database module), so a file the manifest assigns to one is checked as
  fully as a named one.
- A database_session.py below a resource package has no layer, so it
  can't escape the session-module rule.
- Every call into SQLAlchemy or SQLModel counts in routes, services and
  the pure layers, except to its exception classes, so
  "import sqlalchemy.exc" no longer opens sqlalchemy.and_.
- A request's .session and client or requests sessions are not database
  sessions; only <Model>.__table__.<builder>() counts as a table builder.
- The pure rule's scope leaves out the connector package __init__ files,
  which are checked as connectors. The docs list what the guard
  recognises and what it doesn't.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An injected session factory opens a session: session_factory.begin()
  and self._session_factory() count in connectors and routes, and so does
  a database_session container's transaction() in a connector.
- New rule api.v2.session-modules-no-data-access: a database_session
  module opens, commits, rolls back and closes sessions, but adds, gets,
  merges or executes nothing and builds no SQL. Proof fixtures included.
- A request's .session is recognised by its owner (req, websocket,
  *_request), so request_context.session and request_session are
  database sessions.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Injected session factories open sessions: session_factory(),
  self._session_factory() and session_factory.begin() count in routes,
  connectors and the pure layers, and a connector may not call a
  database_session container's read() or transaction().
- New rule api.v2.session-modules-no-data-access: database_session
  modules call no data-access session method or SQL builder and import
  no connector. Today's modules only open, commit, roll back and close.
- A request's .session is recognised by its owner (req, request,
  websocket, ws, *_request), so request_context.session and
  request_session are database sessions, and requests_session is not.
- The docs say services may open sessions, which their rule doesn't
  forbid.

The real code still has exactly the 4 tolerated sqlalchemy.exc imports.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 2 commits November 5, 2026 17:31
…ling)

- New name rules: process_state.py is process-state, and under
  fastapi_routes/v2 any <name>_models.py is http-model and any
  <name>_support.py is http-support.
- A file whose name gives no layer can only be allowed by a
  [[rule.exception]] for api.v2.files-assigned-to-layers. The exception
  chooses no layer: the file gets its directory's strictest checks, the
  route checks under fastapi_routes/v2 and the pure-module checks under
  services/v2. An exception for a missing file, a file whose name already
  gives a layer, a glob, or a path outside the two directories fails.
- The files rule's proof shows both an unassigned name and two excepted
  files getting the strictest checks.
- The tests import the eight renamed modules by their new names:
  process_state, errors_support, user_auth_support,
  user_resources_support, route_support, query_support,
  response_cache_support and search_file_support.

These fail until the guard reads exceptions instead of the manifest and
the files are renamed.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The v2-layers guard no longer reads rules/v2-layer-manifest.toml, which
  is deleted. New name rules: process_state.py under services/v2, and
  <name>_models.py and <name>_support.py under fastapi_routes/v2.
- A file that can't be renamed needs a [[rule.exception]] for
  api.v2.files-assigned-to-layers in rules/registry.toml. It gets no
  layer, but its directory's strictest checks; an exception for a missing
  file, a file whose name gives a layer, a glob or a path outside the two
  directories fails. None is needed today.
- Renamed, with every import updated:
  - services/v2/metadata/selected_catalogs.py -> process_state.py
  - fastapi_routes/v2/errors.py -> errors_support.py
  - fastapi_routes/v2/user_auth.py -> user_auth_support.py
  - fastapi_routes/v2/user_resources.py -> user_resources_support.py
  - fastapi_routes/v2/metadata/common.py -> route_support.py
  - fastapi_routes/v2/metadata/query_dependencies.py -> query_support.py
  - fastapi_routes/v2/metadata/response_cache.py ->
    response_cache_support.py
  - fastapi_routes/v2/metadata/search_file_response.py ->
    search_file_support.py
- v2-code-organization.md, the registry, the routes baseline and the
  rendered rules.md and AGENTS.md follow.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 2 commits November 5, 2026 17:51
… (failing)

- The proofs compare "<rule id>: <path> <detail>", so a finding reported
  under the wrong rule fails the rule's own proof.
- The exception tests require their registry to load without problems;
  an exception needs an expires date or a removal condition as well as a
  path and a reason, which the fix message must say.
- An unassigned file is told only the names that give a layer in its
  directory.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Each bad proof tree lists its findings as "<rule id>: <path> <detail>",
  so a finding reported under the wrong rule fails the proof. The files
  rule's tree shows its excepted files' findings under the route and
  pure-module rules.
- The files rule's fix message names only the layers of the file's
  directory, and says an exception needs an expires date or removal
  condition as well as a path and a reason; the test helper's exceptions
  now carry one and load cleanly.
- The registry statements say which checks an excepted file gets, and
  the pure-module rule's covers excepted files under services/v2.
- The Import Linter routes fixture uses route_support.py instead of the
  old common.py name.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 2 commits November 5, 2026 18:11
rules-registry.md reserves an exception's removal condition for rules
whose guard reads it, which the v2-layers guard doesn't, so a
files-rule exception should end on an expires date.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The files rule's fix message and v2-code-organization.md ask for an
  exception's path, reason and expires date; rules-registry.md reserves
  removal conditions for rules whose guard reads them.
- The guard, its fixtures and the docs say an excepted file gets the
  pure-module checks under services/v2 and the route checks under
  fastapi_routes/v2, instead of its directory's "strictest" checks,
  which overstated the services case.
- Two fixture comments no longer mention the deleted manifest.

Refs #4190

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er-check

# Conflicts:
#	docs/generated/rules.md
#	scripts/run_quality_guards.py
@anth-volk
anth-volk marked this pull request as ready for review November 5, 2026 14:31
@anth-volk
anth-volk requested a review from a team as a code owner November 5, 2026 14:31
@anth-volk
anth-volk merged commit d852e9e into master Nov 5, 2026
23 checks passed
@anth-volk
anth-volk deleted the v2-architecture-layer-check branch November 5, 2026 14:31
anth-volk added a commit that referenced this pull request Nov 5, 2026
Brings in #4191's v2 layer check. scripts/run_quality_guards.py keeps both
the suppressions and v2-layers guards; docs/generated/rules.md and AGENTS.md
were rendered again from the merged registry. #4191 moved the unexplained
`from fastapi import Request  # noqa: TC002` from
fastapi_routes/v2/user_resources.py to user_resources_support.py, so its
baseline entry now names the new file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Nov 5, 2026
Brings in master (#4191's v2 layer check and #4020) through #4183's branch.
scripts/run_quality_guards.py keeps both the asserting-tests and v2-layers
guards; docs/generated/rules.md and AGENTS.md were rendered again.

#4020 removed
tests/unit/v2/test_catalog_extraction_failures.py::test_national_region_without_a_dataset_path_uses_the_release_default,
so its baseline entry no longer applies: it is removed and the rule's
baseline count goes from 22 to 21, with the testing skill and the changelog
fragment updated to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Nov 5, 2026
Brings in #4191's v2 layer check and #4020. docs/generated/rules.md and
AGENTS.md were rendered again from the merged registry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Nov 5, 2026
Brings in master (#4191's v2 layer check and #4020) through #4196's branch.
docs/generated/rules.md and AGENTS.md were rendered again from the merged
registry; uv.lock merged cleanly and `uv lock --check` passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Assign every v2 route and service file to a layer and enforce the doc-only v2 layer rules

1 participant