Skip to content

MacBook Neo developer builds and preview catalog - #48

Draft
scottjones wants to merge 11 commits into
mainfrom
mac/neo-j700
Draft

scottjones wants to merge 11 commits into
mainfrom
mac/neo-j700

Conversation

@scottjones

Copy link
Copy Markdown
Collaborator

Adds MacBook Neo (apple,j700) support to developer builds only, plus the catalog tooling for a developer preview that installs on every M1, M2 and M3 Mac and the Neo. Public builds and channel catalogs still refuse the Neo.

What's in it

  • Engine .29 and .30:
    • installs on macOS 26 firmware, reading the restore bundle and firmware from the running macOS when the recovery image is AEA-encrypted;
    • on a j700ap, replaces Asahi's Stage 1 with Aurora's J700 Stage 1 from the image's esp/aurora/stage1-j700.bin;
    • collects the Neo's C1FE trackpad firmware, its MT7932 Wi-Fi and Bluetooth inputs (.29) and its Touch ID calibration (.30) from macOS.
  • Removal recognises a Neo install's 6 GB stub and its J700 Stage 1.
  • Developer build (OMARCHY_DEVELOPER_BUILD=1): its own workspace, no channel menu, and only a sealed catalog, so a catalog that admits Macs the public ones don't can never reach it over the network.
  • Catalog developer mode (make-unsigned-catalog.py --developer): developer_models groups give boards from supported-models.json's new developer section their own payload and metadata. The main group still covers exactly every M1, M2 and M3 Mac, refused boards stay refused, and check-catalog still rejects apple,j700.

Preview

The downloads are on the pre-release neo-preview-dev4-20261007:

  • an M1–M3 image with Aurora 12.0 and m1n1-aurora aurora12;
  • a Neo image (dev4) with the J700 boot chain and the same 12.0 kernel;
  • engine .30.

The developer app is to be built from this branch's head by the holder of the Developer ID, notarized, and attached there.

Before merging

Validation

  • ./test/all passes on macOS with Bash 5.3 and Python 3.13, including 6 new catalog tests (29 in test_make_unsigned_catalog.py).
  • The macOS Swift build and tests for the developer build profile need a macOS run on the final commit; the catalog change touches no Swift.

🤖 Generated with Claude Code

scottjones and others added 11 commits October 4, 2026 20:09
The MacBook Neo (J700, T8140) can only use macOS 26 boot firmware, which
asahi-installer v0.9.2 was not written for. The engine now:

- knows T8140, j700ap and a macOS 26.6 (25G72) OS-firmware entry;
- names the restore bundle "./Restore" when macOS 26's bootcaches.plist
  no longer does;
- replaces the restore image's encrypted (AEA) recoveryOS and ExclaveOS
  images with the running macOS's decrypted copies of the same build,
  which the stub's recoveryOS kernel can mount, and collects firmware the
  same way;
- gives a stub on macOS 26 firmware 6 GB instead of 2.5 GB, room for the
  decrypted recoveryOS and two sets of personalized boot objects.

The Python overlay repack now pins the patch the omarchy.14 base was built
with (patches/base), so a base still has to reproduce exactly what it
shipped while the new upstream file takes the current patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OMARCHY_DEVELOPER_BUILD=1 marks the app with OmarchyDeveloperBuild: its
own workspace, no channel menu, and only a sealed catalog in the bundle,
so a developer catalog that admits Macs the public ones do not can never
reach it over the network. Name the MacBook Neo, and stop the bundling step
from overwriting the inspection engine when the catalog's engine is the
same file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removal took a stub outside 2.3-2.7 GB for another macOS installation and
refused it. A stub on macOS 26 firmware is 5,999,951,872 bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Public builds keep unqualified boards such as apple,j614s fail-closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
iBoot panics on stock asahi m1n1 as the J700's custom boot object. On a
j700ap the engine now replaces asahi's Stage 1 with Aurora's J700 Stage 1
from the image's esp/aurora/stage1-j700.bin, filled in place (a port of
aurora-silicon/m1n1 tools/fill_stage1_config.py) to chainload the new ESP's
m1n1/boot.bin. A Neo image without that file is refused; other Macs keep
asahi's Stage 1.

Firmware collection also counts C1FE multitouch keys as trackpads: the
J700_Multitouch.im4p keys its trackpad C1FE0,0, which asahi_firmware
skipped, leaving no apple/tpmtfw-j700.bin and a dead trackpad.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a j700ap the engine now adds the MT7932's Wi-Fi and Bluetooth inputs
to the vendor firmware package: the IZUBA patch, RAM and power-table
files, the factory OCA2, WCAL and BCAL calibrations from BWC2, the J7CF
records from IZUBA_wifi.cfg, the Bluetooth firmware and its .ptx, and
the Bluetooth address from /chosen. Each input is validated. The radios
are experimental, so a failed collection is logged and the install goes
on without them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
installer-v0.9.2-omarchy.29.tar.gz, 17,851,067 bytes, SHA-256
14323c78..., reproduced twice with macOS /usr/bin/python3 3.9.6 from a
fresh v0.9.2 checkout that also reproduces .28. Compared with .28 it adds
omarchy_mt7932.py and changes main.py, omarchy_asahi.py and version.tag.
The bundled inspection pin, the packager and both release templates
select it, so the release scripts publish the engine the catalog names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removal links a startup container to its EFI partition through the
ESP its m1n1 boot object names. Aurora's J700 Stage 1, which the Neo
boots and engine .29 installs, keeps that in a versioned, CRC-checked
config block and ends at STACKBOT, so removal found no ESP and refused
the Neo's own installation. Exactly one valid version-1 block now names
the ESP; asahi's m1n1 variables remain the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Neo keeps its Mesa calibration in the iBoot System Container as one
standalone signed IMG4 of type FSC2, which aurora-touchid's extractor
did not find. On a j700ap the engine now reads the raw container
read-only, takes the one FSC2 image with a CALB payload and an IM4M
manifest, and adds it to the vendor firmware as apple/mesacal-j700.bin,
the name the Neo's device tree gives the driver. Like the radios, a
failure is logged and the install goes on.

installer-v0.9.2-omarchy.30.tar.gz, 17,852,406 bytes, SHA-256
2d5a14c3..., reproduced twice with macOS /usr/bin/python3 3.9.6; it adds
omarchy_mesa.py and changes omarchy_asahi.py and version.tag. The
bundled pin, packager and both release templates select it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make-unsigned-catalog.py --developer accepts developer_models: groups of
boards from supported-models.json's new developer section, each with its own
payload (split or whole) and metadata, alongside the main group, which still
covers exactly every M1, M2 and M3 Mac. Refused boards stay refused, a group
may not repeat a Mac or a file name, and check-catalog still rejects
apple,j700, so a developer catalog can never reach a channel. Each group's
digests are computed once instead of once per Mac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mrobs11

mrobs11 commented Oct 9, 2026

Copy link
Copy Markdown

M3 MacBook Air 13" (J613): dev4 preview installed and booted

I installed the developer preview from neo-preview-dev4-20261007 on a 13-inch M3 MacBook Air, and it booted into Omarchy.

  • Mac: MacBook Air 13" M3, Mac15,12 / J613, 24 GB, 2 TB
  • macOS: 27.0 (26A428), FileVault off
  • Installer: Omarchy-Installer-Developer-2.1.0.zip from this pre-release
  • Catalog: developer catalog sequence 1791382094, payload digest sha256:e4588ecc…6a61
  • Image and engine: developer-preview-20261007-aurora12 with engine v0.9.2-omarchy.30. The app staged the M1–M3 installer_data.json, not installer_data-m3air.json.
  • Result: the install and the Recovery step completed, and the Mac boots into Omarchy. The macOS 27 "system firmware is too old" failure from System Firmware too old when installing omarchy on M3 Pro with MacOS 27 omarchy-mac#440 didn't happen here, although this staged installer_data.json lists supported_fw only up to 26.6.
  • Disk layout after install: EFI 524 MB, Boot 2.1 GB, Root 566.7 GB, stub APFS container 2.5 GB

I haven't checked individual hardware yet (Wi-Fi, Bluetooth, audio, backlight and brightness keys, sleep, USB-C, external display, camera). I'll add those results here.

@garymeyerca

Copy link
Copy Markdown

Follow-up: testing dev5 on the J613 hit a recovery-image prerequisite after disk mutation. Could you advise on a supported recovery path and check this requirement before resizing?

  • Hardware: 13-inch M3 MacBook Air, J613 / Mac15,12, 8 GB RAM, 251 GB internal disk.
  • Host: macOS 27.0.1, build 26A434.
  • Candidate: source 43ddbf274474419b189f51f1dd46c9f9f8d90e55, engine v0.9.2-omarchy.32 (SHA-256 12ba2f04ebdf01854cc791d89d5e577763d97453a88cc521f2092b42d30ea82c), dev5 sealed catalog. The app was locally built with ad-hoc signing using the installed macOS 26.5 SDK; its app/helper trust was pinned to exact cdhashes. Engine, metadata, payload and signed catalog were not modified. This is not a published/notarized dev5 app.
  • Earlier space checks were resolved before this attempt. Installation approved about 50.6 GB.

The UI reached:

Started preparing disk space
Disk space reserved for Omarchy ✓ · verification 1cbdb168…f9259629
Started writing boot files

Then failed:

omarchy_asahi.AsahiAdapterError: the 25G83 recovery image is encrypted; run the installer from macOS 25G83, not 26A434, to use its decrypted copy

The journal records apfs-target-prepared, then stub_and_esp_started, with no stub-and-esp-installed checkpoint. The observed layout afterward is macOS APFS resized to about 194.6 GB, a partial 6 GB Omarchy APFS firmware container, and about 44.6 GB unallocated. EFI/Linux partitions have not been created. The original macOS remains running. No retry or cleanup has been attempted.

Read-only investigation found that ipsw fw aea can decrypt a copy of this recovery image using Apple's public FCS key; hdiutil verify passed and its mounted SystemVersion.plist reports 25G83. The copy was unmounted afterward. Nothing was replaced in the partial stub. Exclave images, signed root hashes and a supported continuation procedure still need validation; this observation alone is not a proposed safe resume.

Could dev5 preflight reject the incompatible host build before prepare_target, or support verified decryption of the exact restore images? What is the supported recovery procedure for this interrupted stub_and_esp stage? We have not cleared the journal or bypassed AmbiguousMutationState.

Drafted with an AI assistant from the local execution journal and disk inspection.

@scottjones

Copy link
Copy Markdown
Collaborator Author

Drafted with an AI assistant (Claude Code); I reviewed it before posting.

@mrobs11 thanks, that's the first M3 Air install of the preview by someone other than us. Both things you noticed are expected for dev4:

  • installer_data.json and engine .30: the notarized dev4 app is signed with catalog 1791382094, which predates the M3 Air group. installer_data-m3air.json and engine .31 on that release page are left over from a later test that never shipped in an app, so the app ignores them.
  • macOS 27: supported_fw lists the OS firmware the installer puts in the boot stub, not the macOS version. With that catalog an Air gets 14.8.3, and your system firmware (27.0) can boot it. To confirm, in Omarchy: tr -d '\0' < /proc/device-tree/chosen/asahi,os-fw-version; echo

neo-preview-dev5-20261011 (Aurora 11.14) gives the M3 Airs their own catalog entry with 26.6.2 OS firmware, and a notarized app for it should follow soon; we're setting up the Omacom Apple Developer account. Please don't try dev5 on an M3 Air running macOS 27 yet, though: as @garymeyerca found above, it stops after resizing the disk because the 26.6.2 recovery image is encrypted. We're looking into that now.

Touch ID is also starting to work on the M3 Air: on our J613 a finger enrols and matches, and the enrolment survives a reboot. It isn't in the preview yet. The kernel side is in omacom/linux-aurora#82.

Hardware results would be very welcome when you have them (Wi-Fi, Bluetooth, audio, brightness, sleep, USB-C, external display, camera), along with the output of uname -r.

@scottjones

Copy link
Copy Markdown
Collaborator Author

Drafted with an AI assistant (Claude Code); I reviewed it before posting.

@garymeyerca thanks for the careful report, and for stopping without clearing anything.

Cause. dev5 gives the M3 Airs only the 26.6.2 (25G83) OS firmware. macOS 26 restore images ship the recoveryOS and Exclave images AEA-encrypted, and the stub's recoveryOS can't mount them. Engine .32 doesn't decrypt them. It copies the decrypted images the running macOS keeps in its own Recovery and Preboot volumes, so it only works when the host is running exactly 25G83 (_use_decrypted_images in Engine/overlay/src/omarchy_asahi.py). Our J613 was installed from 26.6.2 and updated to 27 afterwards. On 26A434 there is no supported way to finish a dev5 install on an M3 Air today.

The bug is ours. That build check runs in install_stub_and_esp, after the resize and the stub container. preflight already loads the chosen IPSW, so the same check belongs there, before prepare_target touches the disk. That's going into the next engine. Supporting verified decryption of the exact restore images is a bigger change, with the open questions you listed (Exclave images and the signed root hashes), so it isn't in that fix.

Recovering this Mac. Nothing was blessed or added to the boot policy, so the partial stub is inert, and macOS is unaffected. The app's Remove can't help yet: it only recognises a whole installation (stub, EFI, Linux), and it would refuse this layout. From macOS:

  1. Confirm the layout and identifiers:
    diskutil list
    diskutil apfs list
    
    You should see disk0s2 (macOS, about 194.6 GB), then the 6 GB APFS partition with the partial stub, then the free space, then the Recovery partition. In the commands below, diskN is the stub's synthesized container disk and disk0sM is its physical partition. If the layout isn't exactly that, stop and post the output here.
  2. Delete the partial stub container and turn its partition into free space. This is what Remove does to a stub:
    sudo diskutil apfs deleteContainer diskN
    sudo diskutil eraseVolume free none disk0sM
    
  3. Give the space back to macOS:
    sudo diskutil apfs resizeContainer disk0s2 0
    

Leave the app's execution journal alone. If the app reports the interrupted install afterwards, post its message here.

Two ways forward on that Mac for now: wait for the fixed engine, or use the notarized dev4 app from neo-preview-dev4-20261007. Its catalog gives the Air 14.8.3 OS firmware, which @mrobs11 installed from macOS 27 and which boots (comment above). Note that dev4 is the older Aurora 12.0 image, and the M3 GPU path we test uses the 25G83 firmware.

@garymeyerca

Copy link
Copy Markdown

Thanks @scottjones, appreciate the explanation and recovery steps. This J613 / Mac15,12 is available for whatever testing you need, including candidate engines, recovery fixes and hardware checks. Let me know what would be most useful and what state you want the machine in before testing.

The partial installation and execution journal are still intact. I haven’t retried or cleaned anything up.

@mrobs11

mrobs11 commented Oct 12, 2026

Copy link
Copy Markdown

Drafted with an AI assistant; I reviewed the details before posting.

@scottjones thanks for the explanation. I checked the firmware reading and gathered hardware observations on my 13-inch M3 MacBook Air (J613 / Mac15,12).

Firmware and current system

$ tr -d '\0' < /proc/device-tree/chosen/asahi,os-fw-version; echo
14.7
$ tr -d '\0' < /proc/device-tree/chosen/asahi,system-fw-version; echo
27.0
$ uname -r
7.1.12-2-20261009.8-sep-ARCH

The OS firmware reading is 14.7, rather than the 14.8.3 you expected. My original installation used the notarized app from neo-preview-dev4-20261007, catalog 1791382094 and payload sha256:e4588ecc…6a61. Is 14.7 expected for that installation?

The current m1n1 versions are stage1 v1.6.1-dirty and stage2 v1.6.1-omarchy.aurora15. These and the kernel version describe the machine now; I haven’t established that the bootloader and kernel are unchanged from the original dev4 install.

Hardware observations

Item Observation
Wi-Fi BCM4388 / wlan0 is connected through NetworkManager and has an IPv4 address. Wired Ethernet is also connected, so I haven’t isolated a Wi-Fi-only connectivity test.
Bluetooth Controller detected and powered on. Pairing and audio/data transfer not tested.
Audio ALSA AppleJ613 devices and the J613 PipeWire microphone/convolver filters are present. Audible playback and recording not tested.
Display brightness /sys/class/backlight/ is empty; no panel backlight device is exposed. I haven’t separately tested the brightness keys.
Keyboard backlight kbd_backlight is exposed with a 0–255 range. Adjustment not tested.
Sleep / wake The journal records PM: suspend entry (s2idle) followed by PM: suspend exit on Oct 11. I haven’t qualified individual devices after wake.
USB-C data A USB Ethernet adapter (enu1u2) is connected. Both ports and other USB-C functions haven’t been tested individually.
External display Not tested. Hyprland currently sees only the internal panel at 2560x1600@60.
Camera Not working in Zoom’s web app or Google Meet. The green camera indicator turns on, but no video appears. Direct V4L2 capture also times out; details below.
Battery Reports 98% and Charging.
Graphics The internal desktop/display works and asahi GPU probe messages are present. Hardware-accelerated rendering hasn’t been separately verified.

Camera detail

The FaceTime HD camera enumerates at /dev/video1 and advertises NV12 at up to 1920×1080/30 fps. In a direct capture test, buffer setup and VIDIOC_STREAMON succeeded, but v4l2-ctl --stream-mmap --stream-count=1 --stream-poll reported select timeout. A polling test at 1280×720 also timed out. Although v4l2-ctl exited with status 0, I haven’t verified a successfully captured frame.

At the time of the CLI test, no process was holding /dev/video1 or /dev/media1, and PipeWire’s camera node was suspended. My user has read/write access to /dev/video1. The kernel log contains ISP request/opcode timeouts and repeated messages:

apple-isp 2a4000000.isp: [isp_stop_command_processor] never received magic number from firmware
apple-isp 2a4000000.isp: firmware did not suspend, stopping it

The green indicator turning on is what I observed in the web apps; I didn’t verify its state during the CLI test. Because capture also times out without a browser or an apparent competing device holder, this appears broader than a Zoom/Meet configuration issue. I don’t yet know the exact cause.

Please let me know which additional tests would be most useful, particularly for the camera and the 14.7 firmware reading.

@satchlj

satchlj commented Oct 12, 2026

Copy link
Copy Markdown

Then failed:

omarchy_asahi.AsahiAdapterError: the 25G83 recovery image is encrypted; run the installer from macOS 25G83, not 26A434, to use its decrypted copy

Claude on @satchlj's behalf:

The "25G83 recovery image is encrypted; run the installer from macOS 25G83" failure on macOS 27 hosts can be fixed without the host copy. Each AEA in the 25G83 IPSW carries Apple's public key URL (wkms-public.apple.com/fcs-keys/…, no auth), with the archive key HPKE-sealed to it. Unwrapping it is a few lines of CryptoKit or a small stdlib-only Python module, and /usr/bin/aea decrypt -key-value does the rest. On a macOS 27.2 Mac we decrypted 022-22048-093.dmg.aea (BaseSystem) and 094-96734-093.dmg.aea (Neo ExclaveOS) from the IPSW, and both SHA-384s match the BuildManifest digests exactly; the recovery image mounts as sealed 25G83. The stub already downloads those files, so the only new fetch is a 241-byte key. Suggested change: in _use_decrypted_images, decrypt each AEA file in place (via the host temp dir) and require SHA-384 == the identity's manifest digest, keeping the host-copy path as fallback. That also lets collect_firmware read the 25G83 recovery's firmware instead of the host's. I have a small stdlib module and a patch sketch if useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants