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
- 01
collect
userspace, on the hostno modelReads /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
- 02
score
deterministic engineno modelApplies 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
- 03
narrate
LLM layermodel in the loopTakes 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
- 04
emit
report.jsonno modelSerialises 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.
# 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.jsonjq -e '.meta.ksl_version and .score.reachable_cve_count' report.json
ksl validate report.jsonThen 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.