Repository navigation
Unify around alr; Termux CLI first (Ubuntu 26.04, tawcroot lane) - #25
Conversation
Record the integrated direction and bring stale documents in line with build 0.4.163 (targetSdk 28, alr engine, PRoot removed). - Add ADR 0004: single product engine (alr), three layers (engine, host services, delivery surfaces), decisions D1-D8, phase plan P0-P4, and the unevaluated in-process SECCOMP_RET_TRAP lane for raw-syscall binaries with a measurable decision table. - Add docs/adr/README.md explaining the three ADR namespaces. - Rewrite README.md and docs/architecture.md; rewrite docs/architecture/gpu-display-bridge.md to the real stack. - Revise product-requirements.md and research/prior-art.md (tawc, glibc-runner, alr engine; PRoot removed). - Label PERFORMANCE.md numbers as legacy-loader measurements; GPU re-measurement on the alr engine is open (P1). - Add status banners to historical specs; amend ADR 0001 status. - Fix runtime/alr README relationship section; add an ADR 0006 addendum. - Add tests/test_unified_direction_docs.py (decision record and link guard). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ea46k1MKywZKnRp7wXsFUU
Vendor wmww/tawc tawcroot/ at d9efb4a (src, include, perf, LICENSE.MIT), unmodified and not wired into any build or package. Record provenance, rules, host build check and reference numbers in VENDORING.md; correct the tawc licensing statement (sources MIT, only the tawc APK is GPLv3) in ADR 0004, prior-art and THIRD-PARTY. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ea46k1MKywZKnRp7wXsFUU
trap_cost.c reproduces tawcroot's filter shape (only the trampoline's return PC is allowed, everything else traps) without its path translation, on the same syscall and iteration count as notif_cost.c. It reports baseline, filter evaluation, signal round trip and the re-issue round trip, applies the D6 table to (re-issue - baseline), and withholds the verdict outside a zygote-forked app context. tawcroot itself cannot produce this number: it does not trap getppid. Its aarch64 build also needs bionic headers; a musl cross-compile fails, which VENDORING.md now records. dev-push.sh trap-cost builds and runs it in Termux. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CEED Spike S2 measured on the Snapdragon reference device (SM-S937N, now Android 17) in a Termux shell (untrusted_app_27, Seccomp=2, zygote filter stacked). Ten runs, unpinned and pinned to CPU 0 and CPU 6: the re-issue round trip minus baseline was +410 to +925 ns, all under the 5 us row of the ADR 0004 D6 table, and over 170x cheaper than USER_NOTIF's 154 us. This is the mechanism alone. Path translation, the static-binary loader and the MediaTek device remain unmeasured, and ADR 0006's non-goal stands until a loader prototype and a new ADR exist. The device having moved to Android 17 is recorded as an observation: ADR 0007 scopes support to Android 16, so the sweep and acceptance runs need repeating there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
glibc 2.42 implements tcgetattr, tcsetattr, isatty and ttyname with TCGETS2 from inside libc, so those calls never reach the PLT-level ioctl wrapper that already translated TCGETS2, and Android's ioctl filter denies them with EACCES. On Ubuntu 26.04 every terminal therefore looked like a non-tty: tmux 3.6 panes _exit(1)ed on tcgetattr before exec, and `tty` printed "not a tty". Wrap tcgetattr/tcsetattr/isatty on TCGETS/TCSETS*, and make ttyname_r check the fd the same way and read the name from /proc/self/fd on glibc >= 2.42. The same release changed speed_t from the CBAUD code to the baud rate, so the speed fields follow the running glibc's convention (gnu_get_libc_version); a rate with no CBAUD code fails with EINVAL instead of being silently dropped. The acceptance suite gains pty-slave checks for termios, ttyname and speed, via script(1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`alr install` with no distro now provisions ubuntu-26.04 (26.04.1 is what discovery picks today). Every other command without a distro uses the first installed tree of ubuntu-26.04, ubuntu-24.04, so a device provisioned before this change keeps running its 24.04 instead of failing rootfs-missing; the app resolves its guest the same way. `alr config` reports that resolved value. The offline fallback pin stays 24.04-only, so an install of the new default with SHA256SUMS unreachable stops with discovery-failed rather than fetching an unverified image; INSTALL.md says so. INSTALL.md also lists Android 17 as supported (ADR 0007 addendum). The device scripts default to 26.04 too, and ALR GIT VERSION asserts the shape of the version line instead of 24.04's 2.43.0, as the node check already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ucf 3.0052 (Ubuntu 26.04) re-execs itself with a cleaned environment, dropping ALR_ROOT, ALR_FAKEROOT and the rest. The child then ran without path virtualization and as the real uid, so package configure steps (ucf, php, mercurial, sysstat, parallel) failed. The preload now records the nine runtime variables at init and the execve wrapper appends any that the new environment lacks. Values the guest set itself are left alone. Acceptance gains ALR ENV SCRUB KEEPS RUNTIME (env -i id -u under fakeroot must still print 0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADR 0005 keeps one supported guest, the latest LTS. Commands without a distro now always use ubuntu-26.04 instead of preferring whichever LTS tree is installed, and ALR_UBUNTU_PIN goes away with the preference list. Release discovery failing on SHA256SUMS now stops with discovery-failed for every release; the hash-less 24.04 fallback name is removed. A 24.04 tree still runs when named with -d, unverified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The zygote sweep on the Snapdragon reference device after its 16 -> 17 upgrade is identical to 16's (239 syscalls), and acceptance passes on 17 (ADR 0007 addendum). P0 now accepts 16 and 17. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…droid 17 - ADR 0005 (Accepted): goals T1-T5, priority T1 -> T5 -> T3 -> T4 with T2 as a gate, latest-LTS-only guest policy, Termux CLI as the product, an extension APK for GUI/GPU, and the standalone APK frozen. - ADR 0004 and the ADR index note the partial supersession. - runtime/alr ADR 0007 addendum: Android 17 sweep identical, now supported. Sweep file for the Snapdragon 8 Elite on 17 added. - INSTALL, 06-cli-spec and 00-product: 26.04 is the only supported guest, no hash-less discovery fallback, Android 16 and 17. - T1 evidence (engine=alr): 26.04.1 acceptance PASS 164, FAIL 2 (codex doctor output format), KNOWN_FAIL 1, SKIP 1; breadth 96/96; the glibc 2.42 termios and ucf environment fixes. breadth.sh uses the 26.04 package names bind9-dnsutils and 7zip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ale check codex 0.162 no longer executes git or rg in `codex doctor`; it prints "found; execution not verified". ALR CODEX FINDS GUEST GIT/RG now match the discovery lines, and the new CLI HOSTBIN RUNS GUEST RG runs rg through the hostbin wrapper so execution stays asserted for both tools (git already was, by CLI HOSTBIN RUNS GUEST TOOL). CLI VERSION DETECTS STALE PRELOAD needed a second .so from $PREFIX/share/alr and so always skipped on a dev push. `alr version` only compares hashes, so the check now appends bytes to the guest copy, expects rc=1, and restores it. Ubuntu 26.04.1 on Android 17: PASS 169, FAIL 0, KNOWN_FAIL 1 (/dev/full, non-goal), SKIP 0, which meets ADR 0005 T1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RET_TRAP puts the returning filter's SECCOMP_RET_DATA in si_errno. The zygote filter's is 0 (measured on Android 17, SM8750); anything else comes from a filter the guest stacked on top, such as an in-process path lane or the guest's own sandbox, and its handler must run. Before this the supervisor answered those traps from the emulation table, so tawcroot's path traps came back -ENOSYS and nothing loaded. Where both filters trap, the kernel reports the later filter's data, so the guest sees those too; a call its handler re-issues that the zygote blocks traps again with data 0 and is rescued as before. test_supervisor gains case 11 (guest filter trap passthrough), which failed with rc=3 before the change. trap_cost now marks its trap the same way so it can also be run under `alr run`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… guest auxv Two local changes for the ADR 0005 T5 coexistence spike, recorded in VENDORING.md as rule 3 requires. Still not built into the product. - The filter's RET_TRAP carries TAWCROOT_TRAP_DATA so alr's supervisor can tell tawcroot's traps from the zygote filter's (data 0). - prctl(PR_GET_AUXV) returns the auxv the loader built for the guest. Passed through it returned tawcroot's own, whose AT_EXECFN names tawcroot or the --exec-child fd, and uutils (which names itself from it via rustix) failed with "unknown program 'tawcroot'". VENDORING also records the aarch64 device build (Termux clang 21 with -mno-outline-atomics) and points at the device workload numbers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t stay traced On the Snapdragon reference device, Android 17, Ubuntu 26.04.1: - si_errno tells the zygote's traps (0) from a stacked filter's. - With the supervisor passing marked traps through, tawcroot runs under alr; acceptance stays PASS 169, FAIL 0, SKIP 0. - A trap in a traced process costs about 60 us instead of 0.5 us, and `find /usr/share -type f` takes 6.0-6.3 s under the supervisor against 0.35 s for tawcroot alone and 0.15-0.18 s native. The lane must detach from the supervisor on entry. - uutils, codex and bash run in the tawcroot lane; uutils needed the PR_GET_AUXV fix, which is also why it fails under the preload lane. ADR 0005 records the T5 status and the next steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elative link path - uutils' multicall dispatch names itself from the kernel's AT_EXECFN, read with a raw prctl(PR_GET_AUXV). Under the explicit loader that is ld.so's path, hence "unknown program 'ld-linux-aarch64.so'". glibc's ld.so only repoints its own copy, so getauxval() never showed it. At init the preload now overwrites that stack string with the guest path when it names the loader and the guest path fits. - symlink/symlinkat relativized an absolute target only when the link path itself was absolute, so `ln -s /etc/hostname l` stored "/etc/hostname" and the kernel followed it into Android's /etc. The link's parent is now resolved by the kernel (O_PATH + /proc/self/fd), which covers relative paths, dirfds and "..". Acceptance gains PRELOAD SYMLINK RELATIVE LINKPATH and DOTDOT LINKPATH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the preload fix uutils runs under alr: acceptance PASS 171 FAIL 0 SKIP 0 and breadth 96/96 on 26.04.1 with uutils in place. install and adopt no longer repoint /usr/bin at the gnu* binaries, and the warning and its reason=uutils-coreutils go away. `alr use-gnu-coreutils` stays for anyone who prefers GNU. Two acceptance checks assumed GNU: DOCTOR P11 DYNAMIC CLEAN scans /bin/bash instead of /bin/true (now the uutils binary, which carries its own svc), and ALR PTY SPEED uses gnustty, since uutils' stty has no `speed` operand and the check is about the preload's speed_t handling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/device/t2_native.sh runs git status over 10k files, node -e 0, python3 -c pass, npm ci with 74 deps and an apt reinstall natively and under alr with hyperfine, both as one `alr run` and as 20 runs from a guest shell, and reports medians against ADR 0005's 1.2x budget. apt is reported but not judged: Ubuntu's lists and dpkg database are far larger than Termux's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Evidence for the uutils fix and the T2 run (git status 1.08, python3 1.11, node 0.41, npm ci 0.79 against Termux), with raw acceptance and breadth output. ADR 0006 gains an addendum: uutils lost only its name, and the ADR now covers binaries that issue path syscalls without libc. 00-product, RISKS, 07-acceptance, the playbook, the provisioning reason list, ADR 0005 and the product requirements are updated to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alr's preload lane and the tawcroot lane share one rootfs, so under TAWCROOT_ALR the lane writes it the way the preload does and keeps syscalls off the trap path: - real uid/gid unless --fakeroot; stat not decorated root-owned - absolute symlink targets stored relative to the link's parent - no link store; a refused hardlink is copied - rescue slot claimed only for virtual euid 0 - resolver caches directory prefixes known not to be symlinks, cleared after rename/unlink/symlink/chroot - usercopy addresses the process by a tgid cached in a MADV_WIPEONFORK page - stat-family results written by the kernel into the guest buffer Without the flag the tree builds upstream's behaviour. VENDORING.md lists the changes as local changes 3 to 9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- alr run and the preload's execve hand static ELFs and ELFs with a Go
note to alr-lane with guest arguments and a loader-free environment;
the supervisor detaches a tracee that execs alr-lane
- Go notes are matched as cmd/link writes them ("Go\0\0", namesz 4)
- the rootfs path is canonicalized: getcwd, /proc/self/fd and
AT_EXECFN come back canonical and never matched a root spelled
with "." or ".."
- alr is linked static at API 29; resolvd asks netd's dnsproxyd
directly because a static libc cannot reach netd
- alr-lane is built by make lane, the release script, the Termux
recipe and dev-push, and ships with its MIT notice
- acceptance gains the lane and cross-lane checks; t2_native gains
Go rows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADR 0010 supersedes ADR 0006 and 0008. Evidence: acceptance 187/0/1/0, breadth 96/96 and every T2 workload within 1.2x with the release build. Workloads that are nothing but path syscalls stay above budget in the lane (find 1.34x, ls -lR 1.65x). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under -DTAWCROOT_ALR only (VENDORING.md local changes 10 to 14): - Read guest memory with load/store loops instead of process_vm_readv. A bad pointer faults into alr-lane's own SIGSEGV/SIGBUS entry and becomes EFAULT. The guest's dispositions live in a shadow; guest faults are forwarded to the guest handler, SIG_DFL re-faults with the default action, SA_RESETHAND and SIG_IGN across exec behave as before, and a CLONE_VM child keeps its own settings. - Pass a one-name relative path to the kernel with the guest's dirfd when the directory is on the rootfs and the name is neither a bind at the root nor a symlink. Cache the cwd's guest path per process. - Stat with following looks at the leaf without following first, and translates symlink leaves through a cache checked by inode and ctime. - Trap fstat only for reserved fds, and getdents64 only for fds 900-999, where opened /proc/<pid>/fd directories are placed. - Let the directory cache hold following leaves too. busybox find /usr/share: 1.28x -> 1.08x of ALR_LANE=0; ls -lR 1.33x -> 1.09x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n error probe_lane.c is built static in the guest and checks 31 lane behaviours: absolute symlinks via dirfd and cwd, vfork chdir, bind names at the root, hidden own fds, retargeted links, EFAULT on bad pointers, and the guest's SIGSEGV/SIGBUS handlers. acceptance.sh emits its lines. alr-lane's handler runs on the guest's stack, so every lane build now fails on a frame above 1024 bytes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Evidence 2026-10-11-t5-lane-cost with raw microbench, interleaved A/B, T2 and acceptance output. ADR 0005 T5 is met; VENDORING records local changes 10 to 15; RISKS lists the guest-visible differences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A fork child whose first trapped call came from its own vfork child cached that child's pid as the memory owner. The vfork child then reset SIGSEGV in the main shadow and the fork child lost its handler (new probe check reproduced it 5/5 before this change). Under -DTAWCROOT_ALR, clone without CLONE_VM now traps. The handler runs the clone and the child stores its tgid before returning to guest code; a new child stack is applied to the child's signal context. Threads and vfork still do not trap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rball A release unpacked outside $PREFIX did not find its own libexec/alr/alr-lane, share/alr/libalr_preload.so or libdl stub: alr looked only beside itself and in $PREFIX. It now also looks in <bin>/../libexec/alr and <bin>/../share/alr. bsdtar on macOS stored com.apple.provenance as ._<name> members, which Termux's tar extracted as files. make-release.sh now archives without extended attributes. acceptance.sh finds alr-doctor beside $ALR, so an unpacked release runs the doctor checks instead of skipping them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An opened /proc/<pid>/fd directory moved to fd 900-999 so that only that band trapped getdents64. Now the directory keeps the fd the kernel gave it and gets a small stacked filter (TSYNC) trapping getdents64 on exactly that number. Other directories still read without a trap; find/ls cost is unchanged against the band. The band remains the fallback when no filter can be added (more than 32 numbers, or a CLONE_VM child). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1228 ns) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewrite runtime/alr/docs/INSTALL.md for Ubuntu 26.04 and the tawcroot lane: building the tarball from the repo, the published v0.5.0 release (no alr-lane, 24.04 default) and how to tell them apart, unpacking outside $PREFIX, --fakeroot for apt, lane limits and troubleshooting. Link it from the top-level README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae3493c3d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| -static -nostdlib -nostartfiles -Wl,--image-base=0x10000000 \ | ||
| -Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384 \ | ||
| -o alr-lane \ | ||
| third_party/tawcroot/src/*.c third_party/tawcroot/src/arch/aarch64_*.S |
There was a problem hiding this comment.
Point the recipe at sources containing alr-lane
The recipe still downloads the pinned v0.5.0 source archive, while the newly added build command expects third_party/tawcroot; runtime/alr/docs/INSTALL.md explicitly documents that the published v0.5.0 predates alr-lane. Consequently a standard Termux package build receives sources without these files and fails when this glob is compiled. Bump the package version/source hash or add a separate source containing the vendored lane.
Useful? React with 👍 / 👎.
| # whether this recipe's $TERMUX_PKG_API_LEVEL and default flags accept | ||
| # -static is UNVERIFIED against termux-packages -- if not, drop it and | ||
| # the package is only slower. | ||
| $CC $CPPFLAGS $CFLAGS $LDFLAGS -O1 -static \ |
There was a problem hiding this comment.
Compile the static Termux binary against API 29
In a standard Termux package build, TERMUX_PKG_API_LEVEL defaults to 24 because this recipe never overrides it (Termux build setup). Adding -static therefore links alr with the API-24 libc.a, even though this repository's Makefile and device evidence state that this exact combination aborts at startup with an underaligned TLS segment and deliberately target Android API 29. Set the package API to 29 or invoke a separate API-29 compiler for alr while retaining the desired doctor target.
Useful? React with 👍 / 👎.
| if (!strncmp(ge[k], "LD_", 3) || !strncmp(ge[k], "ALR_", 4) | ||
| || !strncmp(ge[k], "LOCPATH=", 8)) | ||
| continue; |
There was a problem hiding this comment.
Preserve caller-owned variables when entering the lane
For any top-level static or Go target, this prefix filter silently drops caller-owned variables such as ALR_GPU_BRIDGE_HOST or any accepted -e ALR_CUSTOM=value, despite env_is_reserved() deliberately reserving only the runtime's enumerated names so embedders can use the rest of the ALR_ namespace. The analogous env_strip() path also drops every ALR_ variable for guest-initiated lane transitions. Strip only the runtime-owned keys rather than the entire prefix.
Useful? React with 👍 / 👎.
| long rv = TAWC_RAW(TAWC_SYS_ioctl, fd, args->b, arg, | ||
| args->d, args->e, args->f); | ||
| if (rv == TAWC_EACCES && is_netdev_get_ioctl(cmd)) | ||
| return netdev_ioctl_on_inet(cmd, arg); | ||
| return rv; |
There was a problem hiding this comment.
Emulate Android-denied PTY ioctls inside the lane
When a static or Go program enters alr-lane, all of its descendants remain there without the preload wrappers. Android's measured policy denies TIOCGSID, TIOCGETD, and TIOCEXCL, but this lane handler only translates termios2 and netdevice requests before returning the raw EACCES. Consequently PTY consumers launched from a static shell or Codex can fail even though the same ioctls are explicitly emulated on the normal preload path; the acceptance ioctl probe currently exercises only that preload path. Mirror those PTY translations in the lane handler.
Useful? React with 👍 / 👎.
| unsigned char zero[TAWC_KERN_TERMIOS2_TAIL] = {0}; | ||
| e = tawc_copy_to_guest((void *)(uintptr_t) | ||
| (arg + TAWC_KERN_TERMIOS_SIZE), | ||
| zero, sizeof zero); |
There was a problem hiding this comment.
Preserve termios2 speeds in the lane fallback
On devices where Android rejects TCGETS2, every dynamic Ubuntu 26.04 descendant running inside the lane uses this fallback. Glibc 2.43 interprets the trailing fields as actual baud rates, so zeroing them reports speed 0 rather than the PTY's CBAUD-derived rate; the corresponding setter also discards requested speed fields. The preload path added explicit code-to-baud conversion for this same ABI, but lane-launched tools such as gnustty still receive incorrect terminal state. Populate and consume the termios2 speed fields when converting to or from legacy TCGETS/TCSETS.
Useful? React with 👍 / 👎.
| /* the lane's ids follow the preload's: real unless fakeroot */ | ||
| if (fake) av[n++] = (char *)"--fakeroot"; | ||
| av[n++] = (char *)"--"; | ||
| av[n++] = (char *)guest_cmd; |
There was a problem hiding this comment.
Pass the PATH-resolved command to alr-lane
When the user supplies a command without a slash, resolve() searches the guest PATH and records the located binary in host, but this lane invocation passes the original bare guest_cmd. Unlike execvp, alr-lane opens its command path directly and performs no PATH search, so common forms such as alr run codex, alr run go version, or alr run busybox fail after being successfully classified for the lane; the current lane tests avoid this by using absolute paths. Pass the resolved guest path as the lane executable while preserving the requested value separately as argv[0] if needed.
Useful? React with 👍 / 👎.
Summary
Unifies the project around the alr engine and makes Termux CLI the first target.
alr-lane, ADR 0010), with path virtualization. Lane cost brought within the T2 budget (direct guest-memory copies, one-name kernel lookups, BPF trap filtering).$PREFIX, AppleDouble members in the release tarball,/proc/<pid>/fdkeeps its natural fd number.Verification (device: SM-S937N, Android 17)
PASS=221 FAIL=0 KNOWN_FAIL=1 SKIP=0(KNOWN_FAIL is/dev/full, which native Termux also cannot open).make checkpasses; full pytest has the same 24 failures as the base (legacy Android app/proot tests).Known limits
SigCgt(ADR 0010 decision 7).find/ls -lRsit near the T2 budget (1.08–1.21x across runs).🤖 Generated with Claude Code