summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/26-ARCHITECTURE.md
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs/26-ARCHITECTURE.md')
-rw-r--r--debug/risc-v-gsp-rm-vm/docs/26-ARCHITECTURE.md449
1 files changed, 449 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/docs/26-ARCHITECTURE.md b/debug/risc-v-gsp-rm-vm/docs/26-ARCHITECTURE.md
new file mode 100644
index 0000000..326533b
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/docs/26-ARCHITECTURE.md
@@ -0,0 +1,449 @@
+# 26 — green-eye architecture: full picture, workflow and API surfaces
+
+> **# TOOL IN DEVELOPMENT - NOT READY YET!**
+> The architecture map for the green-eye suite: how the pieces fit, which
+> surface to call, and which document specifies each part. Every claim here is
+> implemented in the tree or explicitly marked as a gated roadmap design.
+> Development-only: never shipped, never a driver dependency, never in a guest.
+
+## 1. What green-eye is (and is not)
+
+green-eye runs GSP/uproc firmware in a controllable machine, observes it with
+Frida-like primitives, records a durable session, changes the machine's inputs
+(including injected code), and compares runs as evidence for `open-iov-vfio`
+emulation changes.
+
+| It is | It is not |
+|---|---|
+| A firmware runtime microscope (execution + memory, L0/L1) | A time, interrupt or device model (L2/L3 absent — [06](06-ACCURACY.md)) |
+| A scriptable `Session` API with two backends | A GDB wrapper; there is no GDB dependency |
+| A recorded-evidence system with a versioned format | A product component or a guest agent |
+| A static/dynamic join with Ghidra | A decompiler or a CUDA-corpus replacement |
+| An automation layer (harnesses, MCP, CI) | A fuzzing service against hardware; simulator/re-hosted code only |
+
+## 2. The suite as a whole
+
+```text
+ front doors control plane evidence
+ CLI/REPL/scripts ─┐
+ MCP (stdio) ──────┼─► GreenEyeAPI ─► Session ─┬─► QEMU-RSP backend ──┐
+ Ghidra bridge ────┤ (plugins) Memory │ qemu-system-riscv64│
+ harness/ ─────────┤ SymbolDB └─► native backend ────┼─► session dir
+ rmharness ────────┘ Recorder sim/machine.py │ events.jsonl
+ └─► analyze / compare /
+ export_hits / bundles
+```
+
+```mermaid
+flowchart LR
+ CLI["CLI/REPL/scripts"] --> CORE
+ MCP["MCP server"] --> CORE
+ GH["Ghidra bridges"] --> CORE
+ HAR["harness/*.py"] --> CORE
+ RMH["rmharness (read-only)"] --> CORE
+ CORE["GreenEyeAPI + Session<br/>hooks, watchpoints, call, run_until<br/>Memory, SymbolDB, Recorder"]
+ CORE --> QB["QEMU-RSP backend"] --> SESS
+ CORE --> NB["native backend (M1/M1.5)"] --> SESS
+ SESS["session dir, greeneye-session/1"] --> REP["analyze / compare / export_hits / MCP / bundles"]
+ RMH --> SESS
+```
+
+Status: transport, core, recording, both backends (native M1 trace included),
+injection ABI v1, both Ghidra bridges, traces, device map, harnesses, MCP (R3)
+and `rmharness` are implemented and exercised. The `Backend` protocol
+refactor (16 §5), the H2.2 QEMU device model, RM-core injection (H1.3) and the
+H3 M2–M6 milestones are design/roadmap only ([16](16-CUSTOM-SIMULATOR.md)).
+
+## 3. Session core: hooks, watchpoints and dispatch
+
+`Session` ([greeneye/session.py](../greeneye/session.py)) owns all stop-driven
+state and is backend-agnostic: every stop reads registers, resolves the PC,
+records an event, invokes matching hooks/watchpoints, then resumes.
+
+```mermaid
+sequenceDiagram
+ S->>FW: set breakpoint / watchpoint
+ loop run(duration, max_stops)
+ FW-->>S: stop reply (T/S/W)
+ S->>S: resolve PC via SymbolDB
+ S->>R: bp_hit / watch_hit / stop / step
+ alt symbol entry
+ S->>H: on_enter(hit) with a0..a7
+ S->>S: arm return breakpoint from ra (LIFO)
+ S->>FW: continue
+ else return breakpoint
+ S->>H: on_leave(hit), a0 as retval
+ else watchpoint (Z2/Z3/Z4)
+ S->>FW: clear watch, step over store, re-arm
+ S->>H: callback + store PC
+ end
+ S->>R: on_event(kind, hit)
+ end
+ S->>R: session_end on close
+```
+
+| Primitive | Semantics / limits |
+|---|---|
+| `breakpoint(addr, kind=sw\|hw, temporary=)` | sw is stepped over transparently; hw uses the backend's `Z1` |
+| `hook(target, on_enter, on_leave)` | leave is a return breakpoint on `ra`; tail calls/interrupt returns are not tracked |
+| `watch(target, size, kind=w\|r\|a, callback)` | QEMU stops *at* the access; Session clears/steps/re-arms; store PC is recorded |
+| `run(duration, max_stops)` | services stops until a budget or exit; the deadline path interrupts once |
+| `run_until(target, timeout)` | temporary breakpoint; raises `RspTimeout` when not reached |
+| `call(target, args, timeout)` | Frida-like remote call; restores `RESTORE_REGS` around it |
+| `step(n)`, `note(text)`, `on_event(kind, fn)` | single-step with dispatch; annotate and observe the stream |
+
+Instructions between stops are invisible unless the TCG plugin
+(`qemu/greeneye_trace.c`) or the native trace (`sim/trace.py`) is on
+(01 §11, 16 §1.1a).
+
+## 4. Backend abstraction: QEMU-RSP and native
+
+`Session` assumes a small RSP-shaped primitive set; `run_until` and `call`
+live above it and work on every backend. M1 implements the same method
+surface (`greeneye/sim_backend.py`) without the planned `Backend` protocol
+refactor (16 §5); the table is the M2+ target.
+
+```mermaid
+flowchart LR
+ S["Session (backend-independent)"]
+ S -->|"read/write reg+mem, step, continue, Z0-Z4"| RC
+ S -->|"same surface"| ST
+ subgraph QEMU["QEMU-RSP backend (default)"]
+ QT["QemuTarget<br/>qemu-system-riscv64 virt"] --- RC["RspClient gdbstub packets"]
+ end
+ subgraph NAT["Native backend (--backend native)"]
+ ST["SimTarget / SimClient"] --- MC["sim.machine.Machine<br/>RAM + UART + decode loop"]
+ MC --- TR["sim.trace.Trace exec/access"]
+ MC --- DM["DeviceMap lenient MMIO"]
+ end
+```
+
+| Session need | QEMU-RSP | Native |
+|---|---|---|
+| registers/memory | `g`/`p`/`P`, `m`/`M`/`X` chunked to `PacketSize` | direct register file and memory map |
+| breakpoints/watchpoints | `Z0`–`Z4` | dispatch-loop tables; same stop semantics |
+| snapshots | region copies over RSP | direct map copy; machine moments are M2+ |
+| virtual time | host wall clock | deterministic instruction count (`SIM_IPS`) |
+| full-access trace | TCG plugin (`-plugin`) | in-process `sim/trace.py`, `--trace` |
+| device environment | QEMU `virt` only (no TU104) | device map + lenient MMIO; real devices H2.2 |
+| injection | debugger writes + `Session.call` | same SDK ABI; RM interception is M5 |
+
+`--backend qemu|native` is accepted by `demo`, `run`, `attach`, `record`,
+`inject` and `ghidra`; `attach --port` and QEMU `--trace` are QEMU-only;
+`--trace` is native-only (16 §1.2).
+
+## 5. Recording and the evidence format
+
+A session directory is the durable artifact (`greeneye-session/1`,
+[greeneye/record.py](../greeneye/record.py)). The event stream is the map; the
+snapshot and blob files are the dump.
+
+```text
+dumps/<stamp>-<name>/
+ session.json # format, name, created, counts, snapshot inventory, meta
+ events.jsonl # append-only JSON lines: {"t": monotonic, "type": ...}
+ snapshots/*.bin.z # zlib-compressed region captures (sha256 recorded)
+ blobs/* # QEMU logs, trace.jsonl, rmharness stdout, plugin dumps
+ summary.md # rendered analysis
+```
+
+```mermaid
+flowchart LR
+ E["Session stops, memory ops,<br/>probe/plugin/injection events"] --> R["Recorder.event()"]
+ R --> J["events.jsonl (append-only)"]
+ M["Memory.snapshot()"] --> Z["snapshots/*.bin.z + sha256"]
+ B["Recorder.blob()"] --> BD["blobs/*: trace.jsonl, qemu.log"]
+ X["rmharness external runs"] --> J
+ J --> A["analyze: summary.md"]
+ J --> C["compare: A/B delta report"]
+ J --> G["export_hits: greeneye_hits.tsv"]
+ J --> S10["value_demos s10: manifest + M1-M6 skeleton"]
+```
+
+Event classes: core stop/memory events (`session_start`, `image_loaded`,
+`hook`, `watch`, `bp_hit`, `leave`, `watch_hit`, `stop`, `step`, `mem_read`,
+`mem_write`, `snapshot*`, `probe*`, `plugin*`, `note`, `exit`, `session_end` —
+01 §6); injection (`service_injected`, `call_enter`, `call_return` — 12 §6);
+traces (`exec`/`access`); M2/M3 native bus (`device`/`irq`/`schedule` — 16
+§10); `rmharness` (`rm_harness_run`, `t=0.0` with epoch `start`/`end` — 17 §5).
+
+Format invariants: append-only (partial sessions stay readable); additive
+versioning with migration notes (V2); `compare` treats `t`, host paths and
+backend identity as non-semantic; a claim without a session is not evidence
+(06 §7, 25 §1).
+
+## 6. Injection SDK lifecycle
+
+The SDK (`sdk/greeneye_service.h`, `sdk/injected_demo.c`) builds
+position-independent ELF64 `EM_RISCV` blobs; `greeneye/inject.py` loads,
+registers and invokes them. ABI v1: `ge_u64 (*)(ge_u64 cmd, ge_u64 a0,
+ge_u64 a1)`, magic `0x47455356`, entry `ge_demo_service_dispatch`.
+
+```mermaid
+sequenceDiagram
+ Dev->>Inj: load_service(elf, slot=6, load_addr)
+ Inj->>FW: write PT_LOAD segments into RAM
+ Inj->>FW: patch gsp_ctrl.handlers[slot] entry
+ Inj->>Rec: service_injected + mem_write events
+ Dev->>Inj: invoke_remote(cmd, a0, a1)
+ Inj->>FW: Session.call(entry, args)
+ Rec-->>Rec: call_enter / call_return
+ Dev->>FW: mailbox = 16 + slot (arg0=cmd, arg1, arg2)
+ FW->>FW: dispatch handlers[slot]
+ FW-->>Rec: hook/watch events + result field
+```
+
+Demo registration: mailbox commands 16..23 select slots 0..7; slot 6 is the
+SDK default; control-block offsets (`mailbox +0x20`, `handlers +0x150`) are
+demo-specific and must come from `greeneye/inject.py` constants (12 §4).
+Cleanup is not automatic in v1; record the injection so runs start from a
+known state. Roadmap: RM-core shim, service registry and `log`/`stub`/
+`forward` interception at M5/H1.3 (12 §8, 16 §11).
+
+## 7. Ghidra bridges (two directions)
+
+The static/dynamic join uses three address spaces — file vaddr, Ghidra program
+addr, runtime addr — with a bias per source (01 §5, 05 §1).
+
+```mermaid
+sequenceDiagram
+ G->>B: {"id":n,"cmd":"run_until","args":{"target":"0x..."}}
+ B->>S: run_until(addr) (temporary breakpoint)
+ S->>T: continue; service intervening stops
+ T-->>S: stop at addr
+ S-->>B: {addr, symbol, kind}
+ B-->>G: JSON response
+ G->>B: read_mem / read_reg / call / snapshot / hits / export_hits
+ B->>R: hits and notes recorded in the session
+ R-->>G: greeneye_hits.tsv (via MarkGreenEyeHits.java)
+```
+
+| Tool | Purpose | Doc |
+|---|---|---|
+| `symbols export` + `SymbolDB` loaders | Ghidra TSV/JSON → runtime symbol DB | [05](05-GHIDRA-BRIDGE.md) |
+| `greeneye.ghidra.export_hits` | session hits → `addr<TAB>hits<TAB>symbol<TAB>annotation` | 05 |
+| `ExportForGreenEye.java` | Ghidra → `addr<TAB>name<TAB>size` TSV | 05 |
+| `MarkGreenEyeHits.java` | annotate functions with runtime hits + bookmarks | 05 |
+| `GreenEyeControl.java` + `greeneye.sh ghidra` | drive a live session from GhidraScripts over localhost JSON-lines | [13](13-GHIDRA-CONTROL.md) |
+
+Bridge commands: `ping`, `info`, `resolve`, `bp_set`, `bp_clear`, `bp_list`,
+`watch_set`, `run_until`, `run`, `step`, `read_mem`, `write_mem`, `read_reg`,
+`write_reg`, `call`, `snapshot`, `note`, `hits`, `export_hits`, `close`
+(13 §3). Unauthenticated by design; localhost only.
+
+## 8. MCP server tool surface
+
+`plugins/mcp_server.py` serves the active `Session` over JSON-RPC 2.0 on
+stdio (or loopback TCP) with no MCP library; read/observe by default,
+`write_mem`/`call_function` require `--allow-write`, `inject_service`
+requires `--allow-inject` ([20](20-MCP-SERVER.md)).
+
+```mermaid
+flowchart LR
+ AG["AI agent (Claude Code, opencode)"] -->|"JSON-RPC 2.0 over stdio"| M["plugins/mcp_server.py"]
+ M --> READ["read: session_info, symbols_resolve,<br/>read_mem, hexdump, snapshot, plugins_list"]
+ M --> OBS["observe: bp_set/clear, watch_set,<br/>run, run_until, step (recorded)"]
+ M --> WR["write (gated): write_mem, call_function"]
+ M --> INJ["inject (gated): inject_service"]
+ M --> TR["trace: trace_start/stop"]
+ M --> AN["analysis: analyze_current, compare_sessions, export_hits"]
+ M --> HN["harness: probe_firmware, mutation_run,<br/>portability_matrix_run"]
+ M --> CL["session: close"]
+ READ & OBS & WR & INJ & TR & AN & HN --> S["Session + Recorder"]
+```
+
+All results are one JSON text block; absent modules return a structured
+`not_available` error; stdout carries protocol frames only (20 §4, §8). Full
+argument schemas for all 24 tools are in 20 §5.
+
+## 9. Automation harness
+
+`harness/` turns the playbooks into repeatable, self-describing runs. Modules
+bootstrap the tool root and use the QEMU backend by default (18, 19, 21);
+`common.py` provides flat-image relocation, session helpers and bounded log
+reads.
+
+```mermaid
+flowchart TB
+ FW["firmware ELF / .fwimage"] --> PM & MU & CD & RE & VD & LV
+ SYM["Ghidra exports + symbol DBs"] --> PM & MU & CD & RE & VD & LV
+ STIM["stimulus / probe spec"] --> PM & MU & CD & RE & VD & LV
+ SEED["seed + budgets"] --> PM & MU & CD & RE & VD & LV
+ subgraph H["harness/"]
+ PM["portability_matrix.py R6/H4.1"]
+ MU["mutation_harness.py R5/H4.3"]
+ CD["control_discovery.py H4"]
+ RE["reach.py H4.1/H4.5"]
+ VD["version_diff.py H4.1"]
+ CR["ci_release.py"]
+ LV["value_demos/ s01..s10"]
+ end
+ VD --> CR
+ PM --> CR
+ MU --> CR
+ PM & MU & CD & RE & VD & LV & CR --> OUT["reports + sessions + findings.jsonl<br/>+ compare/ + bundle-manifest.json"]
+```
+
+| Entry point | Purpose | Outputs | Doc |
+|---|---|---|---|
+| `portability_matrix.py` | probe + symbols + fixed scenario + compare across targets; M1–M6 skeleton | `portability-report.json/.md`, `symbols/`, `sessions/`, `compare/` | 18 §1 |
+| `mutation_harness.py` | seeded one-input mutation, outcome classes, per-attempt session | `findings.jsonl`, `summary.md`, `sessions/` | 18 §2 |
+| `control_discovery.py` | sweep command/call surfaces; classify handler/default/trap/fault | `findings.jsonl`, `discovery-report.json`, `control-discovery.md` | 19 §3 |
+| `reach.py` | reachability, trigger discovery, required args and effects for one symbol | `reach-report.json/.md`, `probe.json`, sessions | 19 §4 |
+| `version_diff.py` | old vs new image: shape, symbols, corpus hints, probe, behavior, M1–M6 | `version-diff.json/.md`, `corpus/`, `compare/` | 21 §2 |
+| `ci_release.py` | version_diff → matrix → mutation smoke, then hash the bundle | `release-report.json`, `bundle-manifest.json` | 21 §4 |
+| `value_demos/run_all.py` | ten fixed scenarios (reply, counters, discovery, reach, diff, inject, version, mutation, Ghidra, evidence) | `artifacts/results.json` + per-demo JSON | 25 |
+| `common.py` | shared setup (relocate to `0x80000000`, open session, bounded logs) | library | 18 §1.2 |
+
+Harness limits are intentional: QEMU/re-hosted/demo code only; no hardware,
+guest, kernel module or existing gdbstub; bounded budgets; deterministic
+findings for a fixed seed (18 §5, 19 §8, 21 §7).
+
+## 10. Device map and lenient MMIO data flow
+
+`greeneye-device-map/1` is versioned JSON keyed by
+`(address, width, direction)` with values, hit counts and firmware PCs
+([24](24-DEVICE-MAP.md)). It records what the firmware touched first and lets
+the native interpreter step past unmodelled registers to collect the next
+ones; it feeds the H2.2 QEMU device model and the H3 profile DSL.
+
+```mermaid
+flowchart LR
+ P["greeneye probe (raw .fwimage in QEMU)"] -->|"blobs/qemu.log + qemu_first_fault"| H["device_map.py harvest"]
+ H --> MAP["greeneye-device-map/1<br/>entries + meta (bias, sha256)"]
+ MAP --> LOOK["DeviceMap.lookup<br/>zero / value / unimplemented"]
+ LOOK --> LEN["Native Machine(unknown_mmio=lenient)"]
+ LEN --> LOG["machine.mmio_log / mmio_unknown"]
+ LEN --> LEARN["learn_mmio=True: source trace entries"]
+ LEARN --> MAP
+ MAP --> PROF["docs/16 profile DSL<br/>falcon/csb/ptimer/pmu stubs"]
+ PROF --> H22["H2.2 QEMU device model"]
+ LOG --> NEXT["next unmapped wall; promote to manual"]
+ NEXT --> MAP
+```
+
+Honest scope: lenient execution proves an unmodelled access *can* be served
+deterministically; it does not model read-back, side effects, interrupts or
+timing, and a zero that keeps the firmware running is a hypothesis, not a
+silicon fact (24 §4).
+
+## 11. Workflow architecture: scenario → evidence → emulation change
+
+The canonical loop (H4.5, 06 §5, 07 P4, 25 §7). Each stage names its gate.
+
+| Stage | Action / instrument | Success criterion | Failure signal |
+|---|---|---|---|
+| 1. Scenario | state the firmware question and the smallest stimulus (one command/control/boot phase) | question maps to a symbol or surface | no observable exists → add one (probe/plugin) |
+| 2. Choose instrument | hooks/watchpoints for semantics; TCG/native trace for coverage; `run_until`/`call` for reachability; injection for new code | stop set answers the question without flooding | session too large/noisy → filter PCs, watch fields only |
+| 3. Run | fresh bounded session; fixed stimulus, seed and budgets | run completes; UART/banner liveness holds | timeout, trap loop, wedged target → capture and retry clean |
+| 4. Record | `greeneye-session/1` with snapshots and notes | events + snapshots + hashes written | missing `session.json`/empty analyzer → recording off |
+| 5. Compare | `compare` against a reference (old firmware, hardware/VFIO capture, self-diff) | deltas explained symbolically; self-diff is zero | unexplained hit/watch/write delta → investigate first |
+| 6. Decide | fill M1–M6 with the reference method (06 §5); mutation/triage for error paths | metric table improves or stays equal | metric claimed without a reference capture |
+| 7. Implement | change `open-iov-vfio` (never the simulator for a product fix); keep the stimulus | physical confirmation for behaviour changes (06 §6, 14 §7) | product change from a simulator-only observation |
+| 8. Regression | re-run step 3 with an unchanged stimulus; golden sessions/CI (`make test`, `greeneye.yml`) | profile improves or stays equal; CI green | regression → revert and re-derive |
+
+```mermaid
+flowchart LR
+ Q["Question"] --> OBS["Observe<br/>probe / hooks / watch / trace"]
+ OBS --> ORI["Orient<br/>analyze + symbol decode + snapshots"]
+ ORI --> DEC["Decide<br/>compare + M1-M6 + mutation"]
+ DEC --> ACT["Act<br/>implement in open-iov-vfio"]
+ ACT --> REG["Regression<br/>same stimulus, same budgets"]
+ REG -->|"improved / equal"| SHIP["Evidence pack<br/>sessions + report + hashes"]
+ REG -->|"regressed"| Q
+ SHIP --> OBS
+```
+
+OODA compression evidence (analog, measured) is in 25 §7: hardware cycles are
+minutes and campaigns hours/days; the green-eye path for the same question
+class is sub-second to seconds plus scripting.
+
+## 12. API surfaces by tool
+
+### 12.1 CLI (`greeneye.sh`, `greeneye/cli.py`; usage in [02](02-USAGE.md))
+
+| Command | Purpose | Not for |
+|---|---|---|
+| `info` | environment/tooling check | validating a firmware image |
+| `demo` | canned boot + hooks + watch + record on the bundled uproc | booting real GSP (use `probe`) |
+| `run <script.py>` | run a Python instrumentation session | shell pipelines (write Python) |
+| `attach` / `launch` | interactive REPL / keep a target alive by port | injecting into a live product |
+| `record` | boot and record with watches/snapshots/trace | post-processing an existing session |
+| `probe <image>` | bounded run of a raw image; first divergence report | booting the image (no TU104 model) |
+| `inject` | load/register/invoke an SDK service | shipping or hot-patching signed firmware |
+| `ghidra` | serve a live session to GhidraScripts | network exposure (localhost only) |
+| `analyze <session>` / `compare <a> <b>` | markdown report / symbolic A/B delta | proving accuracy without a reference |
+| `symbols export\|resolve` | build/inspect a symbol DB | recovering types/decompilation |
+| `plugins` | list discoverable plugins | loading untrusted code into the core |
+| `python3 -m greeneye.device_map harvest\|show\|merge\|demo` | MMIO first-touch map data | a device model or timing source |
+
+### 12.2 Python entry points (recipes in [03](03-SCRIPTING.md))
+
+| Entry point | Purpose | Not for |
+|---|---|---|
+| `ge.launch/attach`, `Session.hook/watch/run/run_until/call/step/note` | session lifecycle and instrumentation | in-process C instrumentation |
+| `Memory.read/write/hexdump/search_*/snapshot/diff/render_diff` | memory inspection and region diff | full-machine snapshots (M2+) |
+| `SymbolDB.load_elf/*_tsv/*_json`, `resolve/annotate` | address-space-aware symbols | type recovery from Ghidra |
+| `structs.schema_from_json/decode/render` | declarative struct decoding | automatic struct discovery |
+| `probes.load_spec/install` | JSON probe specs (no arbitrary code) | complex control flow (write a script) |
+| `inject.load_service/invoke_remote/invoke_via_firmware` | ABI v1 service load/invoke | product features or persistence |
+| `ghidra.build_symbol_db/export_hits/corpus_location` | Ghidra TSV in/out and corpus location | replacing Ghidra analysis |
+| `analyze.render_summary/compare_sessions/parse_qemu_faults/load_trace` | offline reports and QEMU fault parsing | live control |
+| `record.Recorder/load_events/load_meta` | session writing/reading | a database (files are the interface) |
+| `device_map.harvest_*/DeviceMap.*` | MMIO map authoring/merge | device semantics |
+| `sim_backend.launch_native`, `sim.trace.Trace/make_trace` | native backend + trace knobs | timing/concurrency claims |
+| `errors.*` (`GreenEyeError`, `RspTimeout`, ...) | typed failure surface | retrying a wedged target blindly |
+
+### 12.3 Plugin API (`register(ge)`, [04](04-PLUGINS.md))
+
+`ge` exposes live objects (`session`, `memory`, `symbols`, `recorder`),
+instrumentation (`hook`, `watch`, `on_event`, `resolve`), CLI extension
+(`add_command(name, fn, help)`, `commands`) and audit logging (`log`). A
+raising plugin is recorded as `plugin_error` and skipped. Not for: shipping
+dependencies, assuming a specific firmware, or hiding unrecorded state.
+
+## 13. Extensibility and invariants
+
+Where extension lives:
+
+* **Plugins** (`plugins/`, `GREENEYE_PLUGINS`) — runtime RE tools, harnesses,
+ environment updates, agent integrations (04).
+* **Harness modules** (`harness/`) — new sweeps/reports over the public
+ `Session`/analysis primitives; a new call surface is a `Surface` subclass
+ (19 §5.1).
+* **SDK services** (`sdk/`) — PIC blobs behind ABI v1; v2 adds an RM-core call
+ table (12 §8).
+* **Device maps/profiles** — versioned data (`greeneye-device-map/1`, the
+ `greenmachine-profile/1` DSL in 16 §7), never core special cases.
+* **MCP tools** (`plugins/mcp_server.py`) — add tools in the plugin, keep the
+ core stdlib-only (20 §10).
+
+Invariants (enforced by tests or review):
+
+1. **stdlib-only core**: `greeneye/` and `sim/` import only the standard
+ library; optional deps live in plugins/harnesses.
+2. **Session format**: `greeneye-session/1` is append-only and evolves
+ additively with migration notes (V2).
+3. **Backend parity**: every backend satisfies the same `Session` primitives,
+ so scripts, probes, plugins, recordings and comparisons survive backend
+ changes (16 §4, R0).
+4. **ABI**: blobs declare an ABI version and capability metadata; breaking
+ changes need a shim and a doc note (12 §8, V3).
+5. **Markers**: the `TOOL IN DEVELOPMENT` banner stays until the acceptance
+ list in 00 passes; never shipped, never in a guest, never a driver
+ dependency.
+6. **Evidence**: no accuracy, portability or security claim without a recorded
+ A/B session and an explicit L2/L3 fidelity statement (06 §5–6, 25 §10).
+7. **Provenance**: tracked docs never name local source trees; public
+ descriptions first, local provenance in the local notes (V6).
+
+## 14. Diagram and document index
+
+Section diagrams and their backing docs: §2 suite (00, 01, README); §3
+dispatch (01, 03); §4 backends (01 §8, 16 §4–5); §5 recording (01 §6, 09, 12
+§6, 16 §10, 17 §5); §6 injection (11, 12); §7 Ghidra (05, 13); §8 MCP (20);
+§9 harness (18, 19, 21); §10 device map (24, 16 §7); §11 OODA (06, 07 P4, 25
+§7). Roadmap keys: R3 = MCP (08), R5 = mutation (18), R6 = multi-version (18,
+21), H1.3 = RM service injection (GREEN-EYE-ROADMAP), H2.2 = QEMU device
+model, H3/M1–M6 = native simulator (16), H4 = adversarial navigation (14),
+H7 = agentic platform (15, 20).