summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md')
-rw-r--r--debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md252
1 files changed, 252 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md b/debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md
new file mode 100644
index 0000000..e208253
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md
@@ -0,0 +1,252 @@
+# 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.