Lei.ManifestGraph (LowEndInsight v0.13.1)

Copy Markdown View Source

Which dependencies a customer chose, and which arrived with them (#263).

risk_rank orders a manifest by how sick each dependency is. It cannot say whether the customer can do anything about it, and those are different questions: a direct dependency was chosen and can be replaced, while a transitive one may not be controllable without upstream moving.

Why not in-degree

This issue originally proposed ranking by in-degree as a blast-radius proxy. Measured against this repository's own 62 dependencies, that is anti-correlated with what it was meant to find:

highest in-degree:  jason 6 · telemetry 6 · decimal 5

the seven filed as issues (#250-#254):
  git_cli, elixir_uuid, yarn_parser, poison, temp, mix_audit, json_xema
  -> in-degree 0, every one

In-degree measures how much other dependencies rely on a package, not how much the customer's code does. git_cli is on every analysis path in the product and nothing in the tree depends on it, because we do -- a relationship no dependency graph contains.

What the measurement did show is that in-degree zero means direct, which is the signal that made git_cli matter. Health says what is wrong; directness says whether they can act on it.

In-degree is still reported, because it is real information about blast radius within the tree. Neither is folded into the rank: both are facts beside it, and the consumer's question decides which matters.

What inference cannot see

Directness here is deduced, not declared. A lockfile knows which dependencies are direct; the request does not carry it, so this reads it off the graph instead.

That is sound for a complete manifest and wrong in a way nothing detects for a partial one. Send only a transitive subset, or a tree with its roots omitted, and every package has in-degree zero -- so every package is reported as a deliberate choice, confidently, with no signal that the answer came from a tree that was never whole.

Unlike unreadable edges, which announce themselves as nil, this failure looks exactly like a correct answer. Accepting a declared direct flag alongside the inference, and recording which was used, is the fix if a partial manifest ever turns up. It is not built, because nothing has sent one and the endpoint is documented for the whole tree.

Summary

Types

A package identified as the manifest identifies it.

Functions

Directness and in-degree for every package in a manifest.

Types

name()

@type name() :: String.t()

A package identified as the manifest identifies it.

Functions

classify(packages, edges)

@spec classify([name()], %{optional(name()) => [name()] | nil}) :: %{
  optional(name()) => %{
    direct: boolean() | nil,
    in_degree: non_neg_integer() | nil
  }
}

Directness and in-degree for every package in a manifest.

edges maps a package to the packages it declares a dependency on. Only edges pointing at packages in this manifest count: a dependency on something the customer does not have says nothing about their tree.

Returns a map of package to %{direct: boolean | nil, in_degree: non_neg_integer | nil}.

A package with no edges known -- an unsupported ecosystem, a registry that did not answer -- gets nil for both. Unknown is not transitive. Marking it direct: false would quietly demote exactly the packages we know least about, and a consumer filtering for "critical and direct" would never see them.