Skip to content

Lesson 14: Explain

Reports are for machines; reviews are done by people reading sentences. The explain command renders a report's flows as deterministic, citable narratives, for a reviewer or for an agent. There is no LLM in it: the narrative is template-driven, so the same input produces the same sentences every time, which is what makes it safe to diff in CI and safe to cite in a review.

The text rendering

On the rusi fixture (Rust microservices talking to kafka):

$ atom-tools explain -i test/data/ecosystem/rust-microservices-kafka-rusi.json
Flow report: rusi 3.2.0 (security mode) — 5 flow(s), 3 package record(s) from 1 input file(s) (rust-microservices-kafka-rusi.json).
severity: 0 error / 5 warning / 0 note; 0 engine-reported, 5 derived
source tags: 5 of 5 flow(s) carry tags on the source node
entry-point join: 1 of 5 flow(s) sit behind an anchored entry point; rusi: 14 of
14 endpoint(s) anchored in the call graph, 2 flow attachment(s).

1. Flow df-slice-a8522d1a3ab83d87 — param-0 → network-request
   Reachability: `pkg:cargo/app-user-service@0.1.0` is reached from `GET /users`
   (app-user-service/src/user/api/rest/routing.rs:11), and 1 more entry point(s);
   rusi does not classify authentication, so the route's exposure is unknown.
   Path: source `u` (.../resources/response/mod.rs:72) tagged `param-0` which
   reaches `get` (.../resources/response/mod.rs:75) in 2 step(s), classified
   `network-request`.
   Evidence: rusi 3.2.0, security mode. Severity `warning` (derived from the
   shared taxonomy, not engine-reported).

2. Flow df-slice-03a64c7753789d6d — param-0 → network-request
   Reachability: `pkg:cargo/common-security@0.1.0` is reached by this flow; its
   source function sits inside no anchored handler's closure (14 of 14 endpoints
   anchored in this report's call graph).
   ...

Every clause in those sentences maps to a field in the unified flow model from lesson 10, and where a field is empty the clause is omitted or hedged, never filled with a plausible default. The header block counts what the join actually achieved: how many flows sit behind an anchored entry point, how many endpoints anchored, how many attachments were made. The honesty clauses are part of the output, not decoration: rusi does not classify authentication, so the sentence says the exposure is unknown; the severity line says the taxonomy derived it because the engine emitted none; an unjoined flow says so.

Selecting flows

Four selectors narrow the narrative in the text and markdown formats. --flow takes an exact id, --package a substring of a purl, --file a path suffix, and --query speaks dosai's compact grammar over the whole model:

$ atom-tools explain -i test/data/ecosystem/dotnet-eshoponweb-dosai-dataflows.json \
      --query "flows[source_category=secret && sink_category=auth]"
1 flow(s) match `flows[source_category=secret && sink_category=auth]`.

1. Flow dfs1 — secret → auth
   Path: source `JWT_SECRET_KEY` (ApiTokenHelper.cs:38) tagged `secret` which
   reaches `CreateToken` (ApiTokenHelper.cs:46) in 4 step(s), classified `auth`.
   ...

$ atom-tools explain -i test/data/ecosystem/dotnet-eshoponweb-dosai-dataflows.json \
      --query "flows[source_category=secret] count"
1 flow(s) match `flows[source_category=secret] count`.

The grammar takes collections (flows, endpoints, packages), predicates joined with &&, sort by prop [desc] and a count postfix. A flow also exposes dotted source. and sink. paths, an atom-tools addition. The one flow that matches here is a textbook review finding: an environment-sourced secret travelling into token creation in the eShopOnWeb API.

The markdown rendering

-f markdown produces the same narratives in a shape that attaches to a pull request. One flow, selected by id:

$ atom-tools explain -i test/data/ecosystem/dotnet-eshoponweb-dosai-dataflows.json \
      -f markdown --flow dfs1
### `dfs1` — error (engine)

- **Reachability:** `pkg:nuget/System.IdentityModel.Tokens.Jwt@7.3.1` is reached
  by this flow; dosai links weaknesses — not flows — to entry points, so no
  route is named here.
- **Path:** source `JWT_SECRET_KEY` (ApiTokenHelper.cs:38) tagged `secret` which
  reaches `CreateToken` (ApiTokenHelper.cs:46) in 4 step(s), classified `auth`.
- **Evidence:** dosai 5.0.0.0, dataflows mode. Severity `error` (engine-reported).
  Confidence `medium`.
- **Sanitizer:** No sanitizer reported on this path by dosai's sanitizer
  analysis; this run found none to report.

Note what the reachability clause does with dosai's model: dosai links weaknesses, not flows, to entry points, so the sentence declines to name a route rather than naming a wrong one. The sanitizer sentence appears only for engines whose schema carries a sanitizer field.

The agent rendering

-f agent emits a compact, token-budgeted JSON context for a consuming agent:

$ atom-tools explain -i test/data/ecosystem/dotnet-eshoponweb-dosai-dataflows.json \
      -f agent --max-tokens 1200
{
  "entryPoints": [],
  "explainVersion": 1,
  "highRiskFlows": [
    {
      "confidence": "medium",
      "id": "dfs1",
      "purls": ["pkg:nuget/System.IdentityModel.Tokens.Jwt@7.3.1"],
      "sanitizerReported": false,
      "severity": "error",
      "severitySource": "engine",
      "sink": { "file": "ApiTokenHelper.cs", "line": 46, "name": "CreateToken" },
      "sinkCategory": "auth",
      "source": { "file": "ApiTokenHelper.cs", "line": 38, "name": "JWT_SECRET_KEY", "tags": ["secret"] },
      "sourceCategory": "secret"
    }
  ]
}

The budget is an estimate, characters divided by four, not a real tokeniser, and the document says so. When the budget binds, the trimming leaves an unmissable truncated: N more marker rather than silently dropping flows.

The MCP server

With --mcp (requires the mcp extra, pip install atom-tools[mcp]), the command starts a read-only stdio MCP server over the loaded report, exposing five tools. --old loads a baseline at startup for the drift tool, because the tools never touch the filesystem:

$ atom-tools explain -i report.json --old baseline.json --mcp

The tool list, confirmed by an initialize handshake and a tools/list call:

atom.explain_flow    Explain one source-to-sink flow in words (deterministic, template-driven)
atom.attack_surface  Entry points grouped by exposure tier, with what each reaches
atom.drift           Reachability delta against the baseline the server was started with
atom.graph_hotspots  Call-graph chokepoints: nodes on the most source-to-sink paths
atom.query           Filter with dosai's compact grammar

This is the integration point for agent workflows: an IDE assistant or a CI bot that speaks MCP can read the report through these tools without re-implementing any parsing, and the answers it gets are the same deterministic sentences a human reviewer gets.

Determinism, and why it matters

Because there is no model call in the pipeline, running explain twice on the same report yields byte-identical output. That property has a practical consequence: you can commit the narrative to the repository, and a later run becomes a diff. Lesson 16 uses exactly that trick to put narrative drift in front of a reviewer.

Exercises

Explain the unified three-engine document from lesson 10, one flow per engine, and watch how the honesty clauses differ per engine. Then try to construct a query that returns zero flows and read what the output does with the empty result:

$ atom-tools explain -i unified-mixed.json --query "flows[sink_category=sql && severity=error]"
$ atom-tools explain -i unified-mixed.json --file routes.go --max-flows 3