Autopsy modules — the analysis pipeline internals
This page maps the module families inside artwork-autopsy so you can find where a given behaviour
lives. It follows the code’s one hard seam: deterministic machinery never imports the LLM, and the
LLM layer never touches the deterministic core except through ports. The top-level packages:
static/tools/— deterministic extraction. Parses and identifies, never decides.readers/— the LLM-free deep-readers: per-language decompiler wrappers, the code-semantics engine, the universal content scan, and the network probe. Deterministic; no AI framework here.ai/— the LLM layer: the analyst loop, the grounded-judgment primitive, narration, and the verification passes. The only place the AI framework may be imported.domain/— framework-free pure logic: the run graph, the behaviour map, scoping, the pipeline phases and their orchestration engine, and the knowledge base the report is projected from.runtime/— the data-driven runtime catalog and its dispatcher: which runtime a work needs, read fromruntime-catalog.yaml, not from code.report/— the report renderers: the HTML report, its graph SVGs, and the artifact writer.
Everything degrades: a missing tool or an absent model yields a flagged note or a deterministic fallback, never a crash.
Deterministic static tools — static/tools/
Each tool is a small self-registering callable that reads one member and returns evidence-linked
findings. All parse, never execute. Guarded tools that wrap an external binary degrade to empty (or a
tool-missing note) when the binary is absent, so a slim deployment still runs the pipeline.
Unpack & entry point
unpack— extracts the bundle and builds the file tree: stdlib archives, Mac fork-carriers (StuffIt/BinHex/MacBinary viaunar), and installer executables (InstallShield / NSIS / Inno / Wise / CAB), detected by magic, so a trapped payload reaches the deep-readers.entry_point— the authoritative entry-point resolver. A header/manifest parse wins and is stamped deterministic (autorun.inf, PEAddressOfEntryPoint, ELFe_entry, JARMain-Class, …); only a bare guess is left eligible for the LLM to disambiguate.director_cast— carves the RIFX/XFIR movie(s) out of a Director projector.exeinto standalone.dir/.dcrmembers so the Lingo decompiler can read the artwork, not the projector.
Format & content identification
siegfried— PRONOM format identification via thesfCLI (preservation canon).magika— Google’s ML content-type identifier; a second opinion that surfaces conflicts with PRONOM rather than silently resolving them.- Runtime detection is not a static tool: it is the data-driven
runtime/catalog dispatch (see below), which maps the extracted signals to the legacy runtime(s) a work needs so the runbook installs the runtime, not the art. media_info— MediaInfo/ffprobe track characterisation; a legacy codec becomes a guest-layer dependency the runbook must provision.validate/extract_meta— format well-formedness (JHOVE) and rich recursive metadata (Apache Tika), each reached over an optional side-car.
Binary & format parsing
binary_parse— parse-only PE/ELF/Mach-O imports, exports, linked libraries, linker hints (viapefile/ LIEF), feeding the guest dependency graph.scan_strings/deep_text— broad printable-string recon over many members, and the targeted full-text read of one important text/config node, mining URLs, hosts, paths, versions.java_fingerprint— no-JVM structural fingerprint of.jar/.class/.jnlp: native-lib bitness, bundled jars,.classversion (the JRE era), manifest.mac_resource— classic-Mac resource-fork / MacBinary / AppleDouble parse to a 68k-vs-PowerPC, app-vs-stack verdict where PRONOM is blind.quicktime—.movatom walk to the codec-runtime verdict (which legacy decoder it pins).nes_rom— iNES/NES 2.0 header parse: mapper, battery SRAM, CHR-ROM/RAM, region/timing, title.max_patch— reads a Cycling ‘74 Max patch (plain JSON): Max major version and the network-capable objects with their literal endpoints.firefox_ext— Firefox/Mozilla.xpianalyzer across both eras (legacy XULinstall.rdf, modernmanifest.json), pinning the period-Firefox range.
Per-format behaviour lifts
These read a member (or a runtime’s recovered strings) and lift the identity-defining mechanics — what the work does — into behaviour findings, without running it:
web_probe— net.art HTML/JS: plugin embeds, bundled libraries, and external endpoints with their HTML context. What the page’s code actually does — thefetch/XHR/WebSocket calls and the wired interactions — is read by the JS/HTML analyzers of the code-semantics engine (readers/codesem/).webext_behavior— what a browser extension is permitted to do, from its manifest: hosts it can reach, pages it injects, whether it intercepts/blocks traffic.pe_behavior— what a native.exedoes, read from its import table (render / network / audio / spawn).script_behavior— what a bare interpreted script (Perl/shell/PHP/…) does: fetch/serve, fork, exec sinks.hypercard_behavior— HyperTalk navigation and network handlers lifted from a stack’s scripts.qt_behavior— a.mov’s interactive behaviour: wired sprite actions, HREF/QTVR tracks, external media references.
The LLM layer — ai/
The LLM layer. The AI framework (pydantic-ai) is import-banned everywhere else — enforced by a ruff
banned-import rule in pyproject.toml plus scripts/check_import_ban.py in CI — and lazily imported
here; every module degrades to a deterministic fallback if the model or a side-car is absent.
The analyst loop
investigate/— the single analyst session: a tool-using agent that navigates what the deterministic tools extracted (read-only, path-jailed list/grep/read-slice plus on-demand unpack/deep-read), judges it, and returns a grounded report. Runs with no caps; any stop composes a graceful partial from accumulated findings. Every citation is validated post-hoc against real workspace paths.graph_nav— the graph accessors, citation matching, and entry-point scoring the analyst uses as its graph tools (the retired standalone “identify” stage, now folded into the analyst session).tiers— the only place capability names (explore / finalize / navigate / classify / reflect / vision / code) map to model tiers. A model swap is one config line; a re-tiering is one edit here.pydanticai/judge— the framework adapter, and the standalone grounded-judgment primitive (judge()/ajudge()) any step can call when it has extracted evidence but must not hardcode the interpretation.prompts/— every prompt as a versioned file (the analyst session, the orient judge, the scope judge, the cartographer, the wayback specialist), not a string buried in code.
Understand, narrate, verify
understand— narrates a conservator-facing document over the behaviour map; a deterministic template narration is the fallback so--no-llmstill emits a useful doc.graph_refine— the run-graph cartographer: after the analyst has read the decompiled flow, it authors the real execution/data-flow graph over the deterministic skeleton, every node grounded against a real file.reflexion— the independent grader of an actionable-unknown’s revival contract against its own bundled source (convergences / divergences / absences), recommending the cheapest escalation.falsify— falsification against an external oracle: tests an archive-replay revival claim against the Internet Archive (refuted / confirmed / revised), with a runtime-VM oracle as a seam.url_hints— a cheap advisory prior labelling well-known URLs (namespaces, CDNs, telemetry) so the big model spends its budget on the genuine backends. Strictly advisory; never a verdict.
The LLM-free deep-readers — readers/
Deterministic, framework-free readers that recover readable evidence from a runtime’s binary — a pure-parse pass that always runs plus a decompiler pass that degrades to a flagged note when its tool is absent — and persist the recovered source into the durable analysis record. No LLM lives here; the analyst calls these as tools.
-
routing— the deep-read router: a{runtime → sub-reader}table that dispatches a binary/ bundle node to the right decompiler. -
director— Director Lingo via ProjectorRays (script/cast inventory, authoring version, net handlers). -
flash— Flash/.swfvia JPEXS FFDec (ActionScript, network calls) over a pure-Python header/tag pass. -
java—.jar/.classvia jdeps + JADX over a pure-Python manifest/era pass. -
dotnet— managed PE / CLR via ilspycmd (IL → C#) over a pure-Python metadata pass. -
unity— Unity builds: version, scripting backend (Mono vs IL2CPP), platform, with the managed assembly routed onward. -
hypercard— a pure-Python walk of the stack binary recovering its HyperTalk scripts. -
ghidra— native PE/ELF/Mach-O decompile via the pyghidra-mcp side-car. -
source_code/codesem/— imperative source members through a query-first tree-sitter code-semantics layer (one engine, per-language.scmqueries, structural predicates only — no regex fallback): dispatchers, exec sinks, remote-include risk, version ceilings. -
content_scan— the universal fallback: any artifact with no specialised reader still gets a strings / endpoints / toolchain-fingerprint / references pass. -
net_probe— the shared network-operation probe: finds the call sites and lifts the surrounding code block generically (no hostnames), for the analyst to judge. -
codesem/lang/*(lingo,actionscript,java,dotnet, …) — the per-language analyzers inside the query-first codesem engine that turn recovered / decompiled source into behaviour-map nodes via structural tree-sitter queries (the former per-format regex scanners were re-ported here).
The runtime catalog — runtime/
Runtime detection is data, not code. runtime-catalog.yaml holds one entry per runtime module —
detection signals (extension / magic bytes / container structure / embedded strings), the analysis
plan, the ordered environment recipe, fidelity notes, known endpoints — and:
catalog— loads and validates the yaml into typed runtime modules.dispatch— walks the catalog in layered order (declared → ext → magic → container → embedded-string), loads every module whosematchesfires (multi-runtime is normal), and emits the guestruntimefindings; this runs as the pipeline’s dispatch phase. Unmatched formats fall to the_fallbackAI module, never a forced guess.
Teaching autopsy a new runtime is an edit to the catalog yaml — no new code (see Runtime modules).
The framework-free domain — domain/
Pure logic: no framework, no LLM import, no I/O beyond read-only workspace scans. This is the layer that stays standing even when the AI layer is degraded.
The run graph
runtime_graph— theRunGraphdata structure: the artwork’s components as a directed graph, with findings attached to node ids so a node accretes facts instead of being re-reported.graph— the deterministic builder (entry point + imports + cross-references + containment) and the projection of that graph back into the manifest layers.cartography/ref_resolver— the shared engine that grounds a member’s outbound references into typed edges, and the name-based join (exact → path → basename → fuzzy) that resolves one reference string against the nodes other modules registered.graph_dot— renders the graph to DOT for inspection.
The behaviour map
behavior_map— the semantic, conservator-facing map of what the work does, layered over the structural graph. Its node kinds include annotation — the artist’s own note about a component, harvested by the authored-text pass every codesem language and the format sidecars feed — and its edges include the interaction map: a human-input trigger (mouse, keyboard, form) wired to the effect it drives.behavior— the deterministic builder that lifts findings + graph into the behaviour map (runs under--no-llm), plus a Mermaid renderer. It joins each per-languageinteractions()finding intoowner → effect → inputedges and drops lifecycle noise that is not human input.behavior_linker— welds per-format behaviour fragments into one chain, so a polyglot work (front-end → handler → script → net) links up instead of landing as disconnected pieces.
Scoping — artwork vs. commodity
scoping/— a package:opaque.py(deterministic detection of opaque dependency subtrees — vendored runtimes, large bundled content — and the commodity-vs-authored signal: content-prevalence hash, vendored subtree, self-declared library coordinate, generated file — generic signals only, never a hardcoded name),anchors.py(the artwork anchors kept through the opaque collapse), andportrait.py(the deterministic bundle portrait the orient judge reads, and theBundleVerdictit returns).preflight— step 1.5: detects a bundled runtime installer alongside the art and asks the researcher (a two-phase pause on the async path); never rejects.context— seeds the conservator’s intake (Variable-Media intent) as human-authority findings, a strong prior that a contradicting deterministic fact still overrides.
The knowledge base & the deliverables
kb— composes the three-layer KB artifact (overview / file-tree / host / guest / network / fidelity-risks / provenance) from the accumulated report; the “possible KB” is the deliverable KB.inventory— the researcher-facing synthesis views: the dependency manifest (what must be provisioned) and the asset catalogue (what the work is made of).artist_hand— harvests the artist’s voice (comments, authorship/copyright/contact strings) from original and decompiled source into the art-historian’s view.runbook/suggestions(inrunbook) — the ordered host → guest → network reconstruction plan and the suggested base VM + tools.network_profile— builds the machine-readablenetwork-config.jsonhandoff rvmc consumes (the transport verdict plus deterministic hints), so a judged transport carries through to the VM.enrich— a second consumer of the same engine: proposes metadata for a known catalog binary (a single think pass, human-promoted draft → reviewed → published).summary— the ~10-second read at the top of the report, assembled deterministically from fields already produced.url_hygiene— one structural URL/host validator shared by every string-mining tool; judges well-formedness only, never whether a domain is a real endpoint (that is the analyst’s call).pipeline— a thin orchestrator: each phase body lives indomain/phases/*, and the post-preflight sequence is a declared phase plan the orchestration engine validates and runs: quick-pass → dispatch → code-semantics → deep-read (a fixpoint loop that drains every important unread node, relooping until the read-list is empty and the KB stops changing — no top-N gate) → causal → compose → url-hints → commodity-endpoints → network-scope → commodity-scope → investigate → refine-graph → eval → falsify. Static extraction, preflight, orient, and the scope collapse run as the head before the plan; artist-hand harvest and final synthesis run as the tail after it.phases/orient— the orientation phase, run after preflight and before the scope collapse: builds the deterministic bundle portrait and has the LLM judge name the entry point(s) and the work-vs-commodity partition. Gated (a lone clear entry spends no model call); the judged entry fills in at LLM-fallback authority only — a deterministic or human entry always wins — and an open question for the researcher surfaces as a tagged note rather than a silent guess.orchestration/— the phase engine: thePhasetype (name, body, declaredreads/writes),validate_plan(proves every phase’s reads are met by an input or an earlier phase — a reorder that breaks a data dependency fails before anything runs), andrun_plan(executes the plan with cooperative cancellation).model/ports— the shared data model (findings, layers, confidence, authority) and the port interfaces (Reasoner,DeepReader,Storage, …) the domain talks to the outside through.
The report renderers — report/
The presentation layer over the finished KB — rendering only, no analysis:
report_html— the self-contained HTML report a conservator reads.svg_render— the run-graph and behaviour-map SVGs embedded in it.artifacts— writes the deliverable files (report, KB, manifests) into the job’s output.
See also
- The autopsy — what the analysis produces and how to read it.
- Architecture — the pipeline shape and the deterministic/LLM seam.
- Runtime modules — how to teach autopsy a new file type.