One run, four stages

The boundary that matters is between stage 02 and stage 03: everything numeric is decided before the model is called, and everything the model writes is prose or an artifact. Run with --no-explain and the scored output is byte-identical.

01 / architecture

Stage by stage

  1. 01

    collect

    userspace, on the hostno model

    Reads /proc, /sys, /dev, /boot/config-*, modules.dep and the running process table; optionally attaches an eBPF or ftrace backend to observe syscall usage for a fixed window. Every read that fails is recorded in meta.skipped with its reason.

    raw facts + a trace window

  2. 02

    score

    deterministic engineno model

    Applies the three reachability gates, weights each element, attributes surface debt across workloads, isolates orphaned surface, and solves the weighted set cover that orders the hardening plan. No model is involved and no randomness is used.

    score, ledger, orphaned, plan

  3. 03

    narrate

    LLM layermodel in the loop

    Takes the already-computed figures and writes the causal explanation per ledger row, predicts what a plan step would break on this specific host, and synthesises the artifact for each step (modprobe blacklist, sysctl drop-in, seccomp profile) plus its revert. It never produces or edits a number.

    explanation, breakage_note, artifact, revert

  4. 04

    emit

    report.jsonno model

    Serialises everything against the frozen report.schema.json. The dashboard is a pure function of this file — no hidden state, no server-side computation, and any schema-valid report from any host renders identically.

    report.json

02 / operator view

Running it

A scan is one command on the host under audit; the dashboard consumes the file it writes.

on the host under audit
# full run, with the trace backend and narration
sudo ksl scan --trace-seconds 60 --out report.json

# scored output only — no model call, byte-reproducible
sudo ksl scan --trace-seconds 60 --no-explain --out report.json
verify the contract before rendering
jq -e '.meta.ksl_version and .score.reachable_cve_count' report.json
ksl validate report.json

Then drop report.json onto the dashboard to inspect the host directly.

03 / contract

Why the schema is frozen

The report is the interface between a Python collector that must run as root on a Linux host and a viewer that must run anywhere. Freezing it means the collector can be rewritten — a different trace backend, a wider CVE map — without touching the dashboard, and a report captured months ago still renders.

It also means the numbers on screen are checkable. Everything the dashboard displays is present in the file; nothing is derived from a server call you cannot inspect.