5f050810

By: Tom Sydney Kerckhove <syd@cs-syd.eu>

Each rule owns what it carries, and the phases stop being a wall

Two corrections, and the second explains the measurement in the last commit.

The phases were too strict. The module phase now produces facts and findings
together, and the package and project phases take facts, produce more findings,
and union those with the findings from before rather than deriving them again.
A package's output is both halves side by side, facts.json and report.json in
one directory, and the project phase reads both.

And the fact type was one record with a field for everything any rule might
want. Now each rule owns the type of what it carries, the codec for it, and how
to get it out of a module:

    data RuleImpl where
      ModuleRule  :: (ModuleContext -> CheckResult) -> RuleImpl
      PackageRule :: JSONCodec c -> (ModuleContext -> c) -> (PackageView c -> CheckResult) -> RuleImpl
      ProjectRule :: JSONCodec c -> (ModuleContext -> c) -> (ProjectView c -> CheckResult) -> RuleImpl

The envelope stays global: which module, where, which component, whether it
could be read, and the suppressions written in it, since judging one is not a
rule's job. Inside is one entry per rule that asked for one.

The constructor is now the level, so ruleLevel is derived rather than a field
that can disagree with the implementation.

A module rule carries nothing, because everything it reads has become a finding
by the time the module phase returns. That is the whole of the size problem:
comments and declarations were seventy percent of a fact file and no rule that
can see past one module ever read them.

    nix-ci-leader   2,205 KB -> 75 KB, 1.22x the source -> 0.04x
    nix-ci-common     510 KB ->  9 KB
    nix-ci-worker     267 KB ->  4 KB
    nix-ci-cache      379 KB -> 10 KB

ModuleFacts is now the envelope and the rich in-memory parse is ModuleContext,
which is never serialised and says so in its type: it has no codec at all.

The split-agreement property gets stronger with the union: it compares every
finding the two paths produce, not only the project-level ones.