From dab0d07d705303978e2e513502ce62ce7558a4de Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 19:36:58 -0400 Subject: [PATCH 01/23] Pin Grease native libc vocabulary source --- source | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source b/source index 8052868..e12537d 160000 --- a/source +++ b/source @@ -1 +1 @@ -Subproject commit 8052868773077602266d80bf39aad6998e2da749 +Subproject commit e12537d8e31999cd9424675c857274a1bb66025c From f0b27aefc2c474f44511434b76789d882fa3d0cd Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 19:48:31 -0400 Subject: [PATCH 02/23] ci: expose native diagnostics and run libc test --- .github/workflows/grease-receipt.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/grease-receipt.yml b/.github/workflows/grease-receipt.yml index 08dbc07..7f8210e 100644 --- a/.github/workflows/grease-receipt.yml +++ b/.github/workflows/grease-receipt.yml @@ -43,9 +43,30 @@ jobs: - name: Fix kernel mmap rnd bits run: sudo sysctl vm.mmap_rnd_bits=28 - - name: Run and verify the inherited Oils cpp-spec oracle + - name: Run inherited Oils cpp-spec oracle working-directory: source-worktree run: | soil/github-actions.sh run-job cpp-spec podman test "$(cat _tmp/soil/commit-hash.txt)" = "${{ steps.source_pin.outputs.revision }}" - soil/host-shim.sh did-all-succeed cpp-spec + + - name: Print native build diagnostics + if: always() + working-directory: source-worktree + run: | + for name in oils-cpp-smoke osh-all ysh-all; do + path="_tmp/soil/logs/$name.txt" + if test -f "$path"; then + echo "===== $path =====" + cat "$path" + fi + done + + - name: Build and run focused libc native tests + working-directory: source-worktree + run: | + ninja _bin/cxx-dbg/cpp/libc_test + _bin/cxx-dbg/cpp/libc_test + + - name: Verify inherited cpp-spec result + working-directory: source-worktree + run: soil/host-shim.sh did-all-succeed cpp-spec From cb40194d25690c6ab43ad859fa09caf79e6e73e3 Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 19:48:47 -0400 Subject: [PATCH 03/23] Pin current native libc source and diagnostics --- source | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source b/source index e12537d..bc4f077 160000 --- a/source +++ b/source @@ -1 +1 @@ -Subproject commit e12537d8e31999cd9424675c857274a1bb66025c +Subproject commit bc4f077dfe2244246f36228771056599a7f37024 From 26e79e339311a08dab48606f5b5ef473cdf8f147 Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 21:02:53 -0400 Subject: [PATCH 04/23] native CI: repair translated bridge and host build handoff --- .github/workflows/grease-receipt.yml | 5 ++++- source | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/grease-receipt.yml b/.github/workflows/grease-receipt.yml index 7f8210e..4afbafb 100644 --- a/.github/workflows/grease-receipt.yml +++ b/.github/workflows/grease-receipt.yml @@ -64,9 +64,12 @@ jobs: - name: Build and run focused libc native tests working-directory: source-worktree run: | + # SOIL's container writes build outputs as its own uid. Hand only the + # build/output directories back to the host runner before host ninja. + sudo chown -R "$(id -u):$(id -g)" _build _bin ninja _bin/cxx-dbg/cpp/libc_test _bin/cxx-dbg/cpp/libc_test - name: Verify inherited cpp-spec result working-directory: source-worktree - run: soil/host-shim.sh did-all-succeed cpp-spec + run: soil/host-shim.sh did-all-succeed cpp-spec \ No newline at end of file diff --git a/source b/source index bc4f077..f965271 160000 --- a/source +++ b/source @@ -1 +1 @@ -Subproject commit bc4f077dfe2244246f36228771056599a7f37024 +Subproject commit f965271778a955c8cb4301312217e6a126bea173 From efa63429b072b1818fce2e3d6aea5ae42e06a09f Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 21:10:32 -0400 Subject: [PATCH 05/23] native CI: run only focused libc bridge tests --- .github/workflows/grease-receipt.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/grease-receipt.yml b/.github/workflows/grease-receipt.yml index 4afbafb..910f3d7 100644 --- a/.github/workflows/grease-receipt.yml +++ b/.github/workflows/grease-receipt.yml @@ -64,12 +64,12 @@ jobs: - name: Build and run focused libc native tests working-directory: source-worktree run: | - # SOIL's container writes build outputs as its own uid. Hand only the + # SOIL's container writes build outputs as its own uid. Hand only the # build/output directories back to the host runner before host ninja. sudo chown -R "$(id -u):$(id -g)" _build _bin ninja _bin/cxx-dbg/cpp/libc_test - _bin/cxx-dbg/cpp/libc_test + _bin/cxx-dbg/cpp/libc_test -t grease_native_ - name: Verify inherited cpp-spec result working-directory: source-worktree - run: soil/host-shim.sh did-all-succeed cpp-spec \ No newline at end of file + run: soil/host-shim.sh did-all-succeed cpp-spec From 3547222ea6739307ee37f00354dff4a2cc2ed0a3 Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 21:33:20 -0400 Subject: [PATCH 06/23] Pin translator-safe native libc boundary --- source | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source b/source index f965271..962e019 160000 --- a/source +++ b/source @@ -1 +1 @@ -Subproject commit f965271778a955c8cb4301312217e6a126bea173 +Subproject commit 962e019e797d4ce7bdc590b991543126bb663057 From 983120a6c3883c18641acaa20d9cd369ebc0baf1 Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 21:37:49 -0400 Subject: [PATCH 07/23] Pin scoped native libc implementation --- source | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source b/source index 962e019..1577873 160000 --- a/source +++ b/source @@ -1 +1 @@ -Subproject commit 962e019e797d4ce7bdc590b991543126bb663057 +Subproject commit 15778735ec5e5564a6c2305cc5095c141bd4b2c4 From dfa8f582a8afb923a8ca5dcfec52e84fff655ebd Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 21:40:01 -0400 Subject: [PATCH 08/23] Exercise file-backed mapping from Grease --- examples/ib-mapped-index.ysh | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 examples/ib-mapped-index.ysh diff --git a/examples/ib-mapped-index.ysh b/examples/ib-mapped-index.ysh new file mode 100644 index 0000000..17b4f80 --- /dev/null +++ b/examples/ib-mapped-index.ysh @@ -0,0 +1,63 @@ +#!/usr/bin/env ysh + +# Representative IB/Pensieve proof: work directly with a mapped index page. +# The acceptance workflow prepares a 4096-byte file whose first eight bytes are +# "fragment" and verifies that this program persists "pensieve" through mmap. + +if (len(ARGV) !== 1) { + echo 'usage: ib-mapped-index.ysh INDEX_FILE' + exit 2 +} + +var opened = native.openat(native.cwd, ARGV[0]; flags=['read-write']) +if (not opened.ok) { + echo 'openat failed' + exit 3 +} +var index_file = opened.value + +var mapped = native.mmap(4096, index_file; + protection=['read', 'write'], flags=['shared']) +if (not mapped.ok) { + echo 'mmap failed' + exit 4 +} +var index_page = mapped.value + +var before = native.readMapping(index_page, 0, 8) +if (not before.ok or before.value !== 'fragment') { + echo 'mapped input did not contain fragment' + exit 5 +} + +var wrote = native.writeMapping(index_page, 0, 'pensieve') +if (not wrote.ok) { + echo 'mapped write failed' + exit 6 +} + +var synced = native.msync(index_page; flags=['sync']) +if (not synced.ok) { + echo 'msync failed' + exit 7 +} + +var after = native.readMapping(index_page, 0, 8) +if (not after.ok or after.value !== 'pensieve') { + echo 'mapped value did not change to pensieve' + exit 8 +} + +var unmapped = native.munmap(index_page) +if (not unmapped.ok) { + echo 'munmap failed' + exit 9 +} + +var closed = native.close(index_file) +if (not closed.ok) { + echo 'close failed' + exit 10 +} + +echo 'IB mapped-index proof passed' From 3e61bf368a62951992405b66cc43b78ec3550d39 Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 21:40:14 -0400 Subject: [PATCH 09/23] Exercise native actions from Grease syntax --- .github/workflows/grease-receipt.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/grease-receipt.yml b/.github/workflows/grease-receipt.yml index 910f3d7..1670327 100644 --- a/.github/workflows/grease-receipt.yml +++ b/.github/workflows/grease-receipt.yml @@ -70,6 +70,19 @@ jobs: ninja _bin/cxx-dbg/cpp/libc_test _bin/cxx-dbg/cpp/libc_test -t grease_native_ + - name: Exercise mapped IB index from Grease + working-directory: source-worktree + shell: bash + run: | + ninja _bin/cxx-asan+gcalways/ysh + index_file=$(mktemp) + trap 'rm -f "$index_file"' EXIT + truncate -s 4096 "$index_file" + printf fragment | dd of="$index_file" conv=notrunc status=none + _bin/cxx-asan+gcalways/ysh \ + "$GITHUB_WORKSPACE/examples/ib-mapped-index.ysh" "$index_file" + test "$(head -c 8 "$index_file")" = pensieve + - name: Verify inherited cpp-spec result working-directory: source-worktree run: soil/host-shim.sh did-all-succeed cpp-spec From bfe9f10fc299cbf3c343eaaedc015fb25f294180 Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 21:40:42 -0400 Subject: [PATCH 10/23] Document the narrow native libc boundary --- docs/NATIVE-LIBC.md | 155 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 docs/NATIVE-LIBC.md diff --git a/docs/NATIVE-LIBC.md b/docs/NATIVE-LIBC.md new file mode 100644 index 0000000..0143d43 --- /dev/null +++ b/docs/NATIVE-LIBC.md @@ -0,0 +1,155 @@ +# Native libc actions in Grease + +Grease is still the Oils/YSH-derived executable described in +[`CURRENT-GREASE.md`](CURRENT-GREASE.md). This native vocabulary extends that +implementation; it does not add a second runtime. + +The C++ layer here is inherited from Oils. Oils translates the shell runtime to +C++ for the native `oils-for-unix` / YSH executable, and already keeps native +replacements for Python-extension operations in `cpp/`. Grease therefore uses +that existing seam: + +```text +Grease / YSH program + | + v +native object in builtin/func_native.py + | + v +pyext/libc.pyi boundary + | + v +existing Oils cpp/libc.h + cpp/libc.cc native layer + | + v +libc (Bionic on Android) -> kernel +``` + +There is no raw syscall-number table and no general C FFI. Grease does not get +`dlsym`, arbitrary pointer calls, variadic calls, callbacks, or general struct +marshalling from this work. + +## Public actions + +The built-in `native` object exposes these operating-system actions: + +- `mmap` +- `munmap` +- `mprotect` +- `msync` +- `openat` +- `linkat` +- `symlinkat` +- `unlinkat` + +Three small support actions make those resources usable without exposing raw C +values: + +- `close` closes a descriptor returned by `openat`; +- `readMapping` reads a bounded byte range from a live mapping; +- `writeMapping` writes a bounded byte range to a live mapping. + +`native.cwd` is a borrowed directory descriptor representing `AT_FDCWD`. It can +be used as the directory argument to the `*at` actions, but it cannot be closed. + +## Resource values + +The public language boundary does not expose a C pointer or kernel file +descriptor as an integer. + +`mmap` returns a `Mapping` object. A mapping contains an opaque `Address`, its +mapped length, and live/inactive state. The native C++ registry owns the actual +`void *` and length. `munmap`, `mprotect`, `msync`, `readMapping`, and +`writeMapping` resolve the opaque handle through that registry. + +`openat` returns a `FileDescriptor` object. Its opaque token resolves to a real +file descriptor in a separate native registry. The object records whether it is +a directory descriptor, whether it is borrowed, and whether it has been closed. + +These are runtime distinctions in current Grease/YSH, not a claim that YSH has +a new static nominal type system. Lengths, offsets, and modes use the existing +YSH `Int` value at the language surface. Lengths and offsets cross the native +boundary as decimal text and are range-checked before conversion to `size_t` or +`off_t`, avoiding an accidental narrowing through a C `int`. + +## Errors + +Native calls return a Grease result object rather than requiring code to inspect +C sentinels such as `-1` or `MAP_FAILED`. + +A result has: + +- `ok`: boolean success status; +- `value`: the result value on success; +- `error`: a `NativeError` on failure. + +A `NativeError` contains the errno number, its symbolic name when known, and the +platform error message. A failed `mmap` never becomes a usable `Address`. + +## Flags + +Grease uses stable text names instead of exporting libc's numeric constants. +The C++ boundary translates those names' internal bit masks to the constants of +the platform being built. + +Memory protection names are `none`, `read`, `write`, and `execute`. +Mapping names are `private`, `shared`, and `anonymous`; exactly one of `private` +or `shared` is required. Synchronization names are `sync`, `async`, and +`invalidate`; exactly one of `sync` or `async` is required. + +`openat` accepts `read-only`, `write-only`, `read-write`, `create`, `exclusive`, +`truncate`, `append`, `directory`, `no-follow`, and `close-on-exec`, with exactly +one access mode. `linkat` has a `followSymlink` boolean and `unlinkat` has a +`removeDirectory` boolean. + +## Implementation boundary + +The native implementation calls libc directly: + +```text +mmap munmap mprotect msync +openat close linkat symlinkat unlinkat +``` + +On Android these resolve through Bionic. The current phone build targets Android +API 28, which is above the API boundary needed by the selected `*at` and mapping +interfaces. No ARM syscall numbers are part of the Grease API or implementation. + +The Python reference execution path is not claimed as an implementation of +these actions. `pyext/libc.pyi` describes the translated boundary, while the +actual new operations are supplied by `cpp/libc.cc` in the native Grease/Oils +build. A Python-only YSH run can construct the `native` object but is not an +acceptance target for invoking these new actions. + +## Relationship to ish + +`ish` is the separate OdriƧ successor line described in `CURRENT-GREASE.md`. +Its direct `execve` milestone is useful design evidence, but it is not the +runtime underneath current Grease. This work therefore does not manufacture a +second `ish` bridge merely to duplicate the Oils native boundary. + +Existing Oils process execution remains inherited behavior and is checked by +the normal native shell test suite. + +## Representative IB use + +[`../examples/ib-mapped-index.ysh`](../examples/ib-mapped-index.ysh) is the +small end-to-end proof. It opens a prepared 4096-byte index file, maps it shared, +reads `fragment`, writes `pensieve`, synchronizes the mapping, reads the changed +value back, unmaps it, and closes the descriptor. The receipt then checks the +file bytes outside the Grease process to prove that the shared mapping really +persisted the change. + +The focused C++ tests additionally cover anonymous mappings, protection changes, +out-of-bounds access, deliberate error cases, file-backed mappings, hard links, +symbolic links, relative `openat`, and `unlinkat`. + +## Extending the vocabulary + +A later native action should stay explicit and small. Add its declaration to the +translated libc boundary, implement the libc/Bionic call in `cpp/libc.cc`, add a +Grease method that converts readable values to that boundary, and exercise both +success and error behavior. If this repeated shape grows large enough to justify +a declaration-driven generator, that can be introduced from demonstrated +repetition rather than turning this first vocabulary into a general-purpose +FFI. From 24e5ebcfd757f344bc0daebe8fa74f7f067a3a5f Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 21:52:22 -0400 Subject: [PATCH 11/23] Pin protection-aware native mappings --- source | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source b/source index 1577873..9024d14 160000 --- a/source +++ b/source @@ -1 +1 @@ -Subproject commit 15778735ec5e5564a6c2305cc5095c141bd4b2c4 +Subproject commit 9024d14ecd25c8c55cd7e8ea0b901a3a9525565c From dc78e1ea86cb3d33fa4f607e695ba8b595dbac4b Mon Sep 17 00:00:00 2001 From: i Date: Thu, 10 Sep 2026 22:02:29 -0400 Subject: [PATCH 12/23] Use call-site syntax for native named arguments --- examples/ib-mapped-index.ysh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/ib-mapped-index.ysh b/examples/ib-mapped-index.ysh index 17b4f80..bc1f448 100644 --- a/examples/ib-mapped-index.ysh +++ b/examples/ib-mapped-index.ysh @@ -9,14 +9,14 @@ if (len(ARGV) !== 1) { exit 2 } -var opened = native.openat(native.cwd, ARGV[0]; flags=['read-write']) +var opened = native.openat(native.cwd, ARGV[0], flags=['read-write']) if (not opened.ok) { echo 'openat failed' exit 3 } var index_file = opened.value -var mapped = native.mmap(4096, index_file; +var mapped = native.mmap(4096, index_file, protection=['read', 'write'], flags=['shared']) if (not mapped.ok) { echo 'mmap failed' @@ -36,7 +36,7 @@ if (not wrote.ok) { exit 6 } -var synced = native.msync(index_page; flags=['sync']) +var synced = native.msync(index_page, flags=['sync']) if (not synced.ok) { echo 'msync failed' exit 7 From c8b2c70a25dbe097ed599d54d464c2e0b10c44de Mon Sep 17 00:00:00 2001 From: i Date: Fri, 11 Sep 2026 12:21:38 -0400 Subject: [PATCH 13/23] ci: run Oils cpp-spec natively on Debian --- .github/workflows/grease-receipt.yml | 69 +++++++++------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/.github/workflows/grease-receipt.yml b/.github/workflows/grease-receipt.yml index 1670327..04258c9 100644 --- a/.github/workflows/grease-receipt.yml +++ b/.github/workflows/grease-receipt.yml @@ -7,73 +7,52 @@ on: jobs: cpp-spec: - runs-on: ubuntu-22.04 - env: - REPO_ROOT: ${{ github.workspace }}/source-worktree + runs-on: [self-hosted, linux, debian] + timeout-minutes: 120 steps: - name: Check out Grease and pinned Oils source uses: actions/checkout@v4 with: submodules: recursive - - name: Verify and record the source pin - id: source_pin + - name: Verify pinned source and Debian host shell: bash run: | revision=$(git rev-parse HEAD:source) test "$(git -C source rev-parse HEAD)" = "$revision" - echo "revision=$revision" >> "$GITHUB_OUTPUT" + . /etc/os-release + test "$ID" = debian + echo "Grease source pin: $revision" + echo "Debian: $PRETTY_NAME" - # The inherited container harness mounts an Oils worktree by itself. - # A normal submodule has a .git file whose target is outside that mount, - # so give the harness an equivalent standalone checkout of the gitlink. - - name: Check out the pinned source as a standalone worktree - uses: actions/checkout@v4 - with: - repository: isomorphisms/oils - ref: ${{ steps.source_pin.outputs.revision }} - path: source-worktree - persist-credentials: false - - - name: Verify the standalone source revision + - name: Prepare native Debian Oils environment + working-directory: source shell: bash run: | - test "$(git -C source-worktree rev-parse HEAD)" = "${{ steps.source_pin.outputs.revision }}" + sudo sysctl vm.mmap_rnd_bits=28 + soil/worker.sh JOB-dev-setup-debian - - name: Fix kernel mmap rnd bits - run: sudo sysctl vm.mmap_rnd_bits=28 - - - name: Run inherited Oils cpp-spec oracle - working-directory: source-worktree - run: | - soil/github-actions.sh run-job cpp-spec podman - test "$(cat _tmp/soil/commit-hash.txt)" = "${{ steps.source_pin.outputs.revision }}" - - - name: Print native build diagnostics - if: always() - working-directory: source-worktree + - name: Run inherited Oils cpp-spec workload natively + working-directory: source + shell: bash run: | - for name in oils-cpp-smoke osh-all ysh-all; do - path="_tmp/soil/logs/$name.txt" - if test -f "$path"; then - echo "===== $path =====" - cat "$path" - fi - done + . build/dev-shell.sh + soil/worker.sh JOB-cpp-spec + soil/host-shim.sh did-all-succeed cpp-spec - name: Build and run focused libc native tests - working-directory: source-worktree + working-directory: source + shell: bash run: | - # SOIL's container writes build outputs as its own uid. Hand only the - # build/output directories back to the host runner before host ninja. - sudo chown -R "$(id -u):$(id -g)" _build _bin + . build/dev-shell.sh ninja _bin/cxx-dbg/cpp/libc_test _bin/cxx-dbg/cpp/libc_test -t grease_native_ - name: Exercise mapped IB index from Grease - working-directory: source-worktree + working-directory: source shell: bash run: | + . build/dev-shell.sh ninja _bin/cxx-asan+gcalways/ysh index_file=$(mktemp) trap 'rm -f "$index_file"' EXIT @@ -82,7 +61,3 @@ jobs: _bin/cxx-asan+gcalways/ysh \ "$GITHUB_WORKSPACE/examples/ib-mapped-index.ysh" "$index_file" test "$(head -c 8 "$index_file")" = pensieve - - - name: Verify inherited cpp-spec result - working-directory: source-worktree - run: soil/host-shim.sh did-all-succeed cpp-spec From c66220f4cdba10206bf720a07c45c808c9107aa9 Mon Sep 17 00:00:00 2001 From: i Date: Fri, 11 Sep 2026 12:24:29 -0400 Subject: [PATCH 14/23] ci: build ARMv7 runtime natively from Debian --- .github/workflows/phone-armv7-runtime.yml | 57 ++++++++++++++--------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/.github/workflows/phone-armv7-runtime.yml b/.github/workflows/phone-armv7-runtime.yml index 4381800..d833e9f 100644 --- a/.github/workflows/phone-armv7-runtime.yml +++ b/.github/workflows/phone-armv7-runtime.yml @@ -14,57 +14,63 @@ permissions: jobs: build-armv7-runtime: - runs-on: ubuntu-22.04 + runs-on: [self-hosted, linux, debian] + timeout-minutes: 120 env: - REPO_ROOT: ${{ github.workspace }}/source-worktree ANDROID_API: '28' ANDROID_ABI: armeabi-v7a + ANDROID_NDK_VERSION: '27.3.13750724' steps: - name: Check out Grease and pinned Oils source uses: actions/checkout@v4 with: submodules: recursive - - name: Verify and record exact Grease source pin + - name: Verify exact source pin and Debian host id: source_pin shell: bash run: | revision=$(git rev-parse HEAD:source) test "$(git -C source rev-parse HEAD)" = "$revision" + . /etc/os-release + test "$ID" = debian echo "revision=$revision" >> "$GITHUB_OUTPUT" + echo "Debian: $PRETTY_NAME" - - name: Check out pinned source as standalone worktree - uses: actions/checkout@v4 - with: - repository: isomorphisms/oils - ref: ${{ steps.source_pin.outputs.revision }} - path: source-worktree - persist-credentials: false - - - name: Verify standalone source revision + - name: Prepare native Debian Oils environment + working-directory: source shell: bash run: | - test "$(git -C source-worktree rev-parse HEAD)" = "${{ steps.source_pin.outputs.revision }}" + sudo sysctl vm.mmap_rnd_bits=28 + soil/worker.sh JOB-dev-setup-debian - - name: Fix kernel mmap rnd bits - run: sudo sysctl vm.mmap_rnd_bits=28 - - - name: Generate native C++ source bundle from current Grease - working-directory: source-worktree + - name: Generate native C++ source bundle with Oils cpp-tarball job + working-directory: source shell: bash run: | - soil/github-actions.sh run-job cpp-tarball podman - test "$(cat _tmp/soil/commit-hash.txt)" = "${{ steps.source_pin.outputs.revision }}" + . build/dev-shell.sh + soil/worker.sh JOB-cpp-tarball soil/host-shim.sh did-all-succeed cpp-tarball test -f _release/oils-for-unix.tar + - name: Install JDK 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + + - name: Install Android SDK and pinned NDK + uses: android-actions/setup-android@v4 + with: + packages: 'ndk;27.3.13750724' + - name: Extract generated native source bundle id: native_source shell: bash run: | rm -rf build/native-source build/package mkdir -p build/native-source build/package - tar -xf source-worktree/_release/oils-for-unix.tar -C build/native-source + tar -xf source/_release/oils-for-unix.tar -C build/native-source source_dir=$(find build/native-source -mindepth 1 -maxdepth 1 -type d -name 'oils-for-unix-*' -print -quit) test -n "$source_dir" test -x "$source_dir/_build/oils.sh" @@ -75,11 +81,17 @@ jobs: env: GREASE_SOURCE_SHA: ${{ steps.source_pin.outputs.revision }} GREASE_PACKAGING_SHA: ${{ github.sha }} + ANDROID_NDK_HOME: ${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }} run: | bash phone/build-armv7-runtime.sh \ "${{ steps.native_source.outputs.dir }}" \ build/package + - name: Verify pinned NDK receipt + shell: bash + run: | + grep -Fx "ndk_revision\t$ANDROID_NDK_VERSION" build/package/receipts/build.tsv + - name: Package deterministic phone artifact id: package shell: bash @@ -123,9 +135,10 @@ jobs: Pinned Oils-derived Grease source: $SOURCE_SHA ABI: armeabi-v7a Android API floor: $ANDROID_API + Android NDK: $ANDROID_NDK_VERSION Artifact SHA-256: $ARTIFACT_SHA256 - Cloud build evidence only. Physical-phone execution remains pending until Phone Cat Food installs and exercises this exact artifact. + Debian cloud build evidence only. Physical-phone execution remains pending until Phone Cat Food installs and exercises this exact artifact. EOF gh release create "$tag" \ build/grease-armv7-termux.tar.gz \ From 73eaea6a9dd5936b9db08d66f7cd3a00e24087ac Mon Sep 17 00:00:00 2001 From: i Date: Fri, 11 Sep 2026 12:25:18 -0400 Subject: [PATCH 15/23] ci: make Debian ARMv7 dependencies explicit --- .github/workflows/phone-armv7-runtime.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/phone-armv7-runtime.yml b/.github/workflows/phone-armv7-runtime.yml index d833e9f..c485f37 100644 --- a/.github/workflows/phone-armv7-runtime.yml +++ b/.github/workflows/phone-armv7-runtime.yml @@ -59,10 +59,14 @@ jobs: distribution: temurin java-version: '17' - - name: Install Android SDK and pinned NDK + - name: Set up Android SDK uses: android-actions/setup-android@v4 with: - packages: 'ndk;27.3.13750724' + packages: '' + + - name: Install pinned Android NDK + shell: bash + run: sdkmanager "ndk;$ANDROID_NDK_VERSION" - name: Extract generated native source bundle id: native_source @@ -81,8 +85,9 @@ jobs: env: GREASE_SOURCE_SHA: ${{ steps.source_pin.outputs.revision }} GREASE_PACKAGING_SHA: ${{ github.sha }} - ANDROID_NDK_HOME: ${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }} run: | + export ANDROID_NDK_HOME="$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION" + test -d "$ANDROID_NDK_HOME" bash phone/build-armv7-runtime.sh \ "${{ steps.native_source.outputs.dir }}" \ build/package @@ -90,7 +95,9 @@ jobs: - name: Verify pinned NDK receipt shell: bash run: | - grep -Fx "ndk_revision\t$ANDROID_NDK_VERSION" build/package/receipts/build.tsv + awk -F '\t' -v expected="$ANDROID_NDK_VERSION" \ + '$1 == "ndk_revision" { found = 1; if ($2 != expected) exit 1 } END { if (!found) exit 1 }' \ + build/package/receipts/build.tsv - name: Package deterministic phone artifact id: package From 2d5968818444f6bb08b27e544b53985fe8d4eeac Mon Sep 17 00:00:00 2001 From: i Date: Fri, 11 Sep 2026 12:30:14 -0400 Subject: [PATCH 16/23] ci: keep fork PRs off Debian receipt runner --- .github/workflows/grease-receipt.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/grease-receipt.yml b/.github/workflows/grease-receipt.yml index 04258c9..0615bdd 100644 --- a/.github/workflows/grease-receipt.yml +++ b/.github/workflows/grease-receipt.yml @@ -7,6 +7,7 @@ on: jobs: cpp-spec: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: [self-hosted, linux, debian] timeout-minutes: 120 steps: From 958b342d4abc434bd15ea44e7fbdc5c66e5d7fcf Mon Sep 17 00:00:00 2001 From: i Date: Fri, 11 Sep 2026 12:30:33 -0400 Subject: [PATCH 17/23] ci: keep fork PRs off Debian ARMv7 runner --- .github/workflows/phone-armv7-runtime.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/phone-armv7-runtime.yml b/.github/workflows/phone-armv7-runtime.yml index c485f37..40614d9 100644 --- a/.github/workflows/phone-armv7-runtime.yml +++ b/.github/workflows/phone-armv7-runtime.yml @@ -14,6 +14,7 @@ permissions: jobs: build-armv7-runtime: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: [self-hosted, linux, debian] timeout-minutes: 120 env: From aee2364fad60268a9dc82767c28e317515c588c5 Mon Sep 17 00:00:00 2001 From: i Date: Mon, 14 Sep 2026 09:49:30 -0400 Subject: [PATCH 18/23] ci: return native libc receipt to hosted Ubuntu --- .github/workflows/grease-receipt.yml | 61 +++++++++++++++++++--------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/.github/workflows/grease-receipt.yml b/.github/workflows/grease-receipt.yml index 0615bdd..72a67b8 100644 --- a/.github/workflows/grease-receipt.yml +++ b/.github/workflows/grease-receipt.yml @@ -5,55 +5,76 @@ on: push: branches: [main] +permissions: + contents: read + jobs: cpp-spec: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: [self-hosted, linux, debian] + runs-on: ubuntu-24.04 timeout-minutes: 120 + env: + REPO_ROOT: ${{ github.workspace }}/source-worktree steps: - - name: Check out Grease and pinned Oils source + - name: Check out exact Grease head and pinned Oils source uses: actions/checkout@v4 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} submodules: recursive + persist-credentials: false - - name: Verify pinned source and Debian host + - name: Verify and record the source pin + id: source_pin shell: bash run: | revision=$(git rev-parse HEAD:source) test "$(git -C source rev-parse HEAD)" = "$revision" . /etc/os-release - test "$ID" = debian - echo "Grease source pin: $revision" - echo "Debian: $PRETTY_NAME" + test "$ID" = ubuntu + test "$(uname -m)" = x86_64 + echo "revision=$revision" >> "$GITHUB_OUTPUT" + echo "Grease: $(git rev-parse HEAD)" + echo "Oils source: $revision" + echo "Ubuntu: $PRETTY_NAME" + + # The inherited container harness mounts an Oils worktree by itself. + # A normal submodule has a .git file whose target is outside that mount, + # so give the harness an equivalent standalone checkout of the gitlink. + - name: Check out the pinned source as a standalone worktree + uses: actions/checkout@v4 + with: + repository: isomorphisms/oils + ref: ${{ steps.source_pin.outputs.revision }} + path: source-worktree + persist-credentials: false - - name: Prepare native Debian Oils environment - working-directory: source + - name: Verify standalone source revision shell: bash - run: | - sudo sysctl vm.mmap_rnd_bits=28 - soil/worker.sh JOB-dev-setup-debian + run: test "$(git -C source-worktree rev-parse HEAD)" = "${{ steps.source_pin.outputs.revision }}" + + - name: Fix kernel mmap rnd bits + run: sudo sysctl vm.mmap_rnd_bits=28 - - name: Run inherited Oils cpp-spec workload natively - working-directory: source + - name: Run and verify inherited Oils cpp-spec oracle + working-directory: source-worktree shell: bash run: | - . build/dev-shell.sh - soil/worker.sh JOB-cpp-spec + podman --version + soil/github-actions.sh run-job cpp-spec podman + test "$(cat _tmp/soil/commit-hash.txt)" = "${{ steps.source_pin.outputs.revision }}" soil/host-shim.sh did-all-succeed cpp-spec - name: Build and run focused libc native tests - working-directory: source + working-directory: source-worktree shell: bash run: | - . build/dev-shell.sh + sudo chown -R "$(id -u):$(id -g)" _build _bin ninja _bin/cxx-dbg/cpp/libc_test _bin/cxx-dbg/cpp/libc_test -t grease_native_ - name: Exercise mapped IB index from Grease - working-directory: source + working-directory: source-worktree shell: bash run: | - . build/dev-shell.sh ninja _bin/cxx-asan+gcalways/ysh index_file=$(mktemp) trap 'rm -f "$index_file"' EXIT From 30d14fb5017970e92fac5e6d56b4bee7bf2966a5 Mon Sep 17 00:00:00 2001 From: i Date: Mon, 14 Sep 2026 09:49:56 -0400 Subject: [PATCH 19/23] ci: return ARMv7 build to hosted Ubuntu --- .github/workflows/phone-armv7-runtime.yml | 46 ++++++++++++++--------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/.github/workflows/phone-armv7-runtime.yml b/.github/workflows/phone-armv7-runtime.yml index 40614d9..1d3d816 100644 --- a/.github/workflows/phone-armv7-runtime.yml +++ b/.github/workflows/phone-armv7-runtime.yml @@ -14,43 +14,55 @@ permissions: jobs: build-armv7-runtime: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: [self-hosted, linux, debian] + runs-on: ubuntu-24.04 timeout-minutes: 120 env: + REPO_ROOT: ${{ github.workspace }}/source-worktree ANDROID_API: '28' ANDROID_ABI: armeabi-v7a ANDROID_NDK_VERSION: '27.3.13750724' steps: - - name: Check out Grease and pinned Oils source + - name: Check out exact Grease head and pinned Oils source uses: actions/checkout@v4 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} submodules: recursive + persist-credentials: false - - name: Verify exact source pin and Debian host + - name: Verify and record exact source pin id: source_pin shell: bash run: | revision=$(git rev-parse HEAD:source) test "$(git -C source rev-parse HEAD)" = "$revision" . /etc/os-release - test "$ID" = debian + test "$ID" = ubuntu + test "$(uname -m)" = x86_64 echo "revision=$revision" >> "$GITHUB_OUTPUT" - echo "Debian: $PRETTY_NAME" + echo "Ubuntu: $PRETTY_NAME" - - name: Prepare native Debian Oils environment - working-directory: source + - name: Check out pinned source as standalone worktree + uses: actions/checkout@v4 + with: + repository: isomorphisms/oils + ref: ${{ steps.source_pin.outputs.revision }} + path: source-worktree + persist-credentials: false + + - name: Verify standalone source revision shell: bash - run: | - sudo sysctl vm.mmap_rnd_bits=28 - soil/worker.sh JOB-dev-setup-debian + run: test "$(git -C source-worktree rev-parse HEAD)" = "${{ steps.source_pin.outputs.revision }}" + + - name: Fix kernel mmap rnd bits + run: sudo sysctl vm.mmap_rnd_bits=28 - - name: Generate native C++ source bundle with Oils cpp-tarball job - working-directory: source + - name: Generate native C++ source bundle from current Grease + working-directory: source-worktree shell: bash run: | - . build/dev-shell.sh - soil/worker.sh JOB-cpp-tarball + podman --version + soil/github-actions.sh run-job cpp-tarball podman + test "$(cat _tmp/soil/commit-hash.txt)" = "${{ steps.source_pin.outputs.revision }}" soil/host-shim.sh did-all-succeed cpp-tarball test -f _release/oils-for-unix.tar @@ -75,7 +87,7 @@ jobs: run: | rm -rf build/native-source build/package mkdir -p build/native-source build/package - tar -xf source/_release/oils-for-unix.tar -C build/native-source + tar -xf source-worktree/_release/oils-for-unix.tar -C build/native-source source_dir=$(find build/native-source -mindepth 1 -maxdepth 1 -type d -name 'oils-for-unix-*' -print -quit) test -n "$source_dir" test -x "$source_dir/_build/oils.sh" @@ -146,7 +158,7 @@ jobs: Android NDK: $ANDROID_NDK_VERSION Artifact SHA-256: $ARTIFACT_SHA256 - Debian cloud build evidence only. Physical-phone execution remains pending until Phone Cat Food installs and exercises this exact artifact. + GitHub-hosted Ubuntu build evidence only. Physical-phone execution remains pending until Phone Cat Food installs and exercises this exact artifact. EOF gh release create "$tag" \ build/grease-armv7-termux.tar.gz \ From 8e5a5629033f0fc628937e2c6a4c58142b616237 Mon Sep 17 00:00:00 2001 From: i Date: Mon, 14 Sep 2026 12:19:00 -0400 Subject: [PATCH 20/23] Fix ARM ELF receipt checks under pipefail --- phone/build-armv7-runtime.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/phone/build-armv7-runtime.sh b/phone/build-armv7-runtime.sh index 29dca55..12db60d 100644 --- a/phone/build-armv7-runtime.sh +++ b/phone/build-armv7-runtime.sh @@ -106,12 +106,14 @@ runtime=_bin/android-armv7-clang++-opt-sh/oils-for-unix.stripped exit 3 } -"$readelf" -h "$runtime" | grep -Eq 'Machine:[[:space:]]+ARM' || { +elf_header=$("$readelf" -h "$runtime") +grep -Eq 'Machine:[[:space:]]+ARM' <<<"$elf_header" || { echo 'Grease phone runtime is not an ARM ELF binary' >&2 - "$readelf" -h "$runtime" >&2 + printf '%s\n' "$elf_header" >&2 exit 3 } -if "$readelf" -d "$runtime" 2>/dev/null | grep -q 'libc++_shared\.so'; then +elf_dynamic=$("$readelf" -d "$runtime" 2>/dev/null || true) +if grep -q 'libc++_shared\.so' <<<"$elf_dynamic"; then echo 'Grease phone runtime unexpectedly depends on libc++_shared.so' >&2 exit 3 fi From 9985a12665e3d84db0a94619f78278cba620aabd Mon Sep 17 00:00:00 2001 From: i Date: Mon, 14 Sep 2026 12:27:22 -0400 Subject: [PATCH 21/23] Provision readline for hosted libc receipt --- .github/workflows/grease-receipt.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/grease-receipt.yml b/.github/workflows/grease-receipt.yml index 72a67b8..e5fee37 100644 --- a/.github/workflows/grease-receipt.yml +++ b/.github/workflows/grease-receipt.yml @@ -63,6 +63,11 @@ jobs: test "$(cat _tmp/soil/commit-hash.txt)" = "${{ steps.source_pin.outputs.revision }}" soil/host-shim.sh did-all-succeed cpp-spec + - name: Install host dependency for focused native test + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends libreadline-dev + - name: Build and run focused libc native tests working-directory: source-worktree shell: bash From 758f9ad8af42658ef3a047b7f98f023b31d5327a Mon Sep 17 00:00:00 2001 From: i Date: Mon, 14 Sep 2026 12:35:08 -0400 Subject: [PATCH 22/23] Reuse cpp-spec YSH for mapped IB receipt --- .github/workflows/grease-receipt.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/grease-receipt.yml b/.github/workflows/grease-receipt.yml index e5fee37..c0d3570 100644 --- a/.github/workflows/grease-receipt.yml +++ b/.github/workflows/grease-receipt.yml @@ -80,11 +80,11 @@ jobs: working-directory: source-worktree shell: bash run: | - ninja _bin/cxx-asan+gcalways/ysh + test -x _bin/cxx-asan/ysh index_file=$(mktemp) trap 'rm -f "$index_file"' EXIT truncate -s 4096 "$index_file" printf fragment | dd of="$index_file" conv=notrunc status=none - _bin/cxx-asan+gcalways/ysh \ + _bin/cxx-asan/ysh \ "$GITHUB_WORKSPACE/examples/ib-mapped-index.ysh" "$index_file" test "$(head -c 8 "$index_file")" = pensieve From ef9c74055c8079a7027771c6b869085ec7df3e3d Mon Sep 17 00:00:00 2001 From: i Date: Wed, 16 Sep 2026 11:30:36 -0400 Subject: [PATCH 23/23] Reconcile ARM runtime verifier with base --- phone/build-armv7-runtime.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/phone/build-armv7-runtime.sh b/phone/build-armv7-runtime.sh index 12db60d..66b66a4 100644 --- a/phone/build-armv7-runtime.sh +++ b/phone/build-armv7-runtime.sh @@ -106,14 +106,12 @@ runtime=_bin/android-armv7-clang++-opt-sh/oils-for-unix.stripped exit 3 } -elf_header=$("$readelf" -h "$runtime") -grep -Eq 'Machine:[[:space:]]+ARM' <<<"$elf_header" || { +"$readelf" -h "$runtime" | grep -E 'Machine:[[:space:]]+ARM' >/dev/null || { echo 'Grease phone runtime is not an ARM ELF binary' >&2 - printf '%s\n' "$elf_header" >&2 + "$readelf" -h "$runtime" >&2 exit 3 } -elf_dynamic=$("$readelf" -d "$runtime" 2>/dev/null || true) -if grep -q 'libc++_shared\.so' <<<"$elf_dynamic"; then +if "$readelf" -d "$runtime" 2>/dev/null | grep 'libc++_shared\.so' >/dev/null; then echo 'Grease phone runtime unexpectedly depends on libc++_shared.so' >&2 exit 3 fi