artifact-investigation · git:20261105.54ee456 · 2026-11-05 · sha256 9ddfd3439aa27f97
artifact-investigation git:20261105.54ee456A
Immutable. This exact content never changes and is served at /api/v1/blob/9ddfd3439aa27f97.
---
name: artifact-investigation
description: "Opaque binary or artifact investigation: structure first, script second, agent last."
allowed-tools: ["Read", "Bash", "Glob", "Grep", "Agent"]
---
# Artifact Investigation (phase 2: gated)
Investigate an opaque artifact (a PE or .NET binary, a stripped executable, a vendor bundle) in
cost order: structure, then script, then agent judgment. The caller holds the artifact store; no
stage here copies the artifact's bytes into a repo. Cost order is
`coordinator/docs/wiki/coordinator-tripwires/cost-order-is-agent-tokens-then-wall-time-then-cpu.md`.
## When not to use
Ordinary source repos: read the code. This skill is for artifacts with no readable source.
## Stages, in order
1. **Parse and ingest, by script, no agent.** PE and .NET structural parsing (roadmap cluster C16,
project-rag); Ghidra program-facts ingest (cluster C5, engine).
2. **Query the structural index.** Ask the target's indexed facts a ranked question before spending
an agent (cluster C4, project-rag).
3. **Record claims and unknowns through the one writer** (cluster C3, engine), using the
field names of cluster C2 (`coordinator/pipelines/deep-research/evidence-unknowns-ledger-schema.md`).
4. **Agent only on judgment** the earlier stages could not settle.
A blocker on any stage: read `references/stage-map.md` (stage to owning plane and cluster). Read it
at no other time.
## Degraded mode
No stage is wired to a sibling tool yet; each sibling primitive wires in as it lands. For every
stage whose sibling primitive has not landed, the run record writes `coverage: unknown` for that
stage, labelled provisional, and the investigation's conclusions name that stage as unchecked.
`coverage: unknown` is a provisional label, not a record shape: project-rag owns the record shape
for unknowns and claims. A stage is never reported covered because it was skipped.
## Gates (phase 2)
One run folder per investigation; layout in `references/run-folder.md`. Each stage writes its file
there and the next stage reads it. Between stages, run in order:
1. `python <skill>/scripts/claim_filter.py <run> <stage>` drops claims with no `source_link`.
2. `python <skill>/scripts/sweep_gate.py <run> <stage>` exits 1 and writes `sweep.json` until the
stage is clean; do not start the next stage on exit 1.
3. After the last stage, `python <skill>/scripts/coverage_scorer.py <run>` writes `coverage.json`.
4. For a native binary, run `python <engine-root>/scripts/ghidra_ingest.py <binary>`
(`ghidra_ingest.cmd` on Windows). It needs Ghidra 11.x via `GHIDRA_INSTALL_DIR` or `--ghidra`,
analyses statically, and records functions, xrefs, imports and strings through
`rea_ledger.write` itself. A missing script or Ghidra install leaves this stage at
`coverage: unknown`.
5. Record what survived through the one writer:
`coordinator-invoke rea_ledger.write '{"kind": "<claim|evidence_record|unknown_revision>", "records": [...]}'`.
Each record is a JSON string in canonical form: keys sorted at every level, `,` and `:`
separators, raw UTF-8, no trailing newline. A non-canonical record refuses the whole batch and
nothing is written. An unknown revision N>1 carries the digest of revision N-1.
An unlanded stage records `coverage: unknown` with `provisional: true`, never a score.