Repository navigation
Conversation
maralcbr
left a comment
There was a problem hiding this comment.
PR #29 review: Harden installer failure paths and add battle-test regressions
Reviewed at head 48ffd49 (main 6f1b40d merged in). Since my first pass at cbc7581, the only change is that merge, which brings in main's image-builder work from #33 and doesn't touch any file in this PR, so the notes below still apply line for line. CI green. On this head, the engine and overlay suites pass (38 + 111). With the three overlay sources and Engine/build-locked-engine.sh reverted to main, 15 overlay tests and 3 build-script subtests fail, so the new tests really cover the new guards. Swift passes on this head too: 564 XCTest (1 skipped) + 4 Swift Testing locally in debug, and 566 debug / 556 release (2 skipped each) in CI.
Verdict
Merge after small changes. Thanks, Naeem, this is careful work: the resize check is evaluated on raw geometry before anything is written, the repair preflight validates every member before a device is opened, and the unlock after a pre-submission failure is scoped to the steps that really run before the single execute.
Done since the first pass: the Swift suite now runs on the current head (above), so item 3 is covered. Updating the validation section of the PR body to match would still be nice.
Requested before merge
- Repair manifest compatibility (
Engine/overlay/src/omarchy_repair.py:465-474). Rewritten roles now needbefore/aftersizes within partition capacity and a multiple of 4096. That matches the rule for image members, but the manifest producer isn't in this repo, so a manifest we already ship with an unalignedexisting_contentsize would silently yield no repair candidate. Please check the producer (or a real shipped manifest) and add a test with one, and write the producer contract down indocs/battle-testing.md. - A clear error when a prepared resume doesn't match (
Engine/overlay/src/omarchy_asahi.py:540-549). Exact identity is right (the ±5 % discovery tolerance still applies when the partition is first found), but a mismatch currently falls to the unclassified failure inSources/OmarchyAppleInstaller/EngineFailureDiagnostics.swift:32-46. Please give it its own recoverable diagnostic that keeps the journal and says what was already changed, so a Mac isn't left in the locked state with no explanation.
Suggestions
InstallerSession.submit()setsisExecuting(Sources/OmarchyInstallerUXCore/InstallerSession.swift:524) beforewaitUntilPayloadVerified()(:529), and a failure there isn't wrapped as pre-submission. Install can't get there today, becausecanStartInstallationrequires a verified payload, so this is defensive only: either wrap it inInstallerPreSubmissionFailureor add a test that pins the invariant.InstallerPreSubmissionFailure(Sources/OmarchyAppleInstaller/InstallerExecutionCoordinator.swift:6) is a public type that unlocks retry. Keep the wrapped sites as narrow as they are now; a short test thatexecuteis never wrapped would guard that.- The window, Reduce Motion, accessibility, plan identity and copy-steps changes are welcome but unrelated to failure paths. Next time, a separate PR would make each easier to review.
Hardware
The body is right that physical qualification is still owed. Before a release that includes this, run interrupted stage one and Recovery resume on a lab Mac.
scottjones
left a comment
There was a problem hiding this comment.
Thanks, this is a solid hardening pass. It stops the engine build from replacing a good engine when a rebuild fails, checks the whole repair payload before writing any partition, confirms the resize source's geometry, re-checks a resumed install's prepared and installed state before the next step, and closes several prefetch and stager cancellation races. ./test/all passes with Python 3.12 (all seven new engine tests included), and swift test passes in debug and release.
Two things to fix before merge, plus a question about the engine lock:
- Quitting and closing the window are blocked for the whole payload download once credentials are entered, before anything reaches the helper (inline below).
- Wrapping the connection and ping errors hides the specific "installation service isn't responding" advice (inline below).
Engine/source-lock.jsonno longer matches the overlay. The SHA-256 ofomarchy_asahi.py,omarchy_repair.py,omarchy_stage1.pyand their three test files differs from the lock. That's 8 of 20 overlay files, against 2 onmain(the planner and its test). Is the lock refresh and locked-engine rebuild planned for this PR or a follow-up? None of the engine fixes here reach users until that happens.
|
|
||
| func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { | ||
| if Self.removalInProgress { | ||
| if Self.removalInProgress || Self.session?.isExecutionInProgress == true { |
There was a problem hiding this comment.
isExecutionInProgress just reads isExecuting, and submit() sets that at InstallerSession.swift:524, before waitUntilPayloadVerified() at line 529. So if the owner enters their password while the multi-GB payload is still downloading, or while it's paused on a metered network, the app can't be quit (this line beeps and returns .terminateCancel) and the close button is disabled (line 149). dismissCredentials() also refuses while isExecuting is true (InstallerSession.swift:467). Nothing has been sent to the helper yet, so the only way out is Force Quit.
Before this change, quitting at that point was allowed, and cancelPrefetchOnQuit (line 146) handled cleanup. Could the block depend on the request actually having been sent, for example a flag set just before environment.execute(...) at line 532? Or split isExecuting into "waiting for payload" and "submitted". A test for "quit allowed while waiting for the payload" would pin it down.
| liveSession?.cancelPrefetchOnQuit() | ||
| } | ||
| .disabled(showsRemoval) | ||
| .windowDismissBehavior( |
There was a problem hiding this comment.
Same condition as line 12: the close button stays disabled for the whole payload wait. Whatever fixes line 12 should apply here too.
| ) | ||
| // Ping sends no execution request, handoff or credentials. | ||
| try await submitter.ping() | ||
| } catch { |
There was a problem hiding this comment.
Wrapping every error from AuthenticatedEngineXPCSubmitter.init and ping() in InstallerPreSubmissionFailure makes the .helperUnresponsive handling at PlainLanguage.swift:554 unreachable. That error is only thrown by ping() (AuthenticatedEngineXPCSubmitter.swift:113 and :131), and PlainLanguage.swift:524 matches the wrapper first, returning the generic message without looking inside.
So when the helper isn't registered or isn't answering, the owner no longer sees "Run the downloaded package again, then reopen this app". They see "choose Check again to prepare and review a fresh plan", which can't fix a missing helper, so they can go round in circles.
Suggestion: let EngineXPCSubmissionError.helperUnresponsive (and any other specific connection error) through unwrapped, or have the InstallerPreSubmissionFailure case at PlainLanguage.swift:524 check preflight.underlying for errors that have their own message first. A test sending a .helperUnresponsive ping through the coordinator and checking the resulting FailureDisplay would cover it.
| try Task.checkCancellation() | ||
| try self.lock.withLock { | ||
| guard self.generation == generation else { throw CancellationError() } | ||
| try self.promote(staged.fileURL, to: canonical) |
There was a problem hiding this comment.
Optional nit: the generation check under the lock is the right fix. It does mean the file move happens while holding the lock. That's a quick rename within the same folder, so it's fine unless the destination can ever be on another volume, where moveItem becomes a copy.
|
Pushed the review-response fixes in Fixed
Local validation At source commit Remaining P2 gap: released repair-manifest compatibility Marcelo's request remains open. The real fixture comes from an explicitly non-release canary, so it cannot establish compatibility with production output. I verified public Stable/RC catalog signatures and metadata, inspected image-builder source, and checked the published engine archive against its release checksum. These establish fresh-install metadata and image alignment, but do not establish how repair manifests choose @maralcbr, could you provide the repair-manifest producer at a specific revision with representative input/output, or an unchanged manifest and the release that distributed it? That will let us test the actual existing/replacement content sizes against partition capacity and 4096 alignment rather than assume compatibility. For Scott's engine-lock question: the lock refresh and authenticated engine rebuild remain an explicit, separately reviewed release follow-up. The pinned archive does not yet contain these engine fixes. Interrupted stage-one and Recovery-resume qualification on authorized supported hardware also remains required before release; local tests and simulation do not replace it. |
Keep helper provisioning and its quit guard before payload verification while preserving failure-path hardening. Retain both plain-language test groups and cover helper setup across install and Recovery retry.
Main now checks every downstream engine input and build recipe against Engine/source-lock.json, so the engine overlay edits here failed the portable suite: seven locked files no longer matched the authenticated .28 archive's recorded digests. Refreshing only the hashes would claim .28 contains fixes it does not. Restore Engine/ to main and move the engine hardening, its tests, the canary repair manifest fixture and the repair research note to the codex/engine-hardening branch, to ship with a rebuilt engine. The app side of this PR is unchanged; it recognizes the prepared-resume diagnostic ahead of the engine that emits it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Interrupted installations could resume against a changed APFS partition, resize changed geometry before rejecting it, or partially overwrite a repair before discovering an invalid later image. This change rejects those conditions before the next disk operation and adds regression coverage for nine failure paths.
Changes
docs/battle-testing.md.Validation
Tested commit:
068f809786f2aa814386ae8fdd845f58a6456981, based on7a61e0a604ae812ddf9c1ff0f322ad301f6da7fb.Host: arm64 macOS 27.0 (26A428), Xcode 27.0 (27A266a), Swift 6.4, XcodeBuildMCP 2.7.0, Bash 5.3.15 and Python 3.14.7.
OMARCHY_PRIVATE_CATALOGwas not supplied.Limits and release boundary
Native visual, keyboard and VoiceOver behavior remains unverified: simulation processes launched, but UI automation could not attach. The complete image-builder suite stopped because GnuPG was unavailable. Three pre-existing weak-capture warnings remain in release compilation.
No physical installation, privileged helper registration, production signing or authenticated engine rebuild was performed. Source locks, trust roots and hardware restrictions—including the
apple,j614sblock—are unchanged. The overlay edits require a separately reviewed engine rebuild/repack and updated authenticated artifact identity before distribution; this PR is not an installation release.