From 599481d4c1a9874f3717a767938caa1437c2bf99 Mon Sep 17 00:00:00 2001 From: Henrik Klee Date: Wed, 28 Oct 2026 14:22:13 +0800 Subject: [PATCH 1/4] Drop the J71b platform check and charge lease The torvalds-based stack has no bring-up gates to verify and no userspace charging lease. Charging moves into a kernel power_supply driver. Remove j71b-platform-ready and j71b-charge, the login console's dependency on them, and their entries in the image policy, root builder, audit and tests. Co-Authored-By: Claude Opus 5.5 --- image/j71b/image_policy.py | 6 ++---- release/j71b/audit.py | 2 +- release/j71b/public_root.py | 4 ++-- .../20-j71b-platform.conf | 3 --- .../j71b-charge.service | 1 - .../lib/systemd/system/j71b-charge.service | 20 ------------------- .../system/j71b-platform-ready.service | 11 ---------- tests/test_device_install.py | 2 -- tests/test_extraction.py | 2 +- 9 files changed, 6 insertions(+), 45 deletions(-) delete mode 100644 system/j71b/overlay/etc/systemd/system/getty@tty1.service.d/20-j71b-platform.conf delete mode 120000 system/j71b/overlay/etc/systemd/system/multi-user.target.wants/j71b-charge.service delete mode 100644 system/j71b/overlay/usr/lib/systemd/system/j71b-charge.service delete mode 100644 system/j71b/overlay/usr/lib/systemd/system/j71b-platform-ready.service diff --git a/image/j71b/image_policy.py b/image/j71b/image_policy.py index d36e1c3..eb6e5b0 100644 --- a/image/j71b/image_policy.py +++ b/image/j71b/image_policy.py @@ -36,8 +36,7 @@ def ready(*, ssh_revoked=False): def validate_root(root, release): - for helper in ('usr/lib/systemd/systemd', 'usr/libexec/j71b-check-platform', - 'usr/libexec/j71b-charge-lease', 'usr/libexec/omarchy-diagnostics'): + for helper in ('usr/lib/systemd/systemd', 'usr/libexec/omarchy-diagnostics'): if not os.access(root / helper, os.X_OK): raise ValueError('Missing executable: ' + helper) root_release = json.loads((root / 'etc/orchard/release.json').read_text()) @@ -46,8 +45,7 @@ def validate_root(root, release): def validate_handoff(root): - for helper in ('usr/lib/systemd/systemd', 'usr/libexec/j71b-check-platform', - 'usr/libexec/j71b-charge-lease', 'usr/libexec/omarchy-diagnostics'): + for helper in ('usr/lib/systemd/systemd', 'usr/libexec/omarchy-diagnostics'): if not os.access(root / helper, os.X_OK): raise ValueError('Handoff executable missing: ' + helper) diff --git a/release/j71b/audit.py b/release/j71b/audit.py index 09cb88f..5821ea3 100644 --- a/release/j71b/audit.py +++ b/release/j71b/audit.py @@ -141,7 +141,7 @@ def audit(manifest): verify_labwc(labwc, hashes, modes, owners, groups) if 'home/tablet/.local/share/omarchy-ipad/shell/bluetooth-pairing/shell.qml' not in desktop: raise ValueError('Pairing UI is absent from desktop integrity manifest') - for name in ('j71b-check-platform', 'j71b-charge-lease', 'omarchy-diagnostics', 'omarchy-desktop-ready', 'omarchy-wtype', 'omarchy-screen-toggle', 'omarchy-bluetooth-pair'): + for name in ('omarchy-diagnostics', 'omarchy-desktop-ready', 'omarchy-wtype', 'omarchy-screen-toggle', 'omarchy-bluetooth-pair'): if modes.get('usr/libexec/' + name) != 0o755: raise ValueError('Required helper is not executable: ' + name) if links.get('lib') != (True, 'usr/lib') or owners.get('home/tablet') != 1000 or sizes.get('etc/machine-id') != 0: diff --git a/release/j71b/public_root.py b/release/j71b/public_root.py index 1204fc6..7427cea 100644 --- a/release/j71b/public_root.py +++ b/release/j71b/public_root.py @@ -221,7 +221,7 @@ def add_parents(name): member = tarfile.TarInfo(name) member.mode, member.size = mode, len(data) add(member, data) - for unit in ('j71b-charge.service', 'omarchy-diagnostics.service'): + for unit in ('omarchy-diagnostics.service',): member = tarfile.TarInfo('etc/systemd/system/multi-user.target.wants/' + unit) member.type, member.linkname, member.mode = tarfile.SYMTYPE, '/usr/lib/systemd/system/' + unit, 0o777 if member.name not in seen: @@ -231,7 +231,7 @@ def add_parents(name): if compressor.wait(): raise ValueError('Public root compression failed') required = {'lib', 'usr/lib/systemd/systemd', 'etc/group', 'etc/gshadow', qml_path, - 'arch-ipad-ui-manifest.json', 'usr/libexec/j71b-check-platform', 'usr/libexec/omarchy-diagnostics'} + 'arch-ipad-ui-manifest.json', 'usr/libexec/omarchy-diagnostics'} if not required <= seen or not privileges.keys() <= seen or not replacements.keys() <= seen: raise ValueError('Incomplete public root or privileged package metadata') receipt = {'schema': 1, 'kind': 'rootfs', 'profile': 'j71b', 'release': release, diff --git a/system/j71b/overlay/etc/systemd/system/getty@tty1.service.d/20-j71b-platform.conf b/system/j71b/overlay/etc/systemd/system/getty@tty1.service.d/20-j71b-platform.conf deleted file mode 100644 index ff3f8fe..0000000 --- a/system/j71b/overlay/etc/systemd/system/getty@tty1.service.d/20-j71b-platform.conf +++ /dev/null @@ -1,3 +0,0 @@ -[Unit] -Requires=j71b-platform-ready.service -After=j71b-platform-ready.service diff --git a/system/j71b/overlay/etc/systemd/system/multi-user.target.wants/j71b-charge.service b/system/j71b/overlay/etc/systemd/system/multi-user.target.wants/j71b-charge.service deleted file mode 120000 index ffbb672..0000000 --- a/system/j71b/overlay/etc/systemd/system/multi-user.target.wants/j71b-charge.service +++ /dev/null @@ -1 +0,0 @@ -/usr/lib/systemd/system/j71b-charge.service \ No newline at end of file diff --git a/system/j71b/overlay/usr/lib/systemd/system/j71b-charge.service b/system/j71b/overlay/usr/lib/systemd/system/j71b-charge.service deleted file mode 100644 index 0f1c3e6..0000000 --- a/system/j71b/overlay/usr/lib/systemd/system/j71b-charge.service +++ /dev/null @@ -1,20 +0,0 @@ -[Unit] -Description=J71b guarded 425mA charging lease (kernel PM coordinated) -Requires=j71b-platform-ready.service -After=systemd-udev-trigger.service j71b-platform-ready.service -ConditionPathExists=/sys/module/j71b_charge_policy/parameters/sleep_parked - -[Service] -Type=simple -ExecStartPre=/usr/libexec/j71b-check-platform -ExecStart=/usr/libexec/j71b-charge-lease -Restart=on-failure -RestartSec=10 -TimeoutStopSec=10 -NoNewPrivileges=yes -ProtectHome=yes -ProtectSystem=strict -# Do not ProtectKernelTunables: this service needs its root-only lease sysfs. - -[Install] -WantedBy=multi-user.target diff --git a/system/j71b/overlay/usr/lib/systemd/system/j71b-platform-ready.service b/system/j71b/overlay/usr/lib/systemd/system/j71b-platform-ready.service deleted file mode 100644 index d24a25a..0000000 --- a/system/j71b/overlay/usr/lib/systemd/system/j71b-platform-ready.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Verify J71b hardware payload and boot activation -After=systemd-udev-trigger.service -Before=j71b-charge.service getty@tty1.service - -[Service] -Type=oneshot -ExecStartPre=/usr/bin/rm -f /run/ipadlinux/platform-verified -ExecStart=/usr/libexec/j71b-check-platform -ExecStartPost=/usr/bin/touch /run/ipadlinux/platform-verified -RemainAfterExit=yes diff --git a/tests/test_device_install.py b/tests/test_device_install.py index 34c1c69..c5cc1fe 100644 --- a/tests/test_device_install.py +++ b/tests/test_device_install.py @@ -28,8 +28,6 @@ def exercise(self, *, corrupt=False, existing=True, unexpected_link=False, bad_s ('etc/omarchy/package-privileges.json', 'file', json.dumps(privileges).encode()), ('lib', 'symlink', 'usr/lib'), ('usr/lib/systemd/systemd', 'file', b'executable'), - ('usr/libexec/j71b-check-platform', 'file', b'executable'), - ('usr/libexec/j71b-charge-lease', 'file', b'executable'), ('usr/libexec/omarchy-diagnostics', 'file', b'executable'), ('etc/orchard/release.json', 'file', b'{"release":"1.0"}'), ]) diff --git a/tests/test_extraction.py b/tests/test_extraction.py index 5c4e867..150186a 100644 --- a/tests/test_extraction.py +++ b/tests/test_extraction.py @@ -235,7 +235,7 @@ def test_public_producer_bakes_parent_directories_and_desktop_updates(self): patch.object(public_root, 'bluetooth_files', return_value={}), \ patch.object(public_root, 'labwc_files', return_value={}), \ patch.object(public_root, 'runtime_files', return_value=('test', { - 'usr/libexec/j71b-check-platform': (b'platform', 0o755)})): + 'usr/bin/orchard-device': (b'device', 0o755)})): result = public_root.build(base, receipt, root, dropbear, root / 'release', 'test', root / 'cursor-tools') raw = subprocess.check_output(['zstd', '-dq', '-c', str(root / 'release/rootfs.tar.zst')]) with tarfile.open(fileobj=io.BytesIO(raw)) as bundle: From 0377e23623b5644c45a3c1d61d31de1cc027bc8e Mon Sep 17 00:00:00 2001 From: Henrik Klee Date: Wed, 28 Oct 2026 14:30:21 +0800 Subject: [PATCH 2/4] Require Orchard 0.1.11 and hardware 0.1.11 Older CLIs refuse to install releases built on the torvalds-based stack. Co-Authored-By: Claude Opus 5.5 --- release/manifests.py | 2 +- requirements-build.txt | 2 +- tests/test_manifests_and_recovery.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/release/manifests.py b/release/manifests.py index c1cf552..eed61c5 100644 --- a/release/manifests.py +++ b/release/manifests.py @@ -17,7 +17,7 @@ def write(output, version, root): document = ('schema = 1\nkind = "orchard.install"\ndistro = "omarchy-a"\n' 'name = "Omarchy A"\nversion = ' + json.dumps(version) + '\n' 'device = "j71b"\nboot_method = "checkm8-pongo"\n' - 'min_orchard = "0.1.10"\nprotocol = 1\n\n' + 'min_orchard = "0.1.11"\nprotocol = 1\n\n' '[install]\ndefault_root = "/omarchy"\n') for role, name in (('root', 'rootfs.tar.zst'), ('boot', 'recovery.bin'), ('pongo', 'pongo.bin')): item = receipt(output / name) diff --git a/requirements-build.txt b/requirements-build.txt index ebded4d..70f53a5 100644 --- a/requirements-build.txt +++ b/requirements-build.txt @@ -1,2 +1,2 @@ # Install the local wheel until public package distribution is published. -orchard-hardware>=0.1.10,<0.2 +orchard-hardware>=0.1.11,<0.2 diff --git a/tests/test_manifests_and_recovery.py b/tests/test_manifests_and_recovery.py index 0720290..47fcf8c 100644 --- a/tests/test_manifests_and_recovery.py +++ b/tests/test_manifests_and_recovery.py @@ -29,7 +29,7 @@ def test_single_target_manifest_and_index_pin_exact_bytes(self): self.assertEqual(doc['device'], 'j71b') self.assertEqual(doc['install']['default_root'], '/omarchy') self.assertEqual(doc['root']['sha256'], hashlib.sha256(b'rootfs.tar.zst').hexdigest()) - self.assertEqual(doc['min_orchard'], '0.1.10') + self.assertEqual(doc['min_orchard'], '0.1.11') self.assertEqual(manifests.read_release(path)['assets']['recovery'], doc['boot']) with self.assertRaises(FileExistsError): manifests.write(output, 'test-1', root) From f2c22e0b88c0c02679fbdb914043c9aad1582631 Mon Sep 17 00:00:00 2001 From: Henrik Klee Date: Wed, 28 Oct 2026 15:08:41 +0800 Subject: [PATCH 3/4] Read firmware recipe schema 2 and the generic recovery path Co-Authored-By: Claude Opus 5.5 --- release/j71b/audit.py | 2 +- release/j71b/recovery_audit.py | 2 +- tests/test_extraction.py | 4 ++-- tests/test_manifests_and_recovery.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/release/j71b/audit.py b/release/j71b/audit.py index 5821ea3..ff834a3 100644 --- a/release/j71b/audit.py +++ b/release/j71b/audit.py @@ -62,7 +62,7 @@ def audit(manifest): names, ancestors, directories = set(), set(), set() desktop = public = privileges = cursor = bluetooth = labwc = None recipe = json.loads(firmware_path('j71b').read_text()) - private = {item['destination'] for item in recipe['files'] + recipe['generated']} + private = {item['destination'] for item in recipe['outputs']} with subprocess.Popen(['zstd', '-d', '-q', '-c', str(paths['root'])], stdout=subprocess.PIPE) as decoder: with tarfile.open(fileobj=decoder.stdout, mode='r|') as archive: for member in archive: diff --git a/release/j71b/recovery_audit.py b/release/j71b/recovery_audit.py index 3a17d3d..8db7e3b 100644 --- a/release/j71b/recovery_audit.py +++ b/release/j71b/recovery_audit.py @@ -49,7 +49,7 @@ def audit_runtime(image, root_spec, release): part = next(row for row in receipt['components'] if row['name'] == 'initramfs') hashes, modes, records = {}, {}, {} firmware = json.loads(firmware_path('j71b').read_text()) - private = {row['destination'] for row in firmware['files'] + firmware['generated']} + private = {row['destination'] for row in firmware['outputs']} names = set() with image.open('rb') as raw: raw.seek(part['offset']) diff --git a/tests/test_extraction.py b/tests/test_extraction.py index 150186a..131bb8e 100644 --- a/tests/test_extraction.py +++ b/tests/test_extraction.py @@ -200,7 +200,7 @@ def test_static_diagnostics_authenticates_with_stock_arch_nss(self): result = subprocess.run(['podman', 'run', '--rm', '--network', 'none', '--cap-add', 'NET_ADMIN', '--userns', 'keep-id:uid=0,gid=0', '-v', str(tools) + ':/probe:ro', '-v', str(staged) + ':/runtime:ro', - '-v', str(HARDWARE / 'boot/initramfs/j71b/recovery') + ':/recipe:ro', + '-v', str(HARDWARE / 'boot/initramfs/recovery') + ':/recipe:ro', image, 'sh', '-c', script], capture_output=True, text=True, timeout=30) self.assertEqual(result.returncode, 0, result.stderr) @@ -340,7 +340,7 @@ def test_recovery_adt_endpoint_owns_size_and_exact_binary_selection(self): table.write_text('dev: size erasesize name\nmtd3: 00000004 00000001 "adt"\n') (root / 'mtd3').write_bytes(b'\x00\xff\r\n') env = {**os.environ, 'ADT_MTD_TABLE': str(table), 'ADT_MTD_DEV': str(root)} - endpoint = HARDWARE / 'boot/initramfs/j71b/recovery/capture-adt' + endpoint = HARDWARE / 'boot/initramfs/recovery/capture-adt' size = subprocess.check_output(['sh', str(endpoint), '--size'], env=env) self.assertEqual(int(size.strip(), 16), 4) self.assertEqual(subprocess.check_output(['sh', str(endpoint)], env=env), b'\x00\xff\r\n') diff --git a/tests/test_manifests_and_recovery.py b/tests/test_manifests_and_recovery.py index 47fcf8c..007d612 100644 --- a/tests/test_manifests_and_recovery.py +++ b/tests/test_manifests_and_recovery.py @@ -101,7 +101,7 @@ def test_actual_recovery_root_lock_and_public_hashes_are_checked(self): def test_public_recovery_cannot_contain_firmware_or_session_credentials(self): recipe = json.loads(firmware_path('j71b').read_text()) - for name in (recipe['files'][0]['destination'], 'run/recovery/ssh_host_ed25519_key'): + for name in (recipe['outputs'][0]['destination'], 'run/recovery/ssh_host_ed25519_key'): with self.subTest(name=name), self.assertRaises(ValueError): self.exercise(private_file=name) From 3fff208e7648dd25a2fe3c21588875ab8f09479f Mon Sep 17 00:00:00 2001 From: Henrik Klee Date: Wed, 28 Oct 2026 19:19:09 +0800 Subject: [PATCH 4/4] Use hardware and CLI 0.1.11 Point the submodules at the merged torvalds-based hardware layer (OrchardLinux/hardware#11) and device-generic CLI (OrchardLinux/cli#5). Co-Authored-By: Claude Opus 5.5 --- cli | 2 +- hardware | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli b/cli index 4f96e58..20c67e1 160000 --- a/cli +++ b/cli @@ -1 +1 @@ -Subproject commit 4f96e5805975d00ec4b89fe4064e8c1acbb1bcc4 +Subproject commit 20c67e132c69a7737c8bd52af50d89c58dce8267 diff --git a/hardware b/hardware index d60da6b..2de374f 160000 --- a/hardware +++ b/hardware @@ -1 +1 @@ -Subproject commit d60da6b31a07f84a76fe24df27fcd398e60363ee +Subproject commit 2de374f8ef9dd1b77b7bb42a6425a57df539d7f0 diff --git a/uv.lock b/uv.lock index aa64d2f..5afc403 100644 --- a/uv.lock +++ b/uv.lock @@ -151,7 +151,7 @@ requires-dist = [ [[package]] name = "orchard-cli" -version = "0.1.10" +version = "0.1.11" source = { editable = "cli" } dependencies = [ { name = "jsonschema", version = "4.25.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -177,7 +177,7 @@ dev = [{ name = "mypy", specifier = "==1.18.2" }] [[package]] name = "orchard-hardware" -version = "0.1.10" +version = "0.1.11" source = { editable = "hardware" } dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" },