summaryrefslogtreecommitdiffstats
path: root/debug
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 11:28:49 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 11:28:49 -0400
commitc17da06d6cd8b9c69449904637bf37729a962989 (patch)
treeb51618fd63b776cd545f64fd40729af6154996b2 /debug
parent002e3d7d13c414ea874abbee8d82803dd3a72fe5 (diff)
downloadopen-iov-c17da06d6cd8b9c69449904637bf37729a962989.tar.gz
open-iov-c17da06d6cd8b9c69449904637bf37729a962989.zip
docs: engineering-operations playbook and project-wide status accuracy pass
- docs/eng-ops.md gains Part II, live physical-backend engineering operations: hard rules, the full module-parameter matrix, the routine supervised cycle and clean-release checklist, the debugfs inventory, how to read gsp-phys-state, a debugging scenario catalogue (CTXNOTVALID/promote wedge, fail-safe WPR2, unload hangs, guest wedges, MMU/PTE and PBDMA faults, relay symptoms), a hazard register (crash causes and debugging side effects), the physical-backend OODA loop, and the work-context map with porting flows - architecture, implementation, operations, orientation, getting-started, portability, ecosystem and reference docs corrected to the current state: CUDA driver-API smoke green, CE-class work forwarding and engine-authored releases plus fault relay in place (default off), GR execution blocked on engine-context validity, the promote wedge and designed locked fail-safe, and P5 wave-2 subsumed - internal-reference cleanup in tracked docs (no analysis-source or local-archive references); historical architecture document bannered - README reading list and STATE-CURRENT frontier refreshed; new pages (runbook, green-eye user guide, porting sequence, porting/packaging notes) linked from the tree Link check: scripts/check-docs-links.py passes (56 files, no broken links).
Diffstat (limited to 'debug')
-rw-r--r--debug/risc-v-gsp-rm-vm/docs/28-PORTING-AUTOMATION.md319
-rw-r--r--debug/risc-v-gsp-rm-vm/docs/29-PACKAGING-AND-TRIALING.md246
2 files changed, 565 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/docs/28-PORTING-AUTOMATION.md b/debug/risc-v-gsp-rm-vm/docs/28-PORTING-AUTOMATION.md
new file mode 100644
index 0000000..4669f93
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/docs/28-PORTING-AUTOMATION.md
@@ -0,0 +1,319 @@
+# 28 — Porting automation: driver-version sweep and new-device scaffold
+
+> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status and
+> [`GREEN-EYE-ROADMAP.md`](../GREEN-EYE-ROADMAP.md) H4/R6 for the program.
+
+Two stdlib-only orchestrators turn the docs/07 P1/P2 playbooks and the
+docs/22 porting guide into one repeatable chain: a **driver-version sweep**
+(`harness/port_driver_versions.py`) and a **device-port scaffold**
+(`harness/port_new_device.py`). They reuse the version-diff, portability-matrix
+and mutation machinery already documented in docs/18 and docs/21, add the
+RM 610 gate verbatim from `version_diff.py`, and never download firmware,
+never touch hardware, and write only under `--out`. Both are **unadmitted
+pilots** under `eng-ops.md` §2 — the keep/retire evaluation is F2/F3 in
+[`future-eng-ops.md`](../../../docs/future-eng-ops.md), and a sweep is not
+routine release tooling until that record exists.
+
+| Tool | Roadmap | What it does |
+|---|---|---|
+| `harness/port_driver_versions.py` | **R6**, H4.1 | Finds each driver/firmware version's local image, runs version_diff vs a baseline, the portability matrix and a mutation smoke, and assembles per-version evidence bundles plus a cross-version summary with the docs/06 M1–M6 skeleton. |
+| `harness/port_new_device.py` | **R6**, H4.1, docs/07 P2 | Creates/uses a user-supplied device profile, probes the device firmware's first touch, builds a symbol inventory, runs the matrix against a reference, and emits a port checklist with a go/no-go recommendation. |
+
+Both are dry-run by default, resume-safe (sweep), bounded by QEMU budgets plus
+a per-step wall-clock net, and simulator-only (no `/dev`, kernel module, mdev,
+guest or physical GPU).
+
+---
+
+## 1. The chain
+
+```text
+new driver / GSP image lands on disk (runfile, .fwimage, re-hosted ELF)
+ |
+ +-- port_driver_versions --dry-run plan: inputs found/missing, steps, artifacts
+ | |
+ | +-- --run per version:
+ | 1. target discovery/identification (never downloads)
+ | 2. version_diff vs the chosen baseline
+ | 3. portability_matrix (baseline + candidate)
+ | 4. mutation_smoke (bounded, seeded)
+ | 5. per-version bundle-manifest.json
+ | +-- sweep-summary.json/.md (M1-M6 skeleton, pass/fail interpretation)
+ |
+ +-- port_new_device --dry-run plan: profile fields, firmware, steps
+ |
+ +-- --run profile + probe-first-touch + symbols + matrix + checklist
+```
+
+The chain is the automation of docs/07 P1 (new GSP version), P2 (same version,
+new device) and the docs/22 port checklists; the metric contract stays
+docs/06 §5. Evidence is always attachable to the docs/07 checklist and the
+docs/14 §6 pack.
+
+---
+
+## 2. Driver-version sweep
+
+### 2.1 Known branch cadence
+
+The default version list documents the branch cadence and where to edit it —
+the tool never invents an image or downloads one:
+
+| branch | expected image | default sweep |
+|---|---|---|
+| 540 | earlier branch | yes |
+| 550 | earlier branch | yes |
+| 560 | earlier branch | yes |
+| 570 | current anchor (570.144 and patches) | yes |
+| 580 | expected cadence, no local image assumed | no |
+| 610 | RM 610 gate, refused without approval (docs/23) | no |
+
+Edit `DEFAULT_VERSIONS` in `harness/port_driver_versions.py`, or override on
+the command line / in a config file. A branch with no local image is reported
+`no-input`; it is not an error and nothing is fetched.
+
+### 2.2 Invocation
+
+Dry-run first (the default; reads input directories only):
+
+```bash
+cd debug/risc-v-gsp-rm-vm
+python3 harness/port_driver_versions.py --input-dir ../../firmware/57X-GSP \
+ --versions 540,550,560,570
+```
+
+Execute, resume-safe, with explicit targets and per-version symbol DBs:
+
+```bash
+python3 harness/port_driver_versions.py --run \
+ --versions 570.144 \
+ --target 570.144=../../firmware/57X-GSP/570.144/gsp-570.144.fwimage.bin \
+ --baseline 570.144 \
+ --symbols 570.144=symbols-570.144.json \
+ --out dumps/sweep-570.144
+```
+
+Cron (weekly, quiet, resume-safe; completed versions are skipped):
+
+```cron
+17 3 * * 1 cd /path/to/open-iov/debug/risc-v-gsp-rm-vm && \
+ python3 harness/port_driver_versions.py --run \
+ --input-dir ../../firmware/57X-GSP --versions 540,550,560,570 \
+ --out dumps/sweep-570 >> dumps/sweep-570/cron.log 2>&1
+```
+
+Agent (docs/20): run the dry-run to get the plan, use
+`portability_matrix_run`/`mutation_run` for single steps if the sweep itself is
+not exposed by MCP yet, then read `sweep-summary.json` and the per-version
+`bundle-manifest.json` from the bundle instead of recomputing. The dry-run plan
+is machine-readable (`greeneye-port-sweep-plan/1`) and side-effect free.
+
+Flags that matter:
+
+* `--versions` / `--config` — explicit list or JSON config (below);
+* `--input-dir` — recursive discovery of local `.fwimage`, `.fwimage.bin`,
+ `.elf`, `.bin` and `.run` files; version matching uses file and directory
+ names (`570` matches `570.144`);
+* `--target VERSION=PATH`, `--symbols VERSION=PATH`,
+ `--ghidra-exports VERSION=PATH` — explicit inputs, no discovery;
+* `--baseline` — version diffed against (default: earliest version found);
+* `--extract-runfiles` — opt in to local `sh <runfile> --extract-only`
+ extraction when only a driver runfile is present (still never downloads);
+* `--force` — re-run versions whose `version.json` reports `ok`/`partial`;
+* `--step-timeout` — wall-clock safety net per step (`0` disables; QEMU
+ budgets remain the first bound);
+* the usual `version_diff`/`matrix`/`mutation` budgets.
+
+### 2.3 Configuration file
+
+```json
+{
+ "format": "greeneye-port-sweep-config/1",
+ "versions": ["540", "550", "560", "570.144"],
+ "baseline": "570.144",
+ "input_dir": "../../firmware/57X-GSP",
+ "targets": {"570.144": ["../../firmware/57X-GSP/570.144/gsp-570.144.fwimage.bin"]},
+ "symbols": {"570.144": "symbols-570.144.json"}
+}
+```
+
+Unknown keys are refused so a typo cannot silently change the sweep. CLI flags
+override config values.
+
+### 2.4 Output and evidence bundle
+
+```text
+<out>/
+ sweep-summary.json cross-version report (also .md)
+ bundle-manifest.json sha256 + size of every artifact below
+ <version>/
+ version.json status, inputs, steps, M1-M6, sha256
+ version.md rendered per-version report
+ bundle-manifest.json per-version evidence bundle
+ version-diff/ docs/21 §2.3 tree
+ portability-matrix/ portability-report.json|.md, symbols/, sessions/, compare/
+ mutation-smoke/ findings.jsonl, summary.md, sessions/
+```
+
+`sweep-summary.json` carries per-version status, the cross-version M1–M6
+skeleton (every agreement metric `not measured` without a reference capture,
+docs/06 §5), the RM 610 gate record, and an explicit `interpretation` string:
+`pass`, `fail` (with the failing versions) or `not-run`.
+
+### 2.5 Reading failures
+
+| Status | Meaning | Action |
+|---|---|---|
+| `ok` | all steps ran, none errored | attach the bundle to the docs/07 checklist |
+| `partial` | a step skipped (e.g. no QEMU, no mutation kernel) | explain the skip; re-run where the capability exists |
+| `error` | a step raised or timed out | inspect `<version>/version.json`, then the step report (`version-diff.json`, `portability-report.json`, `findings.jsonl`) |
+| `no-input` | no local image found for the version | add the image under `--input-dir`, pass `--target`, or drop the version from the list |
+| resumed | `version.json` already reported `ok`/`partial` | use `--force` to re-run |
+
+`--run` exits non-zero when any executed version errored or when nothing ran
+at all; skipped steps alone do not fail the sweep, but they are always listed.
+An unexplained delta blocks the docs/07 acceptance: an empty
+unexplained-delta list is a claim, not a default.
+
+### 2.6 RM 610 gate (hard, testable)
+
+Versions numerically `>= 610` or labelled `rm610`/`RM-610` are refused before
+any target file is opened — in dry-run too — unless `--approval-token
+RM610-APPROVED` is passed or `GREENEYE_RM610_APPROVED=1` is set. The refusal
+text points at [`docs/23-RM610-READINESS.md`](23-RM610-READINESS.md) and
+states that nothing was downloaded and no target was opened. Do not put a 610
+input in a default list or a cron line before the §6 approval message exists.
+
+---
+
+## 3. New-device scaffold
+
+### 3.1 Profile
+
+`--device <name>` uses `--profile FILE` or `<out>/profiles/<name>.json`. A
+missing profile is created as a scaffold (`greeneye-device-profile/1`) with the
+user-supplied sections and a `where_to_fill` map:
+
+* `pci` — vendor/device/subsystem IDs (`lspci`, vendor docs);
+* `bars` — BAR layout and sizes (guest `lspci -vvv`, host sysfs);
+* `fb` — VRAM/BAR1 geometry (host oracle `rmtool mem stats --format=json`);
+* `engines` — engine inventory (host oracle `rmtool engine list --format=json`);
+* `interrupts` — interrupt topology (MSI-X);
+* `rom` — ROM/VBIOS notes and hash;
+* `firmware` / `reference` — local image paths (or `--firmware`/`--reference`);
+* `firmware_family` / `firmware_version` — the pairing record (docs/07 P2.2);
+* `physical_validation` — supervised physical evidence, recorded by the human,
+ never by this tool.
+
+Profile data is user-supplied; the tool never guesses a device ID, BAR or
+engine list. `config/hardware/*.yaml` remains a design target (see
+hardware-software-matrix §6) — do not treat it as live configuration.
+
+### 3.2 Invocation
+
+```bash
+# dry-run: check profile fields and inputs, create nothing
+python3 harness/port_new_device.py --device rtx4090 --profile profiles/rtx4090.json
+
+# execute: scaffold profile (if absent), probe, symbols, matrix, checklist
+python3 harness/port_new_device.py --run --device rtx4090 \
+ --firmware ../../firmware/57X-GSP/570.144/gsp-570.144.fwimage.bin \
+ --reference firmware/uproc_demo/build/uproc_demo.elf \
+ --out dumps/port-rtx4090
+```
+
+Output tree:
+
+```text
+<out>/<device>/
+ device-port.json / device-port.md
+ port-checklist.json / port-checklist.md
+ symbols/<device>.json merged ELF/Ghidra/green-eye DB + provenance
+ sessions/<firmware>-probe/ recorded first-touch session
+ portability-matrix/ matrix report + sessions + compare
+ bundle-manifest.json sha256 of every artifact
+<out>/profiles/<device>.json scaffolded profile (when absent)
+```
+
+The checklist items are the docs/07 evidence checklist plus the docs/22 §3.1
+profile fields; the recommendation is `hold` until the simulator-side items
+are complete, `simulator-evidence-complete` while physical validation is
+pending, and `go` only when `physical_validation.status` is recorded. The last
+row is deliberately outside this tool's reach: simulator-only evidence never
+justifies a support claim (docs/22 §3.5, hardware-software-matrix §5.1).
+
+---
+
+## 4. Tests, CI and MCP ties
+
+```bash
+python3 -m unittest tests.test_porting_automation -v
+make test # includes these with the rest of the suite
+```
+
+`tests/test_porting_automation.py` is deterministic: `--help` clean,
+dry-runs side-effect free, missing-input handling without tracebacks, resume
+detection, config validation, the RM 610 gate (refusal, token, env), and a
+tiny one-version QEMU sweep against the demo ELF that must produce the
+evidence bundle; QEMU-dependent tests skip cleanly when QEMU or the demo ELF
+is absent. The repository CI workflow (docs/21 §5) runs the suite on pushes
+and PRs touching the tool tree; a full sweep against real firmware is driven
+manually/agentically, dry-run first, because it needs versioned inputs and
+symbol DBs — the same policy as `ci_release` (docs/21 §4.1).
+
+MCP (docs/20) exposes the step-level tools (`probe_firmware`,
+`portability_matrix_run`, `mutation_run`); the sweep is the CLI/agent entry
+point that sequences them and owns the bundle. An agent should read the dry-run
+plan, run, then read `sweep-summary.json`/`device-port.json` and the manifests
+rather than recompute hashes.
+
+---
+
+## 5. When to extend
+
+* **New branch cadence** — add the version to `--versions`/config; if it should
+ be swept by default, edit `DEFAULT_VERSIONS` and the cadence table above.
+ Never add an image you do not have; never add 580/610 to a default.
+* **New per-version artifact** — add a step function in
+ `harness/port_driver_versions.py`, register it in `run_version`, and list the
+ path in `_expected_artifacts`.
+* **New device profile field** — add it to `scaffold_profile`, `PROFILE_HELP`
+ and `profile_missing_fields`, and surface it in the checklist.
+* **New architecture (GB10-class, aarch64)** — follow docs/07 P3 first; the
+ sweep handles the ISA/image shape and the matrix records the first-touch
+ deltas, but PMU/SOE and partition-model work belongs to the roadmap.
+* **New product behaviour** — out of scope: these tools produce evidence, they
+ never edit `open-iov-vfio`. Product changes need the docs/07 P4 escalation
+ and a physical reference.
+
+---
+
+## 6. Safety and limits
+
+* No downloads: discovery and `--extract-runfiles` only read files already on
+ disk; a driver runfile is never fetched.
+* Simulator-only: own QEMU processes on demo/re-hosted ELF or raw firmware;
+ never a guest, kernel module, mdev, existing gdbstub or physical GPU.
+* Bounded: QEMU probe/scenario/mutation budgets plus `--step-timeout`; the
+ mutation harness refuses non-demo ELFs unless explicitly allowed.
+* No support claims: the sweep records what changed; the device checklist
+ holds until physical validation; the matrix and RUNBOOK process own the
+ "Supported" label (hardware-software-matrix §1).
+* Fidelity limits are docs/06: L0/L1 observations only, L2/L3 (timing,
+ interrupts, device model) explicitly not claimed.
+
+## 7. Roadmap mapping
+
+| Item | Where this doc/tool contributes |
+|---|---|
+| **R6 — multi-version orchestration** | One sweep over 540→current with version_diff + matrix + mutation and a cross-version report |
+| **H4.1 — semantic diffing** | Per-version symbol/corpus/behaviour deltas and recorded sessions |
+| **H4.3 — mutation/fuzzing** | Seeded mutation smoke per version |
+| **docs/07 P1/P2** | The playbooks become dry-run plans and evidence bundles |
+| **docs/23 C5/C7** | Release pipeline extended by a sweep-scale orchestrator; 610 gate reused |
+| **H7 — agentic platform** | Machine-readable plans, summaries and bundles for monitored agent runs |
+
+<!-- change (2026-10-02): state the unadmitted-pilot status and link the
+future-eng-ops F2/F3 evaluation from the document that ships the scaffolds;
+scope: intro only; benefit: no reader mistakes the sweep for admitted release
+tooling. -->
diff --git a/debug/risc-v-gsp-rm-vm/docs/29-PACKAGING-AND-TRIALING.md b/debug/risc-v-gsp-rm-vm/docs/29-PACKAGING-AND-TRIALING.md
new file mode 100644
index 0000000..8be3704
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/docs/29-PACKAGING-AND-TRIALING.md
@@ -0,0 +1,246 @@
+# 29 — Packaging and trialing: sim + injected code as a deliverable
+
+> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status.
+> This is a development-only convenience layer over docs/12 (injection SDK),
+> docs/16 (native simulator) and docs/24 (device map). It makes no hardware,
+> timing or security claim; the real `.fwimage` path is honestly blocked on
+> H2.2 and says so in every result.
+
+## 1. Purpose
+
+green-eye can already load an injected service into the native simulator and
+invoke it (docs/12, docs/16 §1.1, `harness/value_demos/s06_inject_service.py`).
+That evidence lives in the tool tree: a reviewer needs the checkout, the built
+demo and SDK, and the exact command sequence. `harness/package_sim.py` turns
+"the s06 scenario" into a self-contained, hash-addressed package that a third
+party can run, inspect and compare:
+
+* one directory (or deterministic `.tar.gz`) containing the target, the service
+ blob, the scenario and a manifest of hashes;
+* one entry point (`run.sh`) that boots the target **natively**, registers the
+ injected service, invokes it directly and through the firmware dispatcher,
+ runs a hook/watch pass, and writes `results.json` + `results.md`;
+* a machine-readable manifest so a recipient can verify exactly which inputs
+ produced a result before trusting it.
+
+It exists to make a code addition trialable and reviewable, not to ship
+anything: the eventual target is a modified, **unsigned** GSP image for the
+simulator with tested injected code (§7).
+
+## 2. Exact usage
+
+All commands run from `open-iov/debug/risc-v-gsp-rm-vm`. Prerequisites:
+`make demo && make sdk` (or pass `--service`), Python 3.10+; no QEMU needed.
+
+```bash
+# 1) Demo trial (defaults: demo uproc + sdk/build/injected_demo.elf, slot 6)
+python3 harness/package_sim.py --out /tmp/greeneye-trial-demo
+
+# 2) Build, archive and immediately run the trial
+python3 harness/package_sim.py --out /tmp/greeneye-trial-demo --tar --run
+/tmp/greeneye-trial-demo/run.sh # or run it yourself
+
+# 3) Combined artifact: the service is pre-injected at a fixed offset
+python3 harness/package_sim.py --pack-image --out /tmp/greeneye-trial-packed
+python3 harness/package_sim.py --pack-image --tar --run --out /tmp/greeneye-trial-packed
+
+# 4) Real 570.144 fwimage: image + service + lenient device map (blocked, see §5)
+python3 harness/package_sim.py --target image \
+ --image /path/to/gsp-570.144.fwimage.bin \
+ --device-map dumps/570.144-device-map.json \
+ --out /tmp/greeneye-trial-image
+
+# 5) Custom scenario and slot
+python3 harness/package_sim.py --script my_scenario.py --slot 6 --out /tmp/my-trial
+
+# 6) Tests
+python3 -m unittest tests.test_package_sim -v # 6 tests, ~4 s
+```
+
+Measured on the reference host (2026-10-02, CPython 3.12): a demo build plus
+`run.sh` reports `trial: ok` in ~1 s wall; the test file (6 tests, including
+the real-image bounded probe) runs in ~4.1 s.
+
+Flags: `--target demo|image` (default demo), `--image`, `--service`,
+`--device-map`, `--script`, `--slot 0..7` (default 6), `--pack-image`,
+`--tar`, `--run`, `--run-timeout`. `--out` must be empty or absent; builds do
+not overwrite.
+
+## 3. Package layout and manifest schema
+
+```
+<out>/
+ README.md what this is, how to run, what to expect, safety
+ run.sh single entry; finds the tool root (GREENEYE_HOME) and runs scenario.py
+ manifest.json format/version + hashes + component list + run contract
+ scenario.py the pre-scripted scenario as run (builtin or --script)
+ payload/
+ <target>.elf|fwimage the target as supplied
+ injected_demo.elf the service blob
+ device_map.json only for image packages when no --device-map was given
+ combined.elf only with --pack-image
+ preinject.json only with --pack-image (placement config)
+ results/ produced by run.sh: results.json, results.md, session/
+```
+
+`manifest.json` (`greeneye-trial/1`):
+
+| Key | Contents |
+|---|---|
+| `format`, `greeneye_version`, `created`, `results_schema` | identity; `created` is the only timestamp |
+| `target` | `kind` (demo/image), `file`, `input` (repo-relative when possible), `sha256`, `entry`, `min_vaddr`, `max_vaddr`, `bias`, `runnable`, `limitations` |
+| `service` | `file`, `input`, `sha256`, `entry`, `entry_symbol` (`ge_demo_service_dispatch`), `slot` |
+| `device_map` | `file`, `input`, `sha256`, `lenient`, `learn` (null for a plain demo) |
+| `script` | `kind` (builtin/custom), `name`, `input`, `sha256` |
+| `preinject` | `file`, `runnable`, `reason`, `target_load_addr`, `target_entry`, `service_load_addr`, `service_offset`, `service_entry`, `entry_addr`, `slot`, `size`, `segments` (null without `--pack-image`) |
+| `scenario` | demo boot/settle seconds, image instruction budget, `memory_mb`, expected call values |
+| `run` | `command`, expected result paths, `exit_nonzero_on: "assertion failure"` |
+| `safety`, `determinism` | policy text carried into the package |
+| `components` | sorted `{path, role, bytes, sha256}` for every packaged file except the manifest itself |
+
+`results.json` (`greeneye-trial-results/1`): `status` (`ok`/`failed`/
+`blocked`), `success`, `backend`, `target`, `service` (load/entry addresses,
+`preinjected`), `calls` (remote vs firmware with expected/agree), `hooks`
+(`rpc_dispatch` hits and first entries), `watches` (`gsp_ctrl.result` hits),
+`control`, `handlers_slot`, `uart_excerpt`, `session`, `assertions`
+(`checks`, `failed`), `limitations`, plus `error` and the blocked fields
+(§5) where applicable. `results.md` renders the same.
+
+## 4. How a user trials it and reads results
+
+1. Receive `<package>.tar.gz` plus its sha256 out of band; verify
+ `sha256sum <package>.tar.gz`.
+2. Extract and run: `./run.sh` (add `--budget N` for image packages). The
+ script finds the green-eye tree by walking up, or via
+ `GREENEYE_HOME=/path/to/risc-v-gsp-rm-vm`. Exit codes: `0` pass (or the
+ documented blocked image status), `1` assertion failure, `3` tree not found.
+3. Read `results/results.md` first, then `results/results.json` for machines.
+ Anything other than `status: ok` on a demo package is a finding, not a
+ flake: `assertions.failed` names the check (`handlers_slot_registered`,
+ `remote_ping`, `routes_agree`, `dispatch_hook_hits`, `uart_banner`, ...).
+4. For deeper review, point the tools at `results/session/`:
+ `./greeneye.sh analyze <pkg>/results/session` and
+ `python3 -m greeneye compare baseline/results/session candidate/results/session`.
+5. Verify provenance: each `components[].sha256` must match the packaged file;
+ `target.sha256`/`service.sha256` identify the inputs.
+
+The demo package asserts: the service is registered in
+`gsp_ctrl.handlers[slot]`; direct and mailbox invocations agree with the SDK
+ABI (`cmd 1: a0 ^ 0xbeef`, `cmd 2: a0 + a1`); at least one `rpc_dispatch`
+hook hit and one `gsp_ctrl.result` watch hit; and the UART banner/self-test
+lines are present. A bisected or broken service fails these and exits 1.
+
+## 5. The `--pack-image` path and its honest limits
+
+For the demo, `--pack-image` writes `payload/combined.elf`: the demo's PT_LOAD
+segments plus the service's segments relocated to `target_base + 0x01000000`
+(the same address `greeneye.inject.load_service` uses by default), merged into
+one ELF64. `payload/preinject.json` records the placement, and the scenario
+boots the combined ELF, writes the entry into the handler slot and skips the
+separate blob load. Round-trip verified in
+`tests/test_package_sim.py::test_pack_image_round_trip`: call results are
+identical to the load/register path and `service.preinjected` is `true` in
+`results.json`. This is the trial starting with the injected code already part
+of the simulated system.
+
+For the real `.fwimage`, the package contains the image, the service blob and
+a lenient `greeneye-device-map/1` map (given or emitted empty with
+`learn: true`). **The native simulator cannot boot it yet**: the H2.2 device
+model is absent. The scenario therefore runs a bounded, deterministic lenient
+first-touch probe and records `status: blocked`,
+`blocked_on_device_model: true`, `instructions_executed`, the stop PC, the
+unmodelled MMIO list and (when learning) `results/learned_device_map.json`.
+Reference measurement: a 200 000-instruction budget (199 998 executed),
+stopped at `pc=0x84009070` (firmware `0x4009070`), exactly one unmodelled
+access at `0x84009014` reading `0x2000000000000a00`, matching docs/24 §5. No
+service is loaded, because no RM service registration point exists yet (M5) and the default load
+address would overwrite the image. Exit is 0 for the documented blocked
+status; a thrown error is recorded and exits 1.
+
+## 6. Sharing the results of a code addition
+
+1. Author builds the package with the service under review
+ (`service.sha256` is the code addition's identity) and runs it.
+2. Attach `manifest.json`, `results/results.json`, `results/results.md` and
+ the `results/session/` directory; pre-injection evidence is in the session
+ as `service_injected`, `call_enter`/`call_return`, `watch_hit` and `bp_hit`
+ events, so the claim is replayable.
+3. A baseline package with the previous service is diffed with
+ `greeneye compare`; the delta is the behavior change.
+4. A reviewer re-runs the received package on the tool tree, verifies hashes
+ and expects the same `status`/call results. Divergence is a finding against
+ the package or the tree, not a nuance.
+5. Nothing here is product evidence by itself: any conclusion that would
+ change `open-iov-vfio` still escalates to a physical capture (docs/06 §6,
+ docs/25 §2.1). The package makes that physical confirmation cheap, it does
+ not replace it.
+
+## 7. Eventual path: a modified GSP image for the simulator
+
+Once H2.2 can boot the real image (docs/16 M4) and the RM service registry
+exists (M5), the same pipeline packages a **modified, unsigned** image:
+
+1. take the original `.fwimage` (hash in `target.sha256`); never a signed
+ production image, never a copy that can be flashed;
+2. record the patch/injection recipe (`preinject`) and the verified device map
+ as versioned profile data keyed by `(device, firmware-family, profile)` per
+ docs/16 §7;
+3. run the package's scenario, which boots the image under the H2.2 device
+ model, registers the tested service with the simulated RM core, and asserts
+ its behavior end to end;
+4. attach the session as evidence and escalate any product-relevant finding to
+ hardware.
+
+Until M4/M5, image packages are deliberately non-runnable and labelled so; the
+`preinject.runnable: false` + `reason` fields and the blocked result keep the
+gap visible instead of implying a boot.
+
+## 8. Security and safety
+
+* The package executes only inside the in-process simulator: no hardware
+ access, no mdev, no guest, no kernel module, no network. `run.sh` invokes
+ Python in the tool tree found locally.
+* Injected code runs with the simulator's full privileges and may corrupt
+ simulated firmware state (docs/12 §9). Treat every package as untrusted
+ code: run only packages whose sha256 and `manifest.json` you have verified,
+ in a scratch copy of the tree, and restart clean after a crash.
+* Never use a package to inject into a guest, a shipped environment or real
+ hardware; never sign, flash or install a modified `.fwimage`.
+* `/tmp` outputs are scratch; packages and sessions contain firmware bytes and
+ are development artifacts, not for redistribution outside the project.
+
+## 9. Review/evaluation criteria (keep vs prune)
+
+Keep the feature only while it earns its maintenance. This is an **unadmitted
+pilot** under `eng-ops.md` §2: the evaluation recorded in
+[`future-eng-ops.md`](../../../docs/future-eng-ops.md) F4 is the admission
+decision. It is worth keeping if:
+
+* `python3 -m unittest tests.test_package_sim` stays green in CI in well under
+ a minute, including the bounded real-image probe;
+* two builds under a fixed `SOURCE_DATE_EPOCH` are byte-identical
+ (manifest and `.tar.gz`) and an intentionally broken service fails the
+ assertions and exits 1 (add that negative test if it is missing);
+* at least one code addition was actually reviewed from a package by someone
+ who did not author it, with hashes verified and results reproduced;
+* the tar carries enough provenance (`manifest.components`, target/service/
+ device-map/script hashes, `preinject` placement) for a reviewer to trust a
+ result without touching the author's checkout;
+* the image path converts into a real H2.2 boot when M4/M5 land, and its
+ `blocked` status is removed rather than quietly left in place.
+
+Prune or rework it if: packages are never shared or reviewed (the s06 demo is
+enough); the M5 native service registry subsumes `--pack-image`; the manifest
+format churns without consumers; the real-image path stays permanently blocked
+and adds only maintenance; or package results are being quoted as product or
+hardware evidence.
+
+Evidence for this document: `harness/package_sim.py`,
+`tests/test_package_sim.py` (6 tests), docs/12, docs/16 §1.1, docs/24 §4–5,
+docs/25 §5–6.
+
+<!-- change (2026-10-02): use the tree's stdlib unittest runner instead of an
+undeclared pytest dependency, and state the unadmitted-pilot status with the
+future-eng-ops F4 evaluation; scope: §2 command, §9 only; benefit: the test
+command runs with the same interpreter as CI and the keep/prune decision is
+linked. -->