symfonic.devtools.archcheck¶
archcheck ¶
Architecture rule checks (T1.2.4, REQ-S1.2).
Automated dependency-direction, cycle, forbidden-import, module-size, and public-export drift checks driven by repository-owned configuration:
- allowed dependency matrix (source of truth):
.agent/team/framework-refactor/evidence/T1.2.1/dependency-matrix.md - checker configuration:
.agent/team/framework-refactor/evidence/T1.2.4/archcheck.toml - exception registry (owner / rationale / expiry / review date):
.agent/team/framework-refactor/evidence/T1.2.4/exception-registry.toml
Run python -m symfonic.devtools.archcheck from the repository root.
ArchcheckConfig
dataclass
¶
ArchcheckConfig(repo_root: Path, src_root: Path, matrix_path: Path, registry_path: Path, export_baseline_path: Path, layer_map: dict[str, str] = dict(), composition_roots: tuple[str, ...] = (), forbidden_kernel_imports: tuple[str, ...] = (), port_module_names: tuple[str, ...] = ('ports', 'protocol', 'protocols'), max_module_lines: int = 300, public_export_modules: tuple[str, ...] = ())
layer_of ¶
Longest-pattern layer lookup for a dotted module name.
A plain row is a dotted PREFIX: it claims the package it names and
every module beneath it. A row carrying a glob metacharacter
(:data:GLOB_CHARS) is matched with :func:fnmatch.fnmatchcase
instead, so it claims only the names the pattern itself spells out.
The glob form exists for one shape the prefix form cannot express: a
module whose dotted name is a prefix of the tree it lives in. The root
re-export shim symfonic is the only such module here โ a bare
symfonic row would become the silent fallback owner of every future
top-level package added under src/symfonic/, which would inherit a
layer instead of raising layer-mapping and being ruled on its
contents. symfoni[c] matches the top-level module and nothing
below it, which is the same idiom the exception registry already uses
for its targets.
Ties break on pattern length, longest wins, so a narrower row always overrides a broader one.
Source code in src/symfonic/devtools/archcheck/config.py
ImportRecord
dataclass
¶
One import statement found in a module.
ModuleInfo
dataclass
¶
ModuleInfo(name: str, path: Path, line_count: int, imports: tuple[ImportRecord, ...] = (), dunder_all: tuple[str, ...] | None = None, is_package: bool = False)
Static facts about one Python module in the scanned tree.
Violation
dataclass
¶
Violation(rule: str, message: str, modules: tuple[str, ...] = tuple(), edge: tuple[str, str] | None = None)
One architecture-rule violation.
modules lists every module implicated (one for most rules, the whole
strongly connected component for cycles); the exception registry matches
against this tuple.
edge is the (importer, imported) pair for the rules that are about
one import statement. It exists because modules cannot express them:
an import rule records modules=(importer,), so a waiver matched on
modules alone waives every edge that importer will ever grow, not the
one edge that was reviewed. The registry refuses such a waiver
(registry-hygiene) and requires the edge instead.