summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md
blob: 86f2b06ade5465d741802d33ea3af723585a0fcb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# 27 — green-eye tool audit

> **# TOOL IN DEVELOPMENT - NOT READY YET!** See [00-OVERVIEW](00-OVERVIEW.md)
> for status and [06-ACCURACY](06-ACCURACY.md) for fidelity limits.
> Development-only: never shipped, never a driver dependency, never in a guest.

Audit date: 2026-10-02. Scope: every runnable surface in
`debug/risc-v-gsp-rm-vm/` (CLI, Python package, `sim/`, `harness/`,
`plugins/`, `qemu/`, `ghidra/`, `rmharness/`, `sdk/`, firmware demo,
`examples/`, `tests/`, docs). The goal is that every tool is either exercised
somewhere, documented well enough to be unmistakable, or explicitly marked
`not used yet`.

## 0. Method (what was actually run)

Environment: QEMU 8.2.2 (`qemu-system-riscv64`), Python 3.12, clang/lld.
Every safe smoke below was executed from the tool root; device/WPR paths were
not touched.

* all 13 `./greeneye.sh <cmd> --help` parsers, `--version`;
* `info`, `demo --backend native`, `record --backend native --trace`,
  `analyze`, `compare`, `plugins`, `inject --backend native`;
* `probe --instructions 400` against the real 570.144 `.fwimage`;
* `launch` (bounded, QEMU killed afterwards) and the attach REPL path via
  `tests/test_injection.py`;
* `symbols export` from a synthetic Ghidra index TSV, then `symbols resolve`;
* `run` of every `examples/scripts/*.py` as documented, plus the declarative
  probe spec `examples/probes/demo_probes.json`;
* `make demo`, `make sdk`, `make -C qemu` (plugin already built) and a live
  QEMU run with `qemu/greeneye-trace.so` (8,012,129 JSONL events in ~1 s);
* `harness/*.py --help`; `harness/value_demos/run_all.py --only s02,s05`;
  `examples/scripts/sim_bench.py`, `sim_diff.py`;
* `plugin --help` / MCP suite; `rmharness/*.sh --help`,
  `rmharness/harness.sh` capture with `rmtool gpu list` (read-only `/proc`);
  `python3 rmharness/tests/test_rmharness_plugin.py` (3 tests);
* `python3 -m greeneye.device_map --help` and `demo` (bounded 50k
  instructions, lenient MMIO);
* full suite: `python3 -m unittest discover -s tests -t .` → **155 tests, OK**;
* `python3 scripts/check-docs-links.py` from `open-iov/` → **OK, 55 files**;
* `git check-ignore -v` for the local-only ignore patterns.

Status vocabulary: `verified useful` (exercised and/or wired into
tests/demos/docs), `not used yet` (no caller found; decision recorded),
`needs improvement` (concrete weakness; fixed where safe), `prune candidate`
(clearly dead; none found).

## 1. Master table — `greeneye.sh` CLI

| surface | purpose | API / entry points | tests | used by | status | notes/actions |
|---|---|---|---|---|---|---|
| `info` | environment/tooling check | `greeneye/cli.py:173`, `greeneye.sh info` | indirect (smoke) | README, 00, 02 | verified useful | ran; native sim + fwimage + CUDA names detection all correct |
| `demo` | canned demo boot + hooks/watch/diff/trap | `cli.py:209` | smoke via `test_smoke_qemu.py` | README, 00, 02; `make demo-run` | verified useful | ran on `--backend native`; 17 hook lines, diff + trap work |
| `run` | run a Python instrumentation script | `cli.py:272` | `test_injection.py` (REPL), `test_sim_trace.py` | 02, 03, examples scripts | needs improvement (fixed) | the exec namespace lacked `__file__`, so the documented `dump_diff.py` failed; namespace now sets `__file__`/`__name__`, doc 03 notes it |
| `attach` | interactive REPL | `cli.py:486`, `GreenEyeRepl` | `test_injection.py:68` | README, 02 | verified useful | REPL commands all back onto tested Session APIs |
| `launch` | start target, keep alive for attach | `cli.py:509`; `qemu/launch.sh` | smoke (bounded run) | README, 02 | verified useful | prints gdbstub endpoint; QEMU survives Ctrl-C by design |
| `probe` | run arbitrary/real firmware, report first divergence | `cli.py:551`; `test_probe.py` unit | `test_probe.py` (ELF/flat/fault parse) | README, 00, 06, 24 (`harvest` input) | verified useful | ran vs real fwimage: first fault `fault_fetch @0x0`, recorded session evidence |
| `record` | record a memory-activity session (+ native trace) | `cli.py:529` | `test_sim_trace.py` | README, 02, 03 | verified useful | ran with `--trace`; trace blob + watch events rendered by `analyze` |
| `analyze` | session → symbol-annotated report | `cli.py:690`, `greeneye/analyze.py` | `test_sim_trace.py`, `test_smoke_qemu.py` | 02, 03, 05, 13, 14, 25 | verified useful | ran on recorded session |
| `compare` | A/B recorded sessions | `cli.py:704`, `analyze.compare_sessions` | `test_sim_backend.py`, `test_value_demos.py` | 02, 03, 14, 25 | verified useful | ran demo-vs-record; correct profile deltas |
| `symbols export\|resolve` | build/inspect symbol DB | `cli.py:711` | `test_core.py` (loaders) | 05, README | needs improvement (fixed) | bad `--db` JSON produced a traceback; `SymbolDB.load_json` now raises a clean `GreenEyeError` (exit 2) |
| `plugins` | list discoverable plugins | `cli.py:733` | smoke | README, 04 | verified useful | lists all three bundled plugins |
| `inject` | load + call an injected RV64 service | `cli.py:630`; `greeneye/inject.py` | `test_injection.py` | 11, 12, 14 | verified useful | ran native: direct call and firmware mailbox both `0xbeae` |
| `ghidra` | serve session to GhidraScripts | `cli.py:660`; `greeneye/gdbridge.py` | `test_bridge.py` (full protocol) | 05, 13, 14 | verified useful | bridge protocol covered live over a socket; Java side needs Ghidra |

## 2. Master table — Python API (`greeneye/*.py`)

| surface | purpose | API / entry points | tests | used by | status | notes/actions |
|---|---|---|---|---|---|---|
| `__init__.py` | public API: `launch`, `attach`, exports | `ge.launch(backend=, trace=, ...)` | all integration tests | README, 02, 03 | verified useful | stable-ish v0.1 surface, documented |
| `errors.py` | exception taxonomy | `GreenEyeError` + subclasses | everywhere | 03 §13 | verified useful | small and meaningful |
| `rsp.py` | GDB-free RSP client | `RspClient`, `StopReply` | `test_rsp.py`, `test_mcp.py` | 01, 03 | verified useful | fake-stub unit tests |
| `elf.py` | ELF64/32 reader, flat kernel writer | `load_elf`, `write_flat_kernel`, `write_relocated_elf`, `FirmwareImage` | `test_core.py` (`write_relocated_elf` test added), `test_probe.py`, `test_device_map.py` | 06, 12 | verified useful | `write_relocated_elf` had no caller; a relocation test now locks its entry/PT_LOAD shift |
| `symbols.py` | symbol DB, address translation, TSV/JSON loaders | `SymbolDB.get/lookup/resolve/annotate/by_subsystem`, `Symbol` | `test_core.py` (`by_subsystem` + list/bad JSON tests added), `test_mcp.py`, `test_memory_record.py` | 03, 05 | verified useful | `load_json` list payloads worked by accident? no — they crashed; fixed and tested; parse errors now `GreenEyeError` |
| `memory.py` | read/write/hexdump/search/snapshot/diff | `Memory`, `Diff` | `test_memory_record.py` | 03 §5/§7 | verified useful | full docs in 03 |
| `structs.py` | schema-driven struct decode/render | `schema_from_json`, `Schema`, `render` | `test_core.py` | 03 §6 | verified useful | docs example matches demo schema |
| `record.py` | durable session: events.jsonl/snapshots/summary | `Recorder`, `NullRecorder`, `load_events` | `test_memory_record.py` | 01, 02, 03 | verified useful | `rmharness/harness.sh` also appends compatible events |
| `session.py` | hooks/bp/watchpoints/events/step/call | `Session`, `hook`, `watch`, `run`, `call` | `test_injection.py`, `test_mcp.py`, `test_bridge.py` | 01, 03, everywhere | verified useful | `Session.symbol_at` is a thin alias of `symbols.resolve` with no caller yet; docstring added, kept for API symmetry |
| `target.py` | QEMU process lifecycle + UART/dlog | `QemuTarget`, `find_qemu`, `free_port` | `test_smoke_qemu.py`, `test_injection.py` | 01, 02 | verified useful | `launch`/`probe` both exercise it |
| `sim_backend.py` | in-process native backend (M1) | `launch_native`, `SimClient`, `SimTarget` | `test_sim_backend.py`, `test_sim_trace.py`, `test_device_map.py`, `test_mcp.py` | README, 16, 24 | verified useful | QEMU-parity differential test |
| `analyze.py` | report renderer, fault parser, A/B compare | `render_summary`, `compare_sessions`, `describe_fault` | `test_sim_trace.py`, `test_probe.py`, `test_smoke_qemu.py` | 02, 03, 06, 25 | verified useful | consumed by CLI and value demos |
| `device_map.py` | versioned first-touch MMIO map + lenient policy | `python3 -m greeneye.device_map {harvest,show,merge,demo}` | `test_device_map.py` | 24 | verified useful | `demo` reproduced the documented first GSP access (lenient run 50k insns) |
| `gdbridge.py` | JSON-lines Ghidra control server | `GhidraBridge`, `cmd_*` | `test_bridge.py` | 05, 13 | verified useful | 25 commands; protocol tested live |
| `ghidra.py` | symbol import / hit export | `build_symbol_db`, `export_hits`, `corpus_location`, `render_ghidra_command` | `test_core.py` (TSV loaders), `test_value_demos.py` s09 (`export_hits`), `test_bridge.py` (`render_ghidra_command` test added) | 05, 13, 25 | verified useful | `render_ghidra_command` now has a test and is cited from 05 §4 |
| `inject.py` | position-independent service load/call | `load_service`, `invoke_remote`, `invoke_via_firmware` | `test_injection.py` | 11, 12 | verified useful | both call paths run in tests and in the CLI smoke |
| `plugins.py` | plugin discovery/API | `PluginManager`, `GreenEyeAPI` | `test_injection.py`; plugin smoke | 04, plugins/README | verified useful | bundled + `GREENEYE_PLUGINS` paths |
| `probes.py` | declarative JSON probes | `load_spec`, `install`, `compile_condition` | `test_smoke_qemu.py`; `demo_probes.json` smoke | 03 §9, 14 | verified useful | 3 probes installed, fired as expected; no standalone runner needed (documented snippet) |
| `cli.py` | argparse entry points (section 1) | `main`, `build_parser` | CLI smokes + integration tests | everywhere | needs improvement (fixed) | docstring now lists `launch`/`inject`/`ghidra`; `run` sets `__file__` |

## 3. Master table — simulator, harness, plugins, host tooling

| surface | purpose | API / entry points | tests | used by | status | notes/actions |
|---|---|---|---|---|---|---|
| `sim/decode.py` | RV64IMAC decoder | `decode32`, `decode16` | `test_sim_backend.py` | 16 | verified useful | compressed canonicalisation tested |
| `sim/machine.py` | deterministic machine, UART, lenient MMIO | `Machine`, `Uart16550` | `test_sim_backend.py`, `test_sim_trace.py`, `test_device_map.py` | 16, 24 | verified useful | no wall-clock claims (06) |
| `sim/trace.py` | native exec/access trace (R4) | `Trace` | `test_sim_trace.py` | 16, README | verified useful | trace blob rendered by `analyze` |
| `harness/common.py` | shared harness helpers | `load_target`, `write_json`, `read_faults_window`, … | `test_harness.py`, `test_discovery.py`, `test_version_diff.py` | 18–23 | verified useful | internal, well factored |
| `harness/ci_release.py` | release triage orchestration | `python3 harness/ci_release.py --old … --new …` | `test_version_diff.py` | 21 | verified useful | dry-run supported; manifest/mini-gate |
| `harness/control_discovery.py` | unknown-call discovery ("Sandsifter for RM") | CLI, `--surface demo\|rm` | `test_discovery.py` | 19, 21 | verified useful | deterministic probe generation + effect observer |
| `harness/mutation_harness.py` | bounded deterministic mutation | CLI `--iterations/--seed` | `test_harness.py` (integration, seed replay byte-identical) | 18, 20, 23 | verified useful | replayed identically in test |
| `harness/portability_matrix.py` | multi-target portability runs | CLI `--target` (repeatable) | `test_harness.py` (self-comparison all-zero deltas) | 18, 22 | verified useful | includes probe/scenario/compare sections |
| `harness/reach.py` | reachability answers for a target | CLI `--method auto\|stimulus\|trigger\|run\|call` | `test_discovery.py` | 19 | verified useful | emits suggested probe spec |
| `harness/version_diff.py` | old-vs-new firmware diff | CLI `--old/--new` | `test_version_diff.py` | 21, 25 | verified useful | composes ci_release |
| `harness/value_demos/*` | s01–s10 value validation + `run_all.py` | `python3 harness/value_demos/run_all.py [--only]` | `test_value_demos.py` | 25, MCP tools | verified useful | ran s02/s05 from the CLI; artifacts under `value_demos/artifacts/` |
| `plugins/example_plugin.py` | plugin authoring reference | `register(ge)` | plugin smoke (`--plugin`) | 04, plugins/README | verified useful | loaded via `greeneye run --plugin` |
| `plugins/mcp_server.py` | MCP server (stdio/loopback) | `--stdio`, `--tcp`, `--allow-write`, `--allow-inject` | `test_mcp.py` (431 lines) | 08/R3, 15, 20 | verified useful | read-only default; structured `not_available` fallbacks |
| `plugins/rmharness_plugin.py` | REPL commands for host RM oracle | `register(ge)`; commands `rmtool/rmtop/rmq` | `rmharness/tests/test_rmharness_plugin.py` (3 tests) | 17, 23 | verified useful | write-named args refused (`rm_harness_skip`) |
| `qemu/greeneye_trace.c` | full TCG exec/access JSONL trace | `make -C qemu`; `-plugin greeneye-trace.so,path=…` | none automated; **live-verified in this audit** | 10, 16, 14 | verified useful | 8M exec events in 1 s on the demo; vendored `qemu-plugin.h` builds out of the box |
| `qemu/launch.sh` | one-line `greeneye launch` wrapper | `qemu/launch.sh --kernel <elf>` | smoke | qemu/README | verified useful | thin `exec` of `greeneye.sh launch` |
| `ghidra/*.java` (Export/Mark/Control) | static/dynamic join inside Ghidra | `analyzeHeadless -postScript …` | protocol counterpart covered by `test_bridge.py`; no Ghidra installed here | 05, 13 | verified useful (static) | README documents exact command lines; Java not executed in this environment |
| `rmharness/rmtool.sh` | RM oracle wrapper | passthrough to `tools/rmtool` | `harness.sh` capture smoke (`gpu list`) | 17, 23 | verified useful | builds tool on first use; read-only |
| `rmharness/rmtop.sh` | dashboard wrapper | passthrough to `tools/rmtop` | `--help` | 17 | verified useful | `--once` for captures |
| `rmharness/rmq.sh` | pre-existing rmq wrapper | passthrough to `tools/rmq` | `--help` | 17 | needs improvement (fixed) | `--help` used to fall through and open the RM; wrapper now answers help itself |
| `rmharness/harness.sh` | capture tool run into a green-eye session | `--session DIR -- TOOL ARGS` | capture smoke (verified event + blobs) | 17, 22 | verified useful | additive recording; read-only allowlist enforced |
| `rmharness/tests/` | plugin registration check | `python3 rmharness/tests/test_rmharness_plugin.py` | 3 tests, run and green | 17 | needs improvement (open) | not part of `tests/` discovery nor CI; kept separate by design, documented in 17/README — consider CI step when the workflow is next edited |
| `sdk/injected_demo.c` | position-independent service SDK | `make sdk` | `test_injection.py`; `inject` smoke | 11, 12 | verified useful | direct + firmware call paths |
| `firmware/uproc_demo/` | bundled RV64 uproc (mailbox RPC/state/trap) | `make demo` | many integration tests | README, 00, 02 | verified useful | self-test + scripted stimulus work |
| `examples/scripts/dump_diff.py` | schema decode + snapshot diff demo | `./greeneye.sh run examples/scripts/dump_diff.py` | verified after `__file__` fix | 02 | needs improvement (fixed) | was broken by the `run` namespace gap; now prints decoded ctrl + changed bytes |
| `examples/scripts/trace_rpc.py` | hook every RPC handler | `./greeneye.sh run …` | ran | 02, 16, 18 | verified useful | 17 hook events from the demo |
| `examples/scripts/sim_bench.py` | native throughput benchmark | standalone CLI | ran (0.92 MIPS untraced, pure Python) | 16 | verified useful | self-bootstraps `sys.path`, works from anywhere |
| `examples/scripts/sim_diff.py` | QEMU-vs-native differential | standalone CLI | ran: `PARITY: OK` | 16 | verified useful | exit 1 on divergence, 0 on skip |
| `examples/probes/demo_probes.json` | declarative probe spec sample | loaded by `greeneye.probes.install` | installed + fired via `greeneye run` | 03 §9 | verified useful | 3 probes, hits `[1, 3, 0]` on the demo |
| `tests/` (15 files) | unit + live QEMU integration | `python3 -m unittest discover -s tests -t .` | **155 tests OK** | CI (`greeneye.yml`) | verified useful | includes live QEMU, native parity, MCP, bridge, harness, value demos |

## 4. Scripting API purpose review

* **Entry points and lifetime.** `ge.launch()` (QEMU or `backend="native"`)
  and `ge.attach()` return a `Session`; `close()` clears breakpoints and kills
  owned QEMU unless `keep_alive=True`. Documented in 03 §1 and the
  `__init__` docstring.
* **`greeneye run` namespace.** Preloads `ge`, `session`/`s`, `memory`,
  `symbols`, `recorder`, plus `__file__`/`__name__` (fixed here so scripts can
  locate bundled assets; `examples/scripts/dump_diff.py` depends on it).
  `docs/03-SCRIPTING.md` §intro now states this.
* **Static/dynamic join.** `SymbolDB` understands ELF, Ghidra name/index/
  export/callgraph TSVs and native JSON; `resolve()/annotate()` power every
  report. `by_subsystem` now has a unit test; `Session.symbol_at` is an
  untested-but-documented alias of `symbols.resolve`.
* **Safety.** Probe conditions are whitelisted (`compile_condition`), and the
  RSP/MCP surfaces never eval user text except through that whitelist.
* **Errors.** All library errors derive from `GreenEyeError`; `load_json` now
  reports file/parse failures as `GreenEyeError` instead of leaking
  `JSONDecodeError` through the CLI.
* **Gaps (deliberate).** No session rewind/replay API; TCG JSONL import into
  sessions is still roadmap (R4).

## 5. Plugin API purpose review

* **Contract.** `register(ge)`; `ge` exposes `session`, `memory`, `symbols`,
  `recorder`, `hook`, `watch`, `on_event`, `add_command`, `log`
  (`greeneye/plugins.py:36`, `plugins/README.md`). A raising plugin is
  recorded as `plugin_error` and cannot break the session.
* **Discovery order.** `--plugin PATH` → `GREENEYE_PLUGINS` → bundled
  `plugins/`. Documented in `plugins/README.md` and 04.
* **Bundled plugins.**
  * `example_plugin.py` — authoring reference; loaded in this audit.
  * `mcp_server.py` — the AI/agent surface; tested by `tests/test_mcp.py`,
    documented in 20. The old "MCP is planned/do not add it" notes in
    `plugins/README.md`, 04 and 08 are now corrected to point at 20.
  * `rmharness_plugin.py` — host oracle REPL commands; tested separately.
* **Gaps.** Plugins cannot yet declare tool capabilities for MCP discovery
  beyond `add_command`; `run_command` is REPL-only. Noted, not blocking.

## 6. Unused tools and decisions

No `prune candidate` was found: every unreferenced item was a small, coherent
public helper. Three were cheaply wired into unit tests; one remains
`not used yet` with a docstring. Nothing was deleted.

| item | decision | rationale |
|---|---|---|
| `greeneye.symbols.SymbolDB.by_subsystem` | **wired**: docstring + `test_core.test_by_subsystem` | natural read-only query alongside `names()` |
| `greeneye.elf.write_relocated_elf` | **wired**: `test_core.test_write_relocated_elf` verifies entry/PT_LOAD shift | complete ELF utility next to `write_flat_kernel` |
| `greeneye.ghidra.render_ghidra_command` | **wired**: `test_bridge.GhidraCommandTests` + cited in 05 §4 | renders the `MarkGreenEyeHits.java` command for scripts |
| `greeneye.session.Session.symbol_at` | `not used yet`: docstring only; kept | thin alias of `symbols.resolve`; external scripts may prefer the Session namespace |

Nothing was deleted.

## 7. Portability / extensibility / discoverability checks

* **Portability.** Stdlib-only Python; `greeneye.sh` pins `PYTHONPATH` to the
  tool root. Harness tools and `run_all.py` bootstrap their own `sys.path`.
  QEMU is optional (integration tests skip cleanly); the native backend makes
  the suite largely hardware-free. `qemu-plugin.h` is vendored so the TCG
  plugin builds without a QEMU source tree (verified).
* **Extensibility.** Three documented extension axes with stable contracts:
  Python scripts (`run`, doc 03), JSON probe specs (`probes`, doc 03 §9), and
  plugins (`register(ge)`, docs 04/20). Device behaviour is data, not code
  (`greeneye-device-map/1`, doc 24).
* **Discoverability.** `README.md` + `docs/00-OVERVIEW.md` doc maps were
  completed for docs 17–27 (previously they stopped at 16); `plugins/` and
  `harness/` are now listed in the README layout. `greeneye.sh --help` and
  `plugins/mcp_server.py --help` are self-describing.
* **CI.** `.github/workflows/greeneye.yml` builds SDK+demo and runs the main
  suite on Linux; `rmharness/tests` is the only suite the workflow misses.

## 8. How to tell if a tool is misbehaving (symptom → check → fix)

| tool | symptom | check | fix |
|---|---|---|---|
| `greeneye.sh` / CLI | `greeneye error: …` on a valid path | `./greeneye.sh info` shows resolved paths; `--help` for arguments | correct the path/arg; check `GREENEYE_QEMU`/`GREENEYE_DUMPS`; bad symbol DBs now fail with a clear message |
| `run` scripts | `NameError: __file__` (pre-fix) or names missing | `./greeneye.sh run` preloads `ge/session/s/memory/symbols/recorder/__file__`; run with `--backend native` to remove QEMU | keep scripts inside the tool tree and invoke through `greeneye.sh run`, not bare `python3` |
| hooks/watchpoints | callback never fires | hook an obviously-hit symbol (`rpc_sum`), `record` + `analyze`; check the session event counts | re-run demo to confirm firmware self-test; ensure the target is resumed (`run`) and the symbol exists (`symbols.get`) |
| `probe` | 0 faults reported | check `dumps/<run>/blobs/qemu.log` and `recorder.meta["qemu_fault_count"]` | expected for images that stay in mapped memory; use `--instructions` for a bounded step instead of `--duration` |
| native backend / trace | QEMU-vs-native diff fails | `python3 examples/scripts/sim_diff.py`; `analyze` on the trace blob | divergence means a real backend bug: capture both sessions and diff `exec`/`access` counts (see 16) |
| `inject` | service not found or call returns garbage | `make sdk`; check slot (`--slot 0..7`) and load address; hook `rpc_dispatch` | rebuild the SDK, restore the default slot, verify `dispatch` ABI (12 §) |
| harness mutation/portability | findings not reproducible | `--seed` must be fixed; compare `findings.jsonl` byte-for-byte | if a seed diverges, the target or environment changed: capture a session and diff the baseline |
| discovery / reach | everything "unreachable" | run `--surface demo` on the built demo first; check `--timeout/--settle` | raise settle time; confirm the stimulus hook matches the firmware's mailbox |
| version diff / CI release | huge spurious diff | check symbol biases/TSVs (`--*-symbols`, `--bias`) and labels | fix the bias first; use `--dry-run` to inspect the plan before writing artifacts |
| MCP server | client sees no tools / writes refused | `python3 plugins/mcp_server.py --help`; stdout must carry only protocol frames (logs go to stderr) | remove stdout prints from plugins; pass `--allow-write`/`--allow-inject` only when intended |
| `rmharness` wrappers | `--help` opens the device (pre-fix) or permission errors | `--help` is now device-free; `rmtool gpu list/topology` read `/proc`/sysfs only | run `rmtool.sh --help` to check the build; device-table queries need the driver's permission model (17 §Not implemented) |
| `qemu/greeneye-trace.so` | empty trace file | run `make -C qemu`; QEMU must reach the `start/end` window | widen `start/end`, set `exec=on`, and resume the target (plugin records only executed code) |
| device map | unexpected `unimplemented` read | `greeneye.device_map show <map>`; check `bias` and schema version | harvest from a `probe` session with the same bias; use `unknown_mmio="lenient"` for first-touch exploration |

## 9. Provenance discipline

Tracked tool-tree docs describe components and behaviour; no non-public
paths, filenames or source locations appear in them. The audit re-ran the
marker scan across this tree and found none. No further action.

## 10. Summary

| status | count | items |
|---|---:|---|
| `verified useful` | 57 | all 13 CLI commands (2 with a fixed gap); all 19 package modules; all `sim/`; all `harness/` incl. value demos; 3 plugins; QEMU plugin + launch helper; Ghidra scripts (static); all `rmharness/` (1 fixed gap); SDK, demo firmware; 5 example assets; tests |
| `not used yet` | 1 | `session.symbol_at` — docstring added, kept (section 6) |
| `needs improvement` | 5 | `run` `__file__` namespace (**fixed**), `symbols resolve --db` traceback (**fixed**), `rmq.sh --help` device touch (**fixed**), stale doc maps/MCP/status text in README/00/04/08/14 + `plugins/README` (**fixed**), `rmharness/tests` outside main suite/CI (**open**, documented) |
| `prune candidate` | 0 | nothing deleted |

Files changed by this audit:

* `greeneye/cli.py` — command docstring completed; `run` namespace sets
  `__file__`/`__name__`.
* `greeneye/symbols.py` — `load_json` raises `GreenEyeError` and now handles
  list payloads; docstrings for `by_subsystem`, `nearest`.
* `greeneye/session.py` — docstring for `symbol_at`.
* `greeneye/ghidra.py` — docstring for `render_ghidra_command`.
* `tests/test_core.py` — tests for list/bad JSON payloads, `by_subsystem`,
  `write_relocated_elf`.
* `tests/test_bridge.py` — test for `render_ghidra_command`.
* `rmharness/rmq.sh` — device-free `--help`.
* `README.md`, `docs/00-OVERVIEW.md` — doc maps completed (17–27), layout
  rows for `harness/`/`rmharness/`, status-table corrections.
* `docs/03-SCRIPTING.md` — documents `__file__`.
* `docs/04-PLUGINS.md`, `docs/08-ROADMAP.md`, `plugins/README.md` — MCP is
  implemented, not planned; point at 20.
* `docs/05-GHIDRA-BRIDGE.md` — documents `render_ghidra_command`.
* `docs/14-ADVERSARIAL-INTEROP-GUIDE.md` — capability table updated to
  implemented harness/device-map/MCP status.
* `docs/27-TOOL-AUDIT.md` — this document.

Verification after the changes: 155/155 unit tests OK; docs-link check OK
(55 files); every smoke in section 0 re-run green.

## 11. Post-audit additions (not yet audited)

`harness/port_driver_versions.py`, `harness/port_new_device.py` and
`harness/package_sim.py` with docs 28/29 landed after this audit; their tests
are `tests/test_porting_automation.py` and `tests/test_package_sim.py` (main
suite). They are unadmitted pilots under `eng-ops.md` §2 and have not had a
tool-audit pass, so treat their status as `needs improvement (open)` until one
does; docs 28/29 carry the exit-code and failure readings that the audit would
otherwise record.

<!-- change (2026-10-02): record the post-audit porting/packaging scaffolds as
unaudited rather than leaving the audit silent about them; scope: new §11 only;
benefit: the audit's scope statement stays true for the commit it describes
and the gap to the new code is explicit. -->