Observed since 20 Oct, 15:37 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Static-analysis enforcement scanners for the 3tears ecosystem (cache primitive contract, underscore-access discipline, codebase conventions, etc.)
Shared static-analysis enforcement scanners for the 3tears ecosystem.
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.
Adopting
invalidation_listenerin a repo that already runs a local copy. Three things change, and only the first is obvious.
- The exemption format. The hub's pre-shared walker keyed by BARE MODULE PATH (
src/aibots/gateway/acl.py). The shared parser wants apath:line:symboltriple and rejects anything else withExemptionError, 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.minimum_live_registriesis 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.- 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-botbacklog, which is this family's backlog of record -- not this repo's, so do not go looking for it here.
| Module | Invariant enforced |
|---|---|
audit_details | Every audit details key a producer publishes is classified (safe, family-safe, or personal) under threetears.agent.audit's erasure rule, and every details argument is statically readable. Wrapper helpers are refused until declared in forwarders, then read at their call sites. Classification is injected, the predicate included: pass safe_keys_for=safe_detail_keys_for, is_classified=is_classified_detail_key. Family keys are credited only for statically resolved event types (literal, module constant local or imported, imported-module attribute, conditional over both branches, helper parameter through every caller, a forwarder's own inner construction when its call site names none), and only when safe for every resolved value. No exemptions file. |
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. slf001_policy_findings(repo_root, exemptions_path) holds the suppression policy: no inline noqa for SLF001 anywhere, and a per-file SLF001 ignore only on a src module (one per third-party library whose private members it confines) that has exemptions-ledger entries. Enable it with the thin shell in pragma_policy's module docstring, asserting floors on scanned_python_files and slf001_ignored_files. scan_private_bindings(repo_root, exemptions_path) holds the two spellings every other check passes, over src, tests and scripts: shape G, a private name or module bound by an import (a test importing _helper from the module under test or from a sibling test module, or importing through pkg._internals), and shape H, a private name bound by a string (patch, patch.object, patch.multiple, patch.dict, monkeypatch.setattr/delattr, mocker.spy, import_module). A src module may bind its own package's privates; a private module or package under a tests tree is a violation wherever it is imported from, its own tests tree included, because shared test support has a plain name (conftest.py, __init__.py, other dunders and data files are never findings); a recorded third-party confinement module may import its library's privates; nothing else is sanctioned, and patching a library's private from a test is a violation. The full rules are in private_binding's module docstring. Enable it with the thin shell there: a floor on files_scanned, imports_examined and binding_calls_examined, the positive control undetected_planted_controls(tmp_path), and an empty private_binding_findings. |
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. |
imports_declared | A consumer repo's every non-stdlib, non-first-party import comes from a distribution its pyproject.toml declares, resolved at the full module path through the INSTALLED distributions' file lists (editable checkouts included), never a hand map. Both inputs carry a non-vacuity guard (required_import_roots, required_owned_modules), and imports nothing installed provides are reported apart. run_imports_declared_enforcement(ImportsDeclaredConfig(...)). No exemptions file. |
dict_state_detection | No raw dict/OrderedDict persistent state in __init__; use SQLiteBackend (L1) or NATS KV for shared state. |
grep_q_pipefail | No pipe (| or |&) into a quiet grep/egrep/fgrep (-q anywhere in its flags, --quiet, --silent) in a *.sh that sets pipefail or in a GitHub workflow (a shell: bash step runs with it; every workflow line is held to it): grep exits on its first match, the writer dies of SIGPIPE, and pipefail fails a check that matched. Continued lines are joined, comments skipped, and a scan that finds no files is refused. run_grep_q_pipefail_enforcement(GrepQPipefailConfig(repo_root=...)). |
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>. |
sender_posture | Every NATS receiving site (a subscribe, a JetStream consumer, the result waiter, a KV watch, an owner-forward handler) declares how it authenticates its sender with # sender-posture: <posture> [open <ROW>]: verified-token, signed-envelope, own-consumer, trigger-only, read-only, unauthenticated (only with an open row), or primitive (only inside the repo's named primitive functions, path::Qual.name). Each repo's allowlist carries exactly the open rows, in both directions, and pins how many sites in each file declare each row: a site cannot open a row the list lacks, a new site cannot ride an existing line, and a line whose row no site still declares fails, so the list only shrinks. sender_posture_findings(SenderPostureConfig(...)), with find_subscribe_sites for the shell's non-vacuity floor; excluded_prefixes leaves test doubles shipped under src unwalked; each must end in /, and one matching no module is a finding. |
release | Release tooling with one owner. api_growth_findings(ApiGrowthConfig(...)) refuses public-API growth inside a patch since the latest vX.Y.Z tag, and never reads an unparseable module or a non-literal __all__ as empty; http_routes and a repo's own extractors add surface a name sweep cannot see. untagged_checkout_findings binds every CI job running the gate to a checkout with the tags. threetears-release patch|minor|major|release|sync|verify X.Y.Z is the version bump each repo's scripts/bump-version.sh wraps in one line, configured by [tool.threetears-release]. |
print_log_lines | In every module that imports 3tears (scope_import_roots, default ("threetears",)): no print(..., file=sys.stderr) and no direct stdlib logging call (getLogger, basicConfig, a root-logger call such as logging.warning) -- log through threetears.observe.get_logger. For an application on 3tears, where codebase_conventions and no_stdlib_logging are too broad: print to stdout is left alone, modules that do not import 3tears are not scanned, and a command line whose stderr is its answer goes in exempt_files. One call takes # print-log: ok. Non-vacuity floor minimum_scoped_modules (default 1) fails a scan that reaches no 3tears module, in every mode. run_print_log_lines_enforcement(PrintLogLinesConfig(...)). |
no_stdlib_logging | No production module imports stdlib logging directly; use threetears.observe. |
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.
3tears-enforcement as a dev dependency.tests/enforcement/test_<domain>.py following the pattern above. Inject your repo's allowlists/exemptions.tests/enforcement/_<domain>_exemptions.txt if needed. Every exemption requires a preceding # rationale: <specific reason> line.pytest tests/enforcement/ to verify the scanners work against your tree.src/threetears/enforcement/<domain>/ with walkers.py, config.py, runner.py, and __init__.py.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.list[Violation]. Configs are frozen dataclasses. Runners orchestrate walker → exemption-application → mode-resolution → report.tests/<domain>/.Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.72.0 | 20 Oct 2026 | 3 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Public sponsor counts have not been collected. These signals belong to the repository owner.
github · 1 observations shown. Missing days are unknown.
| Date / source period | Value | Observed at |
|---|---|---|
| 2026-10-20 | 0 | 2026-10-20T17:18:19.8928356Z |
Observed since 20 Oct, 15:37 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:660c2e6c08ae90045a132fdca490e765519942ac5661a452316a8f8b6f1d167f