feat(ibmi-analyst): bake Ghidra, PowerAS, and the recovery toolkit #363

Merged
binjovi-bot merged 4 commits from feat/ibmi-analyst into trunk 2026-09-14 14:08:51 +00:00
Owner

Add images/ibmi-analyst (VERSION 1): Ghidra 11.1.2 with the PowerAS processor extension and the pinned sean/rpg-demo recovery toolkit on the digest-pinned AlmaLinux 10 base. It is the fleet's only Java and Python carve-out; every gate in the Dockerfile is bash and jq.

Contents (all fetched at image build time)

  • Ghidra 11.1.2 zip from recovery/ghidra/tools.json: byte count 422385730 and sha256 219ec130… checked before unzip. macOS and Windows native directories removed; linux_x86_64 kept. The release zip has no linux_arm_64 natives (the second Build proved it), so the build stage compiles them with support/buildNatives and Gradle 8.14.3 (bd711022…, the -bin checksum from gradle.org/release-checksums; Ghidra 11.1.2's native build uses the legacy Gradle software model that Gradle 9 does not carry): decompiler, sleigh, lzfse, and both GNU demanglers land in <module>/os/linux_arm_64/, and only the build/ and .gradle/ directories the native build created are removed. Every binary is asserted.
  • PowerAS 0eca5b101115a16de8594e1c90f78ce001e716c0, built with Gradle 9.5.1 (bafc141b…, the -bin checksum from gradle.org/release-checksums, 2026-09-14) in a --platform=$BUILDPLATFORM stage with --offline; the extension zip is unzipped into Ghidra/Extensions/PowerAS and ppc_64_as_be.slaspec is compiled once by this Ghidra. Gradle does not reach the runtime.
  • sean/rpg-demo at cd07b7fe67438c9c3e58348e6586b5e937cadefa: recovery/*.py, recovery/*.json (the modules read application_contract.json and initial_stage.json next to themselves), recovery/TOOL.md, and recovery/ghidra/ (three exporters, tools.json, README.md). No fixtures, evidence, tests, harness, examples, or reference.
  • zelnet v0.10.17 via COPY --from=registry.sean.farm/build/ibmi-builder:v23@sha256:e22d9909… (the manifest-list digest, amd64 and arm64).
  • java-21-openjdk-headless plus java-21-openjdk-devel: support/analyzeHeadless launches with the jdk java type and Ghidra compiles .java scripts at run time, so a JRE is not sufficient. OpenJDK 21, not 17: AlmaLinux 10 AppStream ships java-21-openjdk and java-25-openjdk only; there is no java-17-openjdk in EL10 (the first Build failed on it). Ghidra 11.1.2 declares application.java.min=17 with no maximum, Gradle 9.5.1 runs on 21, and the PowerAS build targets sourceCompatibility 17. The rpg-demo tools.json records Java 17.0.19 from the macOS validation; the image gates prove the toolchain on 21.

Build-time gates

  1. java -version and javac -version report 21; jdk.compiler is present.
  2. tools.json pins (Ghidra URL, sha256, bytes; PowerAS repository and commit; Gradle 9.5.1; processor) agree with the Dockerfile pins.
  3. ibmi_recover.py --help exits 0; verify /nonexistent exits 2 with the toolkit's message.
  4. lock-tools runs offline and writes /opt/ibmi-analyst/tool-lock.json; jq asserts Ghidra 11.1.2, Java 21, Ghidra/Extensions/PowerAS/data/languages/ppc_64_as_be.sla, and bin/javac.
  5. The PowerAS synthetic processor test from recovery/ghidra/README.md: eight bytes 3860002a4e800020, exact marker POWERAS_PROCESSOR_SMOKE_PASSED, no SCRIPT ERROR, and ghidra_result.py accepts the log.
  6. End to end: the twelve-byte input from recovery/evidence/deterministic-tool-20260910 through VerifyPowerAS, ExportRecovery, and ExportInterpretation (this starts the native decompiler), then ibmi_recover.py ghidra and verify. The exported listing must be byte-identical to the evidence listing (761bd089…).
  7. The tool lock is taken again after both Ghidra runs and must be byte-identical: Ghidra does not write into /opt/ghidra or JAVA_HOME at run time.
  8. recover-object on a non-save-file fails closed with exit 2 and status: failed.

Notes

  • The AlmaLinux JDK links conf/, lib/security/, and lib/tzdb.dat outside its tree. The toolkit's tool_lock() refuses links that leave the tree, so the build replaces every link under the JDK with a copy of its target and asserts none remain.
  • scripts/check-pipelines-ci-no-tla.sh is scoped to images/pipelines-ci/Dockerfile only, so no exemption is needed. scripts/lint-no-runtime-fetch.sh inspects FROM lines only; it accepts the digest-pinned COPY --from=<image>.
  • No dnf update, per the beam-runtime precedent: the pinned base digest is the contract. Both dnf install steps pass --setopt=install_weak_deps=False: java-21-openjdk-devel requires the full java-21-openjdk, whose weak dependencies are a desktop stack (pipewire, xdg-desktop-portal, polkit, systemd-udev, fonts); the runtime asserts pipewire and xdg-desktop-portal are absent.

https://claude.ai/code/session_01HPdKaFBKQVs8hzfgXMrnwV

Add `images/ibmi-analyst` (VERSION 1): Ghidra 11.1.2 with the PowerAS processor extension and the pinned `sean/rpg-demo` recovery toolkit on the digest-pinned AlmaLinux 10 base. It is the fleet's only Java and Python carve-out; every gate in the Dockerfile is bash and jq. ## Contents (all fetched at image build time) - Ghidra 11.1.2 zip from `recovery/ghidra/tools.json`: byte count `422385730` and sha256 `219ec130…` checked before unzip. macOS and Windows native directories removed; `linux_x86_64` kept. **The release zip has no `linux_arm_64` natives** (the second Build proved it), so the build stage compiles them with `support/buildNatives` and Gradle 8.14.3 (`bd711022…`, the -bin checksum from gradle.org/release-checksums; Ghidra 11.1.2's native build uses the legacy Gradle software model that Gradle 9 does not carry): decompiler, sleigh, lzfse, and both GNU demanglers land in `<module>/os/linux_arm_64/`, and only the `build/` and `.gradle/` directories the native build created are removed. Every binary is asserted. - PowerAS `0eca5b101115a16de8594e1c90f78ce001e716c0`, built with Gradle 9.5.1 (`bafc141b…`, the -bin checksum from gradle.org/release-checksums, 2026-09-14) in a `--platform=$BUILDPLATFORM` stage with `--offline`; the extension zip is unzipped into `Ghidra/Extensions/PowerAS` and `ppc_64_as_be.slaspec` is compiled once by this Ghidra. Gradle does not reach the runtime. - `sean/rpg-demo` at `cd07b7fe67438c9c3e58348e6586b5e937cadefa`: `recovery/*.py`, `recovery/*.json` (the modules read `application_contract.json` and `initial_stage.json` next to themselves), `recovery/TOOL.md`, and `recovery/ghidra/` (three exporters, `tools.json`, `README.md`). No fixtures, evidence, tests, harness, examples, or reference. - `zelnet` v0.10.17 via `COPY --from=registry.sean.farm/build/ibmi-builder:v23@sha256:e22d9909…` (the manifest-list digest, amd64 and arm64). - `java-21-openjdk-headless` plus `java-21-openjdk-devel`: `support/analyzeHeadless` launches with the `jdk` java type and Ghidra compiles `.java` scripts at run time, so a JRE is not sufficient. **OpenJDK 21, not 17:** AlmaLinux 10 AppStream ships `java-21-openjdk` and `java-25-openjdk` only; there is no `java-17-openjdk` in EL10 (the first Build failed on it). Ghidra 11.1.2 declares `application.java.min=17` with no maximum, Gradle 9.5.1 runs on 21, and the PowerAS build targets `sourceCompatibility` 17. The rpg-demo `tools.json` records Java 17.0.19 from the macOS validation; the image gates prove the toolchain on 21. ## Build-time gates 1. `java -version` and `javac -version` report 21; `jdk.compiler` is present. 2. `tools.json` pins (Ghidra URL, sha256, bytes; PowerAS repository and commit; Gradle 9.5.1; processor) agree with the Dockerfile pins. 3. `ibmi_recover.py --help` exits 0; `verify /nonexistent` exits 2 with the toolkit's message. 4. `lock-tools` runs offline and writes `/opt/ibmi-analyst/tool-lock.json`; jq asserts Ghidra 11.1.2, Java 21, `Ghidra/Extensions/PowerAS/data/languages/ppc_64_as_be.sla`, and `bin/javac`. 5. The PowerAS synthetic processor test from `recovery/ghidra/README.md`: eight bytes `3860002a4e800020`, exact marker `POWERAS_PROCESSOR_SMOKE_PASSED`, no `SCRIPT ERROR`, and `ghidra_result.py` accepts the log. 6. End to end: the twelve-byte input from `recovery/evidence/deterministic-tool-20260910` through `VerifyPowerAS`, `ExportRecovery`, and `ExportInterpretation` (this starts the native decompiler), then `ibmi_recover.py ghidra` and `verify`. The exported listing must be byte-identical to the evidence listing (`761bd089…`). 7. The tool lock is taken again after both Ghidra runs and must be byte-identical: Ghidra does not write into `/opt/ghidra` or `JAVA_HOME` at run time. 8. `recover-object` on a non-save-file fails closed with exit 2 and `status: failed`. ## Notes - The AlmaLinux JDK links `conf/`, `lib/security/`, and `lib/tzdb.dat` outside its tree. The toolkit's `tool_lock()` refuses links that leave the tree, so the build replaces every link under the JDK with a copy of its target and asserts none remain. - `scripts/check-pipelines-ci-no-tla.sh` is scoped to `images/pipelines-ci/Dockerfile` only, so no exemption is needed. `scripts/lint-no-runtime-fetch.sh` inspects `FROM` lines only; it accepts the digest-pinned `COPY --from=<image>`. - No `dnf update`, per the beam-runtime precedent: the pinned base digest is the contract. Both `dnf install` steps pass `--setopt=install_weak_deps=False`: `java-21-openjdk-devel` requires the full `java-21-openjdk`, whose weak dependencies are a desktop stack (pipewire, xdg-desktop-portal, polkit, systemd-udev, fonts); the runtime asserts pipewire and xdg-desktop-portal are absent. https://claude.ai/code/session_01HPdKaFBKQVs8hzfgXMrnwV
feat(ibmi-analyst): bake Ghidra, PowerAS, and the recovery toolkit
Some checks failed
binjovi/ci Binjovi failed the frozen plan
ee5afe3e3f
Add the ibmi-analyst image. It runs Ghidra 11.1.2 with the PowerAS
processor extension over IBM i program objects for the recovery pipeline.
It is the fleet's only Java and Python carve-out: the pinned sean/rpg-demo
toolkit is an upstream tool here, and every gate is bash and jq.

Contents, all fetched at image build time:
- Ghidra 11.1.2 from the GitHub release, checked by byte count and sha256.
  The macOS and Windows native directories go; both Linux ones stay.
- PowerAS commit 0eca5b1, built with Gradle 9.5.1 (sha256 from
  gradle.org/release-checksums) in a build-platform stage, unzipped into
  Ghidra/Extensions, with the PowerPC:BE:64:AS sleigh compiled once.
- sean/rpg-demo commit cd07b7f: recovery/*.py, the JSON contracts,
  TOOL.md, and recovery/ghidra (exporters, tools.json, README).
- zelnet v0.10.17, copied by digest from ibmi-builder:v23.
- java-17-openjdk-devel: analyzeHeadless launches with the jdk java type
  and compiles Java scripts at run time, so a JRE is not sufficient.

Gates: Java 17 with jdk.compiler; the tools.json pins agree with the
Dockerfile; the toolkit help and a failing verify; the PowerAS synthetic
processor test with the exact marker; the twelve-byte evidence input
through all three exporters and the bundle builder, byte-identical to the
rpg-demo evidence listing; the tool lock written before and unchanged after
both Ghidra runs; and recover-object failing closed on a non-save-file.

The AlmaLinux JDK links conf/, lib/security/, and lib/tzdb.dat outside its
tree. The toolkit's tool lock refuses such links, so the build replaces
them with copies.

Claude-Session: https://claude.ai/code/session_01HPdKaFBKQVs8hzfgXMrnwV
fix(ibmi-analyst): use OpenJDK 21, the JDK that AlmaLinux 10 ships
Some checks failed
binjovi/ci Binjovi failed the frozen plan
7668d11925
AlmaLinux 10 AppStream has java-21-openjdk and java-25-openjdk only. The
first Build failed with "No match for argument: java-17-openjdk-devel".
Ghidra 11.1.2 declares application.java.min=17 with no maximum, Gradle
9.5.1 runs on JDK 21, and the PowerAS build targets sourceCompatibility
17, so install java-21-openjdk-headless and java-21-openjdk-devel and
assert 21 in the gates. The tools.json check no longer asserts the macOS
Java 17.0.19 record; it asserts the field is present.

Claude-Session: https://claude.ai/code/session_01HPdKaFBKQVs8hzfgXMrnwV
fix(ibmi-analyst): build the Linux ARM64 Ghidra natives
Some checks failed
binjovi/ci Binjovi failed the frozen plan
e8aa14b100
The Ghidra 11.1.2 release zip ships native binaries for linux_x86_64,
mac_arm_64, mac_x86_64, and win_x86_64 only. The second Build failed on
the ARM64 decompiler assertion. Build the Linux ARM64 natives in the
build stage with support/buildNatives: the decompiler and sleigh, lzfse,
and the two GNU demanglers. Gradle 8.14.3 (sha256 from
gradle.org/release-checksums) drives that build, because Ghidra's native
build uses the legacy software model that Gradle 9 does not carry. Copy
the executables from <module>/build/os/linux_arm_64/ into
<module>/os/linux_arm_64/, remove only the build/ and .gradle/
directories the native build created, and assert every binary. The unzip
step now asserts that the zip has no linux_arm_64 directory, so a future
zip that ships one forces a deliberate review.

Claude-Session: https://claude.ai/code/session_01HPdKaFBKQVs8hzfgXMrnwV
fix(ibmi-analyst): keep the shell out of the deleted PowerAS tree
All checks were successful
binjovi/ci Binjovi completed the frozen plan
b857f4146c
The third Build built the ARM64 natives and the PowerAS extension, then
failed in the sleigh compile: the RUN had changed into /tmp/PowerAS and
removed it, so the Ghidra launcher started Java from a deleted working
directory, could not run LaunchSupport, and asked for a JDK path with no
TTY. Run Gradle with --project-dir instead of cd, so the working
directory stays valid.

Both dnf install steps now pass --setopt=install_weak_deps=False.
java-21-openjdk-devel requires the full java-21-openjdk, whose weak
dependencies are a desktop stack (pipewire, xdg-desktop-portal, polkit,
systemd-udev, fonts). The runtime asserts that pipewire and
xdg-desktop-portal are absent.

Claude-Session: https://claude.ai/code/session_01HPdKaFBKQVs8hzfgXMrnwV
sean force-pushed feat/ibmi-analyst from b857f4146c
All checks were successful
binjovi/ci Binjovi completed the frozen plan
to c9d37581b6
All checks were successful
binjovi/ci Binjovi completed the frozen plan
2026-09-14 14:07:55 +00:00
Compare
binjovi-bot deleted branch feat/ibmi-analyst 2026-09-14 14:08:52 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sean/builder-images!363
No description provided.