Skip to content

Add fathom lint static analyzer for cross-construct rule-pack defects #143

Description

@se-jo-ma

Context

fathom validate (src/fathom/cli.py:120-173) parses each YAML document and runs per-document Pydantic validation via validate_document (delegated from _validate_document at cli.py:109). It never cross-references files. fathom compile (cli.py:254) is likewise file-by-file. There is no cross-construct analysis anywhere in src/fathom/ — a grep for unreachable|dead.rule|shadow|conflict|overlap returns only unrelated hits (CSS box-shadows, a grpc_server.py:169 comment, a studio/panels.py:442 doc comment).

As a result, authoring mistakes that span files pass validate cleanly and only surface as opaque CLIPS build errors at load time. The runtime contract confirms this: Engine.load_rules (engine.py:652) only checks that a ruleset's module is registered (engine.py:679); every other defect (a rule pattern referencing a slot the template doesn't declare, a focus order naming a missing module, a condition using a never-bound variable) falls through to _safe_build and raises a cryptic CLIPS error — the same class of footgun behind the Engine.reset() CSTRCPSR bug.

The data model already supports the needed cross-checks: FactPattern.template + ConditionEntry.slot/.bind (models.py:165, models.py:101), TemplateDefinition.slots (models.py:86), RulesetDefinition.module (models.py:285), ModuleDefinition (models.py:304), and the Engine registries template_registry/module_registry/rule_registry (engine.py:222-234).

Task

Add fathom lint <rules_path> that loads all constructs (parse + register, without building CLIPS) and reports cross-construct issues:

  • Rule LHS pattern references a template/slot not declared in any template (cross-check FactPattern.template + ConditionEntry.slot against template_registry).
  • focus order names a module with no rules or an undeclared module (against module_registry).
  • Rule condition uses a variable never declared via a bind.
  • Optional: shadowed rules (same module, identical normalized LHS) and clearly-unreachable rules (LHS constrains a slot to a value outside the template's allowed enum).

Emit findings with file + rule + slot names, classified warning vs error; support --json; exit non-zero on errors only.

Where

  • src/fathom/cli.py — new lint command (alongside validate/compile).
  • src/fathom/compiler.py / src/fathom/engine.py — reuse parse + registry plumbing; the registries needed already exist on Engine.
  • tests/test_cli.py — new cases + fixture packs with intentional defects.
  • docs/reference/cli/index.md (+ a new docs/reference/cli/lint.md) — document the command.

Acceptance criteria

  • Linting a pack with a slot typo reports the offending rule + slot before any CLIPS build.
  • Linting a pack whose focus names a missing module reports it.
  • Linting the shipped clean packs (owasp_agentic, nist_800_53, hipaa, cmmc) reports zero errors.
  • --json output parses; non-zero exit only on errors (warnings exit 0).
  • uv run ruff check, uv run mypy src/, and uv run pytest pass.

Note: distinct from #82 (hot-reload + signed bundle impact analysis), which concerns runtime bundle swaps, not author-time static analysis.

Size: L

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium priority: roadmap gap or DX/toolingcliCLI (validate, test, bench, info, repl)enhancementNew feature or requestrule-packRelated to OWASP/NIST/HIPAA/CMMC rule packssize/L<1 week: cross-cutting feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions