Skip to main content

3tears-enforcement

Shared static-analysis enforcement scanners for the 3tears ecosystem.

What this package does

Each module under threetears.enforcement.<domain> ships an AST-based scanner that enforces a single architectural invariant across a Python source tree. Consumer repos import the scanner, inject their per-repo configuration (allowlists, exemption files, src roots), and run it from a thin pytest test class.

This replaces a previous pattern in which the same enforcement test files were vendored verbatim across multiple repos with manual sync requirements. The shared package eliminates duplication and drift while keeping per-repo configuration where it belongs.

Domains

Adopting invalidation_listener in a repo that already runs a local copy. Three things change, and only the first is obvious.

  1. The exemption format. The hub's pre-shared walker keyed by BARE MODULE PATH (src/aibots/gateway/acl.py). The shared parser wants a path:line:symbol triple and rejects anything else with ExemptionError, so an unmigrated file does not degrade -- it aborts the domain. Rewrite each entry as <path>:*:<registry-spelling>, keeping its # rationale: line. * means "any line in that file", which is what you want: keying an exemption to a line number silently stops matching when the line moves.
  2. minimum_live_registries is yours to supply, and it is REQUIRED with no default. Count the L2-live registries your repo actually wires and put that number in the shell. It is the floor that makes a green report mean anything.
  3. Repo-specific shell assertions do not come across. A local walker typically carries checks the shared domain has no equivalent for -- which processes must be in scope, that an L1-only registry is not dragged in, detector positive/negative cases. Those are yours to keep: port them into the thin shell rather than assuming the domain replaced them, because deleting the local file deletes them silently.

This note describes a one-time migration and should be deleted once every consumer has adopted the domain. Tracked as ENF-7WQ2 in the 14-eng-ai-bot backlog, which is this family's backlog of record -- not this repo's, so do not go looking for it here.

Module Invariant enforced
cache Every stateful data surface routes through BaseCollection; no bespoke SQLiteBackend wrappers; no direct pool access to Collection-backed tables; every migration-defined table has a Collection class.
underscore_access Underscore prefix is a stability contract: no cross-module private import, no cross-class protected access, modules with public names have __all__, no subclass shadowing of base private attributes, no __all__ listing private names.
codebase_conventions No bare print(), no stdlib logging.getLogger (use threetears.observe), from __future__ import annotations required, return type annotations required.
coercion_coverage Tool subclasses override execute, never run, preserving the normalize_kwargs → execute input-coercion path.
dependency_alignment Declared dependencies match actual imports (no undeclared module-top sibling import, no declaration nothing imports); designated contracts packages import only stdlib, their own namespace, and configured extras; a package with a pinned DependencyFloor declares exactly the ruled hard-dependency list, no more and no fewer.
dict_state_detection No raw dict/OrderedDict persistent state in __init__; use SQLiteBackend (L1) or NATS KV for shared state.
invalidation_listener Every L2-live CollectionRegistry starts the cross-pod invalidation listener, and every start is paired with a stop. Carries a REQUIRED non-vacuity floor: a scan reaching nothing reports what a clean repo reports.
logger_coverage Every production module declares a module-level log = get_logger(__name__) unless explicitly exempt.
migration_yugabyte_safety Migration shapes are yugabyte-safe per threetears.core.data.migrations.enforcement.
nats_wrapper_usage All nats-py imports route through threetears.nats.NatsClient; no direct import nats.
no_silent_swallow Exception handlers must log, re-raise, or carry # NOSILENT: <reason>.
no_stdlib_logging No production module imports stdlib logging directly; use threetears.observe.

How to use it

Each domain exposes a configuration dataclass and a high-level runner:

# tests/enforcement/test_cache_primitive_usage.py
from pathlib import Path
from threetears.enforcement.cache import CacheEnforcementConfig, run_cache_enforcement

_CONFIG = CacheEnforcementConfig(
    repo_root=Path(__file__).parents[2],
    allowed_sqlite_construction_sites=frozenset({
        "packages/registry/src/threetears/registry/l1_cache.py",
    }),
    collection_table_allowlist={
        "memories": "MemoriesCollection",
        # ... per-repo
    },
    migration_table_allowlist=frozenset({"_schema_migrations"}),
    exemptions_path=Path(__file__).parent / "_cache_exemptions.txt",
    enforcement_mode_env_var="CACHE_ENFORCEMENT_MODE",
)

class TestCachePrimitiveUsage:
    def test_no_bespoke_sqlite_backend_construction(self) -> None:
        run_cache_enforcement(_CONFIG, walker="sqlite_construction")

    def test_no_bespoke_cache_wrapper_classes(self) -> None:
        run_cache_enforcement(_CONFIG, walker="wrapper_class")

    def test_no_direct_pool_access_to_collection_tables(self) -> None:
        run_cache_enforcement(_CONFIG, walker="pool_access")

    def test_all_tables_have_collections(self) -> None:
        run_cache_enforcement(_CONFIG, walker="missing_collection")

Per-repo exemption files (e.g., _cache_exemptions.txt) stay in the consumer repo's tests/enforcement/ directory. The package's parse_exemptions_with_rationale reads them at test time.

How to onboard a new repo

  1. Add 3tears-enforcement as a dev dependency.
  2. For each domain you want to enforce: create a thin shell test file at tests/enforcement/test_<domain>.py following the pattern above. Inject your repo's allowlists/exemptions.
  3. Create per-domain exemption files at tests/enforcement/_<domain>_exemptions.txt if needed. Every exemption requires a preceding # rationale: <specific reason> line.
  4. Run pytest tests/enforcement/ to verify the scanners work against your tree.

How to add a new enforcement domain

  1. Create src/threetears/enforcement/<domain>/ with walkers.py, config.py, runner.py, and __init__.py.
  2. Use common/ helpers (ast_helpers, collection_registry, repo_layout, pyproject_discovery, inheritance, exemptions, modes, violations, reports). Do not duplicate scaffolding. Two in particular, because both have already been re-implemented in this package rather than imported:
    • ast_helpers owns the dotted-name spelling family -- dotted, callee_names, receiver, argument_spellings. If you are about to write a loop that walks an ast.Attribute chain accumulating segments, it exists. Three copies of that walk shipped here before it was shared, each differing by one detail that turned out to be a parameter (dotted's unwrap_subscript is the survivor).
    • collection_registry owns "which CollectionRegistry names are L2-live" -- take the answer from there rather than re-deriving it. CLIENT_SPELLINGS is a heuristic list that grows, so a second copy goes stale silently while still compiling.
  3. Walkers return list[Violation]. Configs are frozen dataclasses. Runners orchestrate walker → exemption-application → mode-resolution → report.
  4. Write unit tests in tests/<domain>/.
  5. Document the domain in this README.

Release files for 3tears-enforcement 0.51.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for 3tears-enforcement 0.51.1
File Size Uploaded
3tears_enforcement-0.51.1.tar.gz 218.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for 3tears-enforcement 0.51.1
File Interpreter ABI Platform
3tears_enforcement-0.51.1-py3-none-any.whl Python 3 none any Details

Total release size: 419.6 kB

Release files / 3tears_enforcement-0.51.1.tar.gz

Download URL 3tears_enforcement-0.51.1.tar.gz
Size 218.5 kB
Tags Source
SHA-256 checksum
How to use checksums
2a30bc41f91f7355f792e9ab113b4df8dde1670efb5e0d1dc0a655b6d8de1849
BLAKE2b-256 checksum
How to use checksums
2be53e0a1f53d19b51c5815eda173e24632ddd1edf7d669289c8b59b8fc63b2e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / 3tears_enforcement-0.51.1-py3-none-any.whl

Download URL 3tears_enforcement-0.51.1-py3-none-any.whl
Size 201.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a71da5d4edab22ffee0280da1ca1588b2106d86c73767210e3be2ba5d25b0255
BLAKE2b-256 checksum
How to use checksums
5361036c069249c5f21019d29a898d9ff7ccacc61bb8b0602c4841d879dfda4b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

0.52.1

2 release files

0.52.0

2 release files

This release

0.51.1 This release

2 release files

0.51.0

2 release files

0.50.0

2 release files

0.49.0

2 release files

0.48.0

2 release files

0.47.1

2 release files

0.47.0

2 release files

0.46.1

2 release files

0.46.0

2 release files

0.45.1

2 release files

0.45.0

2 release files

0.44.0

2 release files

0.43.0

2 release files

0.42.0

2 release files

0.41.4

2 release files

0.41.3

2 release files

0.41.2

2 release files

0.41.1

2 release files

0.41.0

2 release files

0.40.0

2 release files

0.39.0

2 release files

0.38.0

2 release files

0.37.0

2 release files

0.30.0

2 release files

0.29.0

2 release files

0.28.0

2 release files

0.27.0

2 release files

0.26.1

2 release files

0.26.0

2 release files

0.25.0

2 release files

0.24.7

2 release files

0.24.6

2 release files

0.24.5

2 release files

0.24.4

2 release files

0.24.3

2 release files

0.24.2

2 release files

0.24.1

2 release files

0.24.0

2 release files

0.23.9

2 release files

0.22.4

2 release files

0.22.3

2 release files

0.22.2

2 release files

0.22.1

2 release files

0.22.0

2 release files

0.21.0

2 release files

0.20.0

2 release files

0.19.4

2 release files

0.19.3

2 release files

0.19.2

2 release files

0.19.1

2 release files

0.19.0

2 release files

0.18.0

2 release files

0.17.9

2 release files

0.17.8

2 release files

0.17.7

2 release files

0.17.6

2 release files

0.17.5

2 release files

0.17.4

2 release files

0.17.3

2 release files

0.17.2

2 release files

0.17.1

2 release files

0.17.0

2 release files

0.16.1

2 release files

0.16.0

2 release files

0.15.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page