Skip to content

fix(deploy): verify iOS BEAM overrides - #50

Merged
GenericJam merged 2 commits into
masterfrom
fix/ios-push-erlang-beams
Aug 28, 2026
Merged

fix(deploy): verify iOS BEAM overrides#50
GenericJam merged 2 commits into
masterfrom
fix/ios-push-erlang-beams

Conversation

@GenericJam

Copy link
Copy Markdown
Owner

Summary

  • discover runtime BEAMs from the active Mix build and compile paths without wildcard path hazards
  • source project-app BEAMs only from the active compile output, excluding stale build output and dev-only dependencies
  • replace physical iOS override directories exactly, then verify the Erlang bootstrap byte-for-byte before restart

Why

Physical iOS prefers Documents/otp/ over the complete signed bundle. An incomplete or stale override can therefore shadow a valid native install and fail at boot with undef for :start/0. A successful devicectl transfer previously had no completeness postcondition.

This change makes the active compiler output the source of truth and proves active bootstrap -> staging -> remote bootstrap identity. A failed or mismatched transfer returns an error and does not restart the app.

Verification

  • 40 focused deployer and hot-push tests passed
  • 2,146 non-device tests passed; 10 excluded
  • format check passed
  • compile with warnings as errors passed
  • Credo strict passed
  • Erlang formatting passed
  • git diff check passed
  • two adversarial review passes completed; all Important findings fixed

No Hex package was published and no device was used for this branch. Physical verification can be performed from the PR before release.

GenericJam and others added 2 commits August 28, 2026 00:03
…gression

Two adversarial passes plus a device session on a physical iPhone SE. The
mechanism in this PR works — I deployed through it, pulled Documents/otp/
<app>/<app>.beam back off the device with an independent devicectl call and
confirmed it byte-identical to the compile output, and the app booted clean.
These are the fixes around it.

BLOCKER 1 — every deploy, every platform. runtime_lib_names/0 stopped seeding
the expansion with project_app, so the project's own .app was never read. A dep
declared `runtime: false` and opted back in via `extra_applications:` — the
documented idiom, where extra_applications deliberately overrides the flag — was
silently dropped from the push. Reproduced against a real fixture project:
master resolves ["lazy_lib", "proj"], this PR resolves ["proj"]. On device the
app boots and dies with undef on first use — the same failure class this PR
exists to eliminate, relocated from the bootstrap to a dependency.

The removal was not gratuitous: seeding also leaked only: :dev deps into the
runtime set, contradicting the moduledoc. So expand first, then subtract the
deps we know are dev-only, keeping anything the project re-declares in
extra_applications. Fixture now resolves ["lazy_lib", "proj"] again with
mob_dev (only: :dev) correctly absent.

Worth noting one trap in the fix itself: extra_applications lives on the project
module's application/0 callback, NOT in Mix.Project.config/0. Reading it from
config yields [] and silently subtracts the very libs being protected — which is
what my first attempt did, caught by the fixture.

BLOCKER 2 — the destructive copy has no rollback. --remove-existing-content
makes a mid-transfer failure worse than the bug being fixed: mob_beam.m:317
prefers Documents/otp/<app> on directory EXISTENCE alone, so a partial override
shadows the complete signed bundle and the next Springboard launch dies with
undef, even though the deploy correctly skipped the restart. There is no clean
repair — devicectl has no delete verb, and pushing an empty directory leaves it
present and therefore still preferred, which is strictly worse. So the failure
path now states the override is incomplete and names the two recoveries that do
work. Verified mob_beam.m and the devicectl verb list directly.

Also:
- Mix.Project.compile_path/0 raises at an umbrella root, where the old _build/dev
  wildcard degraded. Now raises mob's own "umbrellas not supported" message.
- The WiFi-only-device throw sat OUTSIDE the try, so it escaped deploy_ios/3 and
  deploy_all/1 (neither catches) and aborted the whole run with a raw ** (throw),
  losing the summary for devices that already succeeded. Returns now.
- Staging dirs are PID-qualified; unique_integer restarts low in a fresh VM, so
  two concurrent deploys could collide and one's cleanup delete the other's.
- validate_ios_override distinguishes a MISSING staged bootstrap from a
  MISMATCHED one; the old message wrapped enoent inside "does not match active
  compile output" and pointed at the wrong file. The test asserted that
  misleading string, so it was pinning the confusion.
- runtime_lib_names/0 now has coverage. It is the function that decides the push
  set and had none — every existing test hand-builds the MapSet it produces, so
  this regression stayed green across 2146 tests.

Verified: 2148 tests, format, compile --warnings-as-errors, credo --strict, and
a real deploy to the physical iPhone that boots and renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@GenericJam
GenericJam merged commit f5e5f74 into master Aug 28, 2026
3 checks passed
@GenericJam
GenericJam deleted the fix/ios-push-erlang-beams branch August 28, 2026 07:18
GenericJam added a commit that referenced this pull request Aug 28, 2026
`mix test` writes an `android/` scaffold into the repo root — mob_dev's
own generators run against cwd, and the suite doesn't redirect them to a
tmp dir. Those 8 files (including a 63KB gradle-wrapper.jar and ~98KB of
logo PNGs) were untracked-but-not-ignored, so #50 swept them in while
adding nothing Android-related.

They don't reach Hex (the package ships lib/priv only, verified with
`mix hex.build --unpack`), and nothing in this repo reads them — every
`android/...` path in lib/ resolves against a *target app's* root, not
ours. Removed and ignored.

The ignore stops recommits; it doesn't fix the leak. The suite should
generate into a tmp dir instead of cwd — left for its own change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
GenericJam added a commit that referenced this pull request Aug 28, 2026
…eet wiring

Deploy fixes (#50): BEAM discovery now follows Mix.Project.build_path/0
and compile_path/0 rather than a hardcoded _build/dev, so a non-dev
MIX_ENV or custom :build_path is honoured — projects on
build_per_environment: false previously pushed no dependency BEAMs at
all. Physical-iOS overrides are replaced rather than merged and the
transfer is verified before restart. Also restores traversal of the
project's own .app so deps reachable only via extra_applications stop
being silently dropped, plus umbrella, WiFi-device and concurrent-staging
error handling.

Doctor (#48): warns when a project still carries the pre-MOB-104 sheet
dismissal wiring, which delivers {:tap, tag} where the contract and iOS
deliver {:dismiss, tag}.

Release review of v0.6.27..HEAD: 2148 tests pass, format/credo --strict/
warnings-as-errors clean. Security scan exits 0; its 2 HIGH findings are
bundled-versions manifest drift in locally cached mob OTP tarballs,
untouched by this diff and not owned by this repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant