[PR #2260] [CLOSED] feat(process): let callers own the macOS helper module cache #5122

Closed
opened 2026-10-31 01:04:09 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2260
Author: @NIU-123370
Created: 10/12/2026
Status: ❌ Closed

Base: main ← Head: feat/darwin-helper-module-cache


📝 Commits (1)

  • f0804d2 feat(process): let callers own the macOS helper module cache

📊 Changes

4 files changed (+175 additions, -7 deletions)

View changed files

📝 docs/process-capture.md (+12 -0)
📝 src/process/DarwinProcessRunTokenReader.ts (+18 -3)
📝 src/process/ProcessOwnershipObservation.ts (+12 -4)
📝 tests/boundary/process/darwinProcessRunTokenReader.test.ts (+133 -0)

📄 Description

Summary

DarwinProcessRunTokenReader compiles the Swift process-ownership helper with xcrun swiftc. The compiler's intermediate objects are already pinned inside the per-run temporary root that REA removes afterwards, but the module cache is deliberately left to Swift's shared default: src/process/DarwinProcessRunTokenReader.ts deletes CLANG_MODULE_CACHE_PATH before invoking the compiler and passes no -module-cache-path.

A caller whose writes are confined to one directory therefore sees the compile fail inside the module cache and has no supported way to move it. This adds one opt-in setting for that directory and leaves the default exactly as it is.

Fixes #2218.

Problem and expected behavior

From the report, observed under an enforced write boundary:

xcrun: error: couldn't create cache file '<system-temp>/xcrun_db-<suffix>' (errno=Operation not permitted)
<unknown>:0: error: unable to open output file '<system-cache>/clang/ModuleCache/<id>/SwiftShims-<id>.pcm': 'Operation not permitted'
<macos-15.5-sdk>/usr/lib/swift/Swift.swiftmodule/<target>.swiftinterface:5:8: error: could not build Objective-C module 'SwiftShims'

Expected: a documented setting selects the helper's module-cache directory inside the caller's boundary, the default reusable-cache behaviour stays unchanged, and REA never deletes what the caller owns.

Change and scope

  • createDarwinProcessRunTokenReader accepts moduleCachePath. Unset or blank keeps today's argv and environment byte-for-byte. Set, REA creates the directory, inserts -module-cache-path <dir> directly after swiftc — the same spelling src/native/NativeUiHelperRuntime.ts:43 already uses — and exports CLANG_MODULE_CACHE_PATH=<dir> so the Objective-C header modules the helper builds land there too.
  • createSystemProcessOwnershipHost reads REA_SWIFTC_MODULE_CACHE_PATH from the host environment it is given (or an explicit darwinModuleCachePath) and passes it to the reader. Precedence is explicit option, then environment, then default.
  • docs/process-capture.md documents the setting, that the directory outlives each run, and the cold-cache cost of pointing it somewhere new.

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/process/DarwinProcessRunTokenReader.ts (runCompilation), src/process/ProcessOwnershipObservation.ts (createSystemProcessOwnershipHost).
  • CLI and MCP/tool-catalog contract: no schema change; no new tool, input, or output field, so no catalog or completion regeneration. The control is one environment variable read when the system ownership host is constructed.
  • Provider, bridge, target-format, or platform compatibility: the macOS Darwin ownership reader only. Windows and Linux paths are untouched.
  • Evidence, artifact, provenance, or reconstruction contract: unchanged. Ownership-token diagnostics, cleanup.unverified_processes, and the withheld-environment exemptions in ProcessOwnership.ts are not touched.
  • Process execution, authorization, cleanup, or containment impact: the compile gains two argv/env entries when opted in. Cleanup still removes only the per-run helper root; the caller's cache directory is never removed by REA. SIGINT interruption of swiftc, the cancellation retry path, and helper-temporary removal are unchanged.
  • Generated metadata, package, or installation impact: none.

Evidence and regression coverage

  • Tests added (4, all in tests/boundary/process/darwinProcessRunTokenReader.test.ts):
    • "forwards a caller-owned module cache and keeps it after the helper root is removed" — argv order (swiftc then the flag then the path), the child CLANG_MODULE_CACHE_PATH, the helper binary gone after close(), and the cache content still present.
    • "selects the caller module cache through REA_SWIFTC_MODULE_CACHE_PATH" — the environment reaches the compiler through the system host.
    • "keeps the shared module cache for the system host without the setting" — control arm: no flag, child CLANG_MODULE_CACHE_PATH null.
    • "compiles the helper against the caller cache with the host compiler" — real /usr/bin/xcrun, no fake; gated it.skipIf(!onDarwin).
  • Default behaviour is pinned by the existing test rather than edited: "uses the compiler-managed module cache instead of rebuilding system modules per REA process" still asserts not.toContain("-module-cache-path") and still passes, as do the two "ignores unusable inherited Swift module cache" arms that exercise the real compiler with an unusable inherited cache.
  • Real-compiler observation (macOS 26.x arm64, Xcode command line tools): an opted-in empty directory received Foundation-*.swiftmodule, Combine-*.swiftmodule, Darwin-*.swiftmodule … at the top level plus hash-named subdirectories holding SwiftShims-*.pcm, xlocale-*.pcm etc. (29 entries). Cold compile into that private cache took 25.1 s inside the focused suite; the same suite's default-path test ran 0.6–1.0 s. That contrast is not a "default is 25x faster" claim — the shared cache on this host was already warm, so a first-ever default run would pay comparable cost.
  • Mutation checks (each arm applied, focused tests re-run, then reverted):
Arm Expected Result
flag renamed -module-cache-path-mut both opt-in tests fail killed (2 failed)
child CLANG_MODULE_CACHE_PATH not set the unit test fails, host test unaffected killed (1 failed)
host ignores the environment variable env-selection test fails killed (1 failed)
close() also removes the caller cache persistence assertion fails killed (1 failed)
REA mkdirs a path nested under the helper root instead — survived; that arm mis-aimed (the fake recreated the directory from the argv value, so nothing observed changed). Not evidence that cleanup is safe; the load-bearing arm for that claim is the close() one above.
  • Remaining proof gaps (stated, not hidden):
    • xcrun's own lookup cache is not covered. I could not reproduce its write on this host: with TMPDIR redirected to an empty directory, xcrun --find swiftc, xcrun --find clang, and xcrun --sdk macosx --find clang produced nothing there, and getconf DARWIN_USER_CACHE_DIR still held 404 entries before and after with no xcrun_db* file found. So the second half of #2218's request (containment for xcrun_db writes) stays open and needs reproduction conditions from the reporter.
    • This is an environment variable, not a --flag. #2218 asked for "a supported CLI configuration". systemProcessOwnershipHost is constructed at module scope (src/process/ProcessOwnershipObservation.ts:432) during import, before parseConfig runs (src/main.ts:53), so a CLI-parsed value cannot reach it without restructuring that singleton. I took the smaller change and am flagging the gap rather than implying the CLI surface exists.
    • The real-compiler test does not run on non-Darwin shards; the argv/env behaviour is covered there by the fake compiler instead.
    • Overlap with open PR #2240 (fix(process): expose Darwin swiftc compile deadline via env): it edits the same options object at DarwinProcessRunTokenReader.ts:243 and ProcessOwnershipObservation.ts:222, so expect a textual conflict on those lines. Different knob; neither reverts the other, and #1139's reusable-default is preserved here too.

Validation performed

  • npm run check:fast (turbo typecheck + lint): 2/2 successful.

  • ./node_modules/.bin/oxfmt --check on the four changed files: all correct format. (Note: format:check here is oxfmt, not Prettier.)

  • npx vitest run tests/boundary/process/darwinProcessRunTokenReader.test.ts: 26 passed (22 pre-existing + 4 new).

  • npx vitest run src/process/ProcessOwnership.test.ts tests/boundary/process/ownedCommand.test.ts: 26 passed.

  • Not run in this worktree: npm run build and the package/shard lanes. Typecheck and lint passed locally on Node 24.18.0; CI covers build, coverage shards, and the darwin package baselines.

  • Observed, derived, and inferred claims remain distinguishable.

  • Artifact identity, source provenance, and failed attempts remain preserved.

  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Empty or absent value keeps the shared default; no caller today sets the new variable.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS host compiler run described above. No Hopper, Ghidra, Windows, or browser involvement.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: the value is any path the REA process can create; this adds no privilege, executes nothing new beyond the existing swiftc call, and never reads the directory back as trusted input. Pointing it at a shared location has the same concurrency exposure as Swift's own default cache, which is why the default stays untouched.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/2260 **Author:** [@NIU-123370](https://github.com/NIU-123370) **Created:** 10/12/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `feat/darwin-helper-module-cache` --- ### 📝 Commits (1) - [`f0804d2`](https://github.com/morluto/rea/commit/f0804d2e20a740bc347891910244953657c579af) feat(process): let callers own the macOS helper module cache ### 📊 Changes **4 files changed** (+175 additions, -7 deletions) <details> <summary>View changed files</summary> 📝 `docs/process-capture.md` (+12 -0) 📝 `src/process/DarwinProcessRunTokenReader.ts` (+18 -3) 📝 `src/process/ProcessOwnershipObservation.ts` (+12 -4) 📝 `tests/boundary/process/darwinProcessRunTokenReader.test.ts` (+133 -0) </details> ### 📄 Description ## Summary `DarwinProcessRunTokenReader` compiles the Swift process-ownership helper with `xcrun swiftc`. The compiler's intermediate objects are already pinned inside the per-run temporary root that REA removes afterwards, but the module cache is deliberately left to Swift's shared default: `src/process/DarwinProcessRunTokenReader.ts` deletes `CLANG_MODULE_CACHE_PATH` before invoking the compiler and passes no `-module-cache-path`. A caller whose writes are confined to one directory therefore sees the compile fail inside the module cache and has no supported way to move it. This adds one opt-in setting for that directory and leaves the default exactly as it is. Fixes #2218. ## Problem and expected behavior From the report, observed under an enforced write boundary: ```text xcrun: error: couldn't create cache file '<system-temp>/xcrun_db-<suffix>' (errno=Operation not permitted) <unknown>:0: error: unable to open output file '<system-cache>/clang/ModuleCache/<id>/SwiftShims-<id>.pcm': 'Operation not permitted' <macos-15.5-sdk>/usr/lib/swift/Swift.swiftmodule/<target>.swiftinterface:5:8: error: could not build Objective-C module 'SwiftShims' ``` Expected: a documented setting selects the helper's module-cache directory inside the caller's boundary, the default reusable-cache behaviour stays unchanged, and REA never deletes what the caller owns. ## Change and scope - `createDarwinProcessRunTokenReader` accepts `moduleCachePath`. Unset or blank keeps today's argv and environment byte-for-byte. Set, REA creates the directory, inserts `-module-cache-path <dir>` directly after `swiftc` — the same spelling `src/native/NativeUiHelperRuntime.ts:43` already uses — and exports `CLANG_MODULE_CACHE_PATH=<dir>` so the Objective-C header modules the helper builds land there too. - `createSystemProcessOwnershipHost` reads `REA_SWIFTC_MODULE_CACHE_PATH` from the host environment it is given (or an explicit `darwinModuleCachePath`) and passes it to the reader. Precedence is explicit option, then environment, then default. - `docs/process-capture.md` documents the setting, that the directory outlives each run, and the cold-cache cost of pointing it somewhere new. ## Contract and boundary impact - Semantic owner and earliest changed stage: `src/process/DarwinProcessRunTokenReader.ts` (`runCompilation`), `src/process/ProcessOwnershipObservation.ts` (`createSystemProcessOwnershipHost`). - CLI and MCP/tool-catalog contract: no schema change; no new tool, input, or output field, so no catalog or completion regeneration. The control is one environment variable read when the system ownership host is constructed. - Provider, bridge, target-format, or platform compatibility: the macOS Darwin ownership reader only. Windows and Linux paths are untouched. - Evidence, artifact, provenance, or reconstruction contract: unchanged. Ownership-token diagnostics, `cleanup.unverified_processes`, and the withheld-environment exemptions in `ProcessOwnership.ts` are not touched. - Process execution, authorization, cleanup, or containment impact: the compile gains two argv/env entries when opted in. Cleanup still removes only the per-run helper root; the caller's cache directory is never removed by REA. `SIGINT` interruption of `swiftc`, the cancellation retry path, and helper-temporary removal are unchanged. - Generated metadata, package, or installation impact: none. ## Evidence and regression coverage - Tests added (4, all in `tests/boundary/process/darwinProcessRunTokenReader.test.ts`): - "forwards a caller-owned module cache and keeps it after the helper root is removed" — argv order (`swiftc` then the flag then the path), the child `CLANG_MODULE_CACHE_PATH`, the helper binary gone after `close()`, and the cache content still present. - "selects the caller module cache through `REA_SWIFTC_MODULE_CACHE_PATH`" — the environment reaches the compiler through the system host. - "keeps the shared module cache for the system host without the setting" — control arm: no flag, child `CLANG_MODULE_CACHE_PATH` null. - "compiles the helper against the caller cache with the host compiler" — real `/usr/bin/xcrun`, no fake; gated `it.skipIf(!onDarwin)`. - Default behaviour is **pinned by the existing test rather than edited**: "uses the compiler-managed module cache instead of rebuilding system modules per REA process" still asserts `not.toContain("-module-cache-path")` and still passes, as do the two "ignores unusable inherited Swift module cache" arms that exercise the real compiler with an unusable inherited cache. - Real-compiler observation (macOS 26.x arm64, Xcode command line tools): an opted-in empty directory received `Foundation-*.swiftmodule`, `Combine-*.swiftmodule`, `Darwin-*.swiftmodule` … at the top level plus hash-named subdirectories holding `SwiftShims-*.pcm`, `xlocale-*.pcm` etc. (29 entries). Cold compile into that private cache took 25.1 s inside the focused suite; the same suite's default-path test ran 0.6–1.0 s. **That contrast is not a "default is 25x faster" claim** — the shared cache on this host was already warm, so a first-ever default run would pay comparable cost. - Mutation checks (each arm applied, focused tests re-run, then reverted): | Arm | Expected | Result | | --- | --- | --- | | flag renamed `-module-cache-path-mut` | both opt-in tests fail | killed (2 failed) | | child `CLANG_MODULE_CACHE_PATH` not set | the unit test fails, host test unaffected | killed (1 failed) | | host ignores the environment variable | env-selection test fails | killed (1 failed) | | `close()` also removes the caller cache | persistence assertion fails | killed (1 failed) | | REA `mkdir`s a path nested under the helper root instead | — | **survived**; that arm mis-aimed (the fake recreated the directory from the argv value, so nothing observed changed). Not evidence that cleanup is safe; the load-bearing arm for that claim is the `close()` one above. | - Remaining proof gaps (stated, not hidden): - **`xcrun`'s own lookup cache is not covered.** I could not reproduce its write on this host: with `TMPDIR` redirected to an empty directory, `xcrun --find swiftc`, `xcrun --find clang`, and `xcrun --sdk macosx --find clang` produced nothing there, and `getconf DARWIN_USER_CACHE_DIR` still held 404 entries before and after with no `xcrun_db*` file found. So the second half of #2218's request (containment for `xcrun_db` writes) stays open and needs reproduction conditions from the reporter. - **This is an environment variable, not a `--flag`.** #2218 asked for "a supported CLI configuration". `systemProcessOwnershipHost` is constructed at module scope (`src/process/ProcessOwnershipObservation.ts:432`) during import, before `parseConfig` runs (`src/main.ts:53`), so a CLI-parsed value cannot reach it without restructuring that singleton. I took the smaller change and am flagging the gap rather than implying the CLI surface exists. - The real-compiler test does not run on non-Darwin shards; the argv/env behaviour is covered there by the fake compiler instead. - Overlap with open PR #2240 (`fix(process): expose Darwin swiftc compile deadline via env`): it edits the same options object at `DarwinProcessRunTokenReader.ts:243` and `ProcessOwnershipObservation.ts:222`, so expect a textual conflict on those lines. Different knob; neither reverts the other, and #1139's reusable-default is preserved here too. ## Validation performed - `npm run check:fast` (turbo typecheck + lint): 2/2 successful. - `./node_modules/.bin/oxfmt --check` on the four changed files: all correct format. (Note: `format:check` here is `oxfmt`, not Prettier.) - `npx vitest run tests/boundary/process/darwinProcessRunTokenReader.test.ts`: 26 passed (22 pre-existing + 4 new). - `npx vitest run src/process/ProcessOwnership.test.ts tests/boundary/process/ownedCommand.test.ts`: 26 passed. - Not run in this worktree: `npm run build` and the package/shard lanes. Typecheck and lint passed locally on Node 24.18.0; CI covers build, coverage shards, and the darwin package baselines. - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Empty or absent value keeps the shared default; no caller today sets the new variable. - Real Hopper/Ghidra, browser, or OS coverage: macOS host compiler run described above. No Hopper, Ghidra, Windows, or browser involvement. - Package or release metadata impact: none. - Security, privacy, process, or containment review: the value is any path the REA process can create; this adds no privilege, executes nothing new beyond the existing `swiftc` call, and never reads the directory back as trusted input. Pointing it at a shared location has the same concurrency exposure as Swift's own default cache, which is why the default stays untouched. ## Review checklist - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:04:09 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#5122
No description provided.