Skip to content

Investigation reports

For operators · case files

For embedders · report model

How-to

An Adler scan answers “what did each site say?” An investigation report answers “what should I put in the case file?” It turns a finished scan artifact into a structured summary with found accounts, evidence, confidence, identity clusters, timeline context, and limitations.

Use reports when a scan result is leaving the terminal: handoff notes, ticket attachments, agent summaries, audit trails, or downstream tools that need stable JSON instead of CLI text.

Reports read persisted scans from the scan history directory. By default that is ~/.cache/adler/scans/.

Useful ways to get a scan id:

Terminal window
adler --web
# Run a scan in the UI, then open History.
adler --watch alice
# Creates / updates persisted scan history for the username.
adler --scans-dir ./scans --web
# Use an explicit artifact directory for a case or test fixture.

Live scan JSON (adler --format json alice) intentionally remains a top-level array of CheckOutcome objects. Reports are a separate finished-scan surface so existing scan JSON consumers do not break.

Markdown is the default and is meant for humans:

Terminal window
adler --report-scan scan_123 > report.md

JSON is the direct InvestigationReport model and is best for tools:

Terminal window
adler --report-scan scan_123 --report-format json > report.json
jq '.summary, .identity_clusters' report.json

HTML is a self-contained local case file:

Terminal window
adler --report-scan scan_123 --report-format html > report.html

The HTML renderer has inline CSS, no JavaScript, and does not load external avatar images. Avatar URLs and avatar hashes appear as text evidence rows instead of remote media.

If you keep scan artifacts outside the default cache, pass the same directory to the report command:

Terminal window
adler --scans-dir ./case-scans --report-scan scan_123 --report-format html > report.html

The JSON model is intentionally direct: no wrapper envelope, just InvestigationReport.

SectionPurpose
schema_versionReport contract version. Consumers should check it before assuming fields.
usernameUsername the scan investigated.
summaryCounts for found / not-found / uncertain rows, high-confidence accounts, evidence, clusters, and timeline events.
found_accountsEvery Found account with URL, signal evidence, profile evidence, confidence, transport, escalations, and cluster ids.
high_confidence_accountsFocused shortlist for quick human review.
evidence_tableNormalized profile evidence across found accounts.
identity_clustersDeterministic cluster candidates built from structured profile evidence.
timelineAdded / removed / reappeared / evidence-changed events from persisted history.
limitationsExplicit caveats such as low confidence or missing profile evidence.

Confidence is conservative. Exact username evidence, authenticated access, rich profile metadata, successful escalation, and historical consistency can raise confidence. Session-required, blocked, CAPTCHA, rate-limit, and weak status-only paths stay low or capped.

Identity clusters are deterministic leads, not biometric proof. Adler does not merge accounts on username alone, and avatar hashes are supporting evidence only. If a cluster has uncertain: true, preserve that flag in your notes and downstream output.

The Web UI exposes report downloads on finished scan views. There is no separate report route in the SPA; the links call the report endpoint:

FormatURL
JSON/api/scan/:id/report?format=json
Markdown/api/scan/:id/report?format=markdown
HTML/api/scan/:id/report?format=html

format=json is the default when the query parameter is omitted.

Error cases are explicit:

StatusErrorMeaning
404scan_not_foundNo in-memory or persisted scan has that id.
400scan_not_finishedReports are finished-scan only.
400invalid_report_formatFormat is not json, markdown, or html.

When a persisted scan is read, Adler applies the same historical confidence overlay used by /api/scan/:id, then rebuilds identity clusters so member confidence matches the returned outcomes. The stored artifact is not rewritten.

Agents should prefer report output for case-level summaries because it combines evidence, confidence, clusters, and timeline context in one contract.

Tool:

{
"name": "get_investigation_report",
"arguments": {
"scan_id": "scan_123",
"format": "json"
}
}

format defaults to json; markdown is available for note-style responses. HTML is intentionally not exposed through MCP v1.

Resource:

adler://reports/scan_123

The resource always returns JSON InvestigationReport.

Report files contain the same sensitive facts as scan artifacts, plus a more convenient summary of them. Treat report files as case material:

  • Scan ids are effectively local read capabilities for artifacts.
  • Evidence access metadata is non-secret by design: no session names, cookie/header values, proxy URLs, or egress names.
  • Raw avatar image bytes are never written to artifacts or reports.
  • HTML reports are local/offline-safe, but they still contain profile URLs, evidence values, cluster reasons, and timeline context.

For the full retention model, see Privacy and retention in the main repository.

The scan is still running, or you are asking for a report from a live SSE snapshot. Wait for the scan to finish, then request the report.

Clusters require Found outcomes with structured profile evidence. Run with enrichment when you need richer cluster inputs:

Terminal window
adler --enrich alice

Username-only matches intentionally do not create clusters.

JSON report differs from old persisted JSON

Section titled “JSON report differs from old persisted JSON”

This is expected. Reports are derived at read time. Adler can refresh confidence and rebuild clusters from old artifacts without rewriting the underlying scan JSON.

I need exact per-site rows, not a case file

Section titled “I need exact per-site rows, not a case file”

Use scan JSON instead:

Terminal window
adler --format json alice > outcomes.json

That shape is a top-level array of CheckOutcome objects. Use report JSON when you want the case-level InvestigationReport model.