Repository navigation
fix(install): replace a stale backup folder in the signed-release installer - #560
Merged
Merged
Conversation
…stall' into fix/signed-install-stale-backup
…fix/signed-install-stale-backup
…older present The install must replace a backup folder left by an earlier run, restore the previous files on a copy or finalize failure, and leave no backup behind. Fails on the current code, which stops with "Could not backup existing app folder before replacement." Assisted-by: ClaudeCode:claude-opus-5-5
…taller A .appversion-backup folder left by an earlier interrupted run made every later signed-release install fail with "Could not backup existing app folder before replacement." The installer now removes it before moving the live folder aside, as the external-release installer already does. The golden is re-recorded for the one scenario that changes: "old backup present, dry run false" now installs. Assisted-by: ClaudeCode:claude-opus-5-5
The signed-release and external-release installers carried identical copies of the backup, copy, restore and cleanup helpers. They now use one ReplacesAppFolder trait, so the stale-backup handling cannot drift apart again. Behaviour is unchanged: both goldens pass as recorded. Assisted-by: ClaudeCode:claude-opus-5-5
Marks the $token parameter #[\SensitiveParameter], on its own line, so a trace through validate() shows a SensitiveParameterValue instead of the token. A test pins the attribute. Assisted-by: ClaudeCode:claude-opus-5-5
openssl_verify() returns -1 when it cannot check a signature at all. The test feeds bytes that are no DER signature under an EC certificate and expects "Release signature verification failed." with the old files in place, so a check that only rejects 0 (or any falsy value) fails it. Assisted-by: ClaudeCode:claude-opus-5-5
…tall-stale-backup
rubenvdlinde
marked this pull request as ready for review
November 2, 2026 09:49
Contributor
Quality Report — ConductionNL/versioniq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| composer | ✅ | ✅ 32/32 | |||
| npm | ❌ | ✅ 302/302 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 762/762 · 6 skipped (8 legs) | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development. |
||||
| Hydra gates | ✅ |
Quality workflow — 2026-11-02 11:09 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The signed-release installer (
SelectedReleaseInstallerService) failed every install with "Could not backup existing app folder before replacement." whenever a<app>.appversion-backupfolder was left behind by an earlier interrupted run:rename()onto a non-empty folder fails. The external-release installer already removes such a stale backup first. Decision of 3 Nov: the signed installer does the same.Five commits:
test(install): a regression test with a stale backup folder present. On the old code all three cases fail with the backup error. On the new code: the install succeeds and no backup folder remains; a copy failure and a finalize failure both restore the previous files (not the stale ones), reportrestore: clean, and leave no backup folder.fix(install): the stale backup is removed before the live folder is moved aside. The golden is re-recorded for the one scenario that changes:old backup present, dry run falsenow installs instead of failing. Every other scenario is byte-identical.refactor(install): both installers carried identical copies of seven filesystem helpers (backupExisting,revertCopy,markFilesystemUpdated,restoreFromBackup,findSingleDirectory,copyRecursive,rmdirr). They now use oneReplacesAppFoldertrait, so the stale-backup handling cannot drift apart again. No behaviour change: both goldens pass unchanged.fix(pat):PatValidator::validatemarks$tokenas#[\SensitiveParameter](on its own line), so a stack trace through it does not carry the token. A test pins the attribute.test(install): pins the signature check's!== 1.openssl_verify()returns -1 when it cannot check a signature at all; the test feeds bytes that are no DER signature under an EC certificate and expects the install to stop with the old files in place.Review requested (install path): this changes what the signed installer does with an existing folder on disk, and moves the shared backup/restore helpers into a trait.
Verification (local)
prlimit --as=1.5G timeout 120), no-op control survives in every run:ReplacesAppFolder::backupExisting: removed, made unreachable, moved after the rename, restore skipped: 4 of 4 killed (selected-release tests); removed and moved: 2 of 2 killed (external-release golden).!== 1turned into=== 0,== false,!$result: 3 of 3 killed with the new test, 0 of 3 killed without it (the gap RW27 found).beStrictAboutCoverageMetadata): 71 tests OK, no risky (the trait is listed in@coversof both goldens).--bootstrap tests/bootstrap-unit-only.phpconfig): 768 tests, 2,745 assertions, OK (exit 0).lib/Repair/RemoveRetiredCronJobs.phpand its test) are reported fixable on development too.SIG numbers (touched files, gate 122 unit reader)
The params and size >30 shares rise only because 91 duplicated LOC of small helpers left the denominator: no unit gained a parameter, and the one 3-parameter unit touched (
backupExistingFolder) went from 17 to 10 LOC. About 100 LOC of literal duplication between the two installers is gone.Also green:
hydra-gates --scope-to-diff --base origin/development(23 of 23 applicable gates, exit 0), gate 119 count 0 on both refs.Inherited, not changed: php-cs-fixer reports
lib/Repair/RemoveRetiredCronJobs.phpandtests/unit/Repair/RemoveRetiredCronJobsTest.php; nothing on the touched lines.AI assistance: prepared with Claude Code (claude-opus-5-5); commits carry an
Assisted-bytrailer.