Skip to content

Commit 08646d5

Browse files
BiosSystemclaude
andcommitted
Add the v3.4.0 release plan
The v3.1.0/v3.2.0 modernization plan was the last roadmap on file and v3.3.0 shipped without a successor. This adds one and points the old plan at it. v3.4.0 is scoped as recoverability and proof. The last three releases each added half a safety story: v3.2.0 added registry backup, v3.3.0 added desired-state verification, but nothing consumes the backup automatically and ten features cannot be verified at all. Every track in this plan extends infrastructure that already ships rather than adding a new subsystem. Tracks: - Automatic rollback on failed apply. The backup is already taken in Phase 1 before the apply phase, and Restore-RegistryBackupState is a working entry point, but InvokeChanges.ps1 has no reference to any restore function. The plan covers the run-scoped backup handle, the failure policy, and where the call belongs. - Verification coverage from 102/112 to 112/112, following the Appx-absence pattern DisableWidgets already uses in Test-FeatureApplied. - An integration suite for Tests/Integration, which has been empty since July, targeting apply, undo, and both rollback branches. - Field validation of the 24H2 DISM fallback, which was diagnosed from DISM's documented contract rather than observed failing. Records the sequencing constraint that tracks 2 and 3 land before track 1, the InvokeChanges.ps1 collision with the deferred upstream port backlog, and four open decisions that need answers before implementation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent e86e279 commit 08646d5

2 files changed

Lines changed: 246 additions & 0 deletions

File tree

docs/WINSWIFT_MODERNIZATION_PLAN.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,8 @@ To prevent irreversible damage during the v3.2.0 aggressive Appx purge:
3030
1. **Pre-Flight Check**: The script will invoke `CreateSystemRestorePoint` (with a 90-second timeout) and `BackupRegistryState`.
3131
2. **Validation**: We will modify `InvokeChanges.ps1` to parse the return status of these backups.
3232
3. **Abort Matrix**: If System Restore is disabled and the user refuses to enable it, the aggressive Appx purge array will be bypassed entirely, executing only safe registry tweaks.
33+
34+
## 4. Successor Plan
35+
36+
This document covers the v3.1.0 and v3.2.0 release tracks and is retained as a record of
37+
those decisions. The current roadmap is [WINSWIFT_V3.4.0_PLAN.md](WINSWIFT_V3.4.0_PLAN.md).

docs/WINSWIFT_V3.4.0_PLAN.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# WinSwift v3.4.0 Plan — Recoverability and Proof
2+
3+
## 1. Context
4+
5+
The last three releases each added one half of a safety story and stopped:
6+
7+
| Release | Added | Left open |
8+
|---|---|---|
9+
| v3.2.0 | Registry backup and system restore enforcement before bulk removal | Nothing consumes the backup automatically |
10+
| v3.3.0 | Desired-state verification with registry read-back and Appx checks | 10 of 112 features cannot be verified at all |
11+
| v3.3.0 | Static validation and Pester unit coverage | `Tests/Integration/` is empty; nothing exercises a real apply |
12+
13+
WinSwift can now take a backup and can prove whether a change landed. It still cannot
14+
*undo a failed run on its own*, and the proof does not cover the features most likely to
15+
fail. v3.4.0 closes that loop.
16+
17+
Everything in this release extends infrastructure that already ships. No new subsystem is
18+
introduced.
19+
20+
## 2. Scope
21+
22+
**Headline:** automatic rollback when an apply run fails.
23+
24+
**Supporting:**
25+
26+
- Verification coverage from 102/112 to 112/112.
27+
- An integration test suite that exercises apply, failure, and rollback for real.
28+
- Field validation of the 24H2 DISM fallback repaired in #9.
29+
30+
## 3. Track 1 — Automatic rollback on failed apply
31+
32+
### 3.1 Why this is mostly wiring
33+
34+
`Invoke-AllChanges` in `Scripts/Features/InvokeChanges.ps1` already runs in phases:
35+
36+
```
37+
Phase 1 Registry backup <- rollback material is created here
38+
Phase 2 System restore point
39+
Phase 3 Apply features <- failures are counted here
40+
Phase 4 Undo features
41+
Final Report failures, Export-RunSummary
42+
```
43+
44+
The backup is taken *before* the apply phase, so at the moment of failure the material
45+
needed to recover already exists on disk. `Restore-RegistryBackupState -Backup $backup` in
46+
`Scripts/Features/RestoreRegistryBackup.ps1` is a complete, working restore entry point,
47+
including the loaded-hive path for `DefaultUserProfile` and `User:*` targets.
48+
49+
`InvokeChanges.ps1` contains **zero** references to any restore function. The three missing
50+
pieces are a handle, a policy, and a call.
51+
52+
### 3.2 What to build
53+
54+
**A handle.** Phase 1 discards the backup object once written. Capture it into a
55+
run-scoped variable (`$script:RunRegistryBackup`) holding the backup payload and its file
56+
path, so Phase 3 can reach it without re-reading from disk.
57+
58+
**A failure policy.** This is the one real design decision in the release, and it must be
59+
settled before code is written. `$script:RegistryImportFailures` already counts registry
60+
import failures, and `$script:AppRemovalFailures` counts removals. Neither currently
61+
triggers anything except a yellow warning in the Final phase.
62+
63+
Proposed policy, to be confirmed:
64+
65+
| Condition | Action |
66+
|---|---|
67+
| Any registry import failure | Prompt to roll back; roll back automatically under `-Unattend` |
68+
| App removal failure only | Do **not** roll back — removals are not restored by a registry backup, so rolling back the registry would misrepresent what was recovered |
69+
| User cancellation (`$script:CancelRequested`) | Roll back changes already applied in this run |
70+
| Failure during rollback itself | Abort, report loudly, leave the backup file in place and name it |
71+
72+
The app-removal exclusion matters. A registry restore cannot bring back an uninstalled
73+
Appx package, so treating a removal failure as a rollback trigger would produce a run that
74+
claims to have recovered while leaving apps gone.
75+
76+
**A call.** Invoke the restore between Phase 3 and Phase 4, before any undo work, so a
77+
failed apply is not compounded by undo operations against a half-applied system.
78+
79+
### 3.3 Surfacing
80+
81+
- Extend `Export-RunSummary` (`Scripts/Features/ExportRunSummary.ps1`) with a rollback
82+
section: whether it triggered, what condition triggered it, which roots were restored,
83+
and whether the restore itself succeeded.
84+
- Add a `-NoAutoRollback` switch for operators who would rather inspect a broken state
85+
than have it reverted underneath them.
86+
- Return a distinct exit code for "apply failed and was rolled back" so unattended callers
87+
can tell it apart from a plain failure. v3.3.0 already established exit code `2` for
88+
verification noncompliance; this needs its own value rather than reusing `2`.
89+
90+
### 3.4 Acceptance criteria
91+
92+
- A run with an injected registry import failure restores the affected roots and reports
93+
it in the run summary.
94+
- A run with an app removal failure and no registry failure does **not** roll back.
95+
- `-WhatIf` never restores anything and says what it would have done, matching the
96+
existing `Restore-RegistryBackupState` WhatIf branch.
97+
- A failed restore is reported with the backup file path so recovery can be finished by
98+
hand.
99+
- `-SkipRegistryBackup` disables auto-rollback with an explicit warning at run start,
100+
rather than failing silently at the moment it is needed.
101+
102+
### 3.5 Upstream note
103+
104+
Upstream `Raphire/Win11Debloat` has an open issue (#612) and PR (#613) for automatic
105+
registry rollback. Review both before finalizing the policy table — not to port the code,
106+
since `InvokeChanges.ps1` is on the deferred-port list in `UPSTREAM.md` and has diverged,
107+
but to avoid a gratuitously different failure model.
108+
109+
## 4. Track 2 — Verification coverage to 112/112
110+
111+
### 4.1 The gap
112+
113+
Of 112 features in `Config/Features.json`, 93 verify through `RegistryKey` read-back and 9
114+
through a `VerificationAdapter`. Ten have neither and are silently unverifiable:
115+
116+
| FeatureId | Why registry read-back cannot cover it |
117+
|---|---|
118+
| `RemoveApps`, `Apps`, `RemoveGamingApps`, `RemoveHPApps` | Package state, not a registry value |
119+
| `ForceRemoveEdge` | Package state plus filesystem leftovers |
120+
| `ClearStart`, `ClearStartAllUsers`, `ReplaceStart`, `ReplaceStartAllUsers` | Start layout lives in a per-user layout file |
121+
| `CreateRestorePoint` | An event, not a persistent desired state |
122+
123+
### 4.2 Approach
124+
125+
There is already a precedent in the codebase. `Test-FeatureApplied` in
126+
`Scripts/Features/GetCurrentTweakState.ps1` handles `DisableWidgets` by querying
127+
`Get-AppxPackage` and treating package absence as the applied state. The four app-removal
128+
features and `ForceRemoveEdge` follow that pattern directly.
129+
130+
- **App removal features:** new `AppxAbsence` adapter resolving the feature's app list via
131+
the existing `Get-WinSwiftFeatureAppIds`, then checking installed *and* provisioned
132+
state. Provisioned state matters — a package can be uninstalled per-user while still
133+
provisioned and due to return on the next servicing pass.
134+
- **`ForceRemoveEdge`:** extend the above with the leftover shortcut paths and autostart
135+
values that `Remove-EdgeAutostartValue` already knows about.
136+
- **Start layout features:** new `StartLayout` adapter comparing the on-disk layout file
137+
against the expected shape.
138+
- **`CreateRestorePoint`:** do not add an adapter. It is an action, not a desired state.
139+
Mark it explicitly exempt in `Features.json` and have the verification engine report it
140+
as `NotApplicable` rather than counting it as an unverifiable gap. Add a unit assertion
141+
that this is the *only* permitted exemption, so the count cannot silently regress.
142+
143+
### 4.3 Acceptance criteria
144+
145+
- `Test-FeaturesJson.ps1` asserts every feature has `RegistryKey`, `VerificationAdapter`,
146+
or the explicit exemption — so a new feature cannot be added without a verification story.
147+
- `-Verify` reports a real verdict for all 111 verifiable features.
148+
- Exit code `2` continues to mean noncompliant, and `NotApplicable` never contributes to it.
149+
150+
## 5. Track 3 — Integration tests
151+
152+
`Tests/Integration/` has existed since July and is still empty. CI runs static validation
153+
and six unit files (470 lines total), none of which apply anything to a live system.
154+
155+
### 5.1 Scope
156+
157+
Windows Sandbox is the right harness: disposable, scriptable, present on Windows 11 Pro,
158+
and it discards state on close so a destructive test cannot damage the host.
159+
160+
Target scenarios, in priority order:
161+
162+
1. Apply a small registry-only feature set, verify with `-Verify`, assert exit code `0`.
163+
2. Apply, then undo, then assert the system returns to the pre-apply state.
164+
3. Inject a registry import failure, assert rollback triggers and restores.
165+
4. Inject an app removal failure, assert rollback does **not** trigger.
166+
5. `-WhatIf` over the full feature set mutates nothing.
167+
168+
Scenarios 3 and 4 are the ones that give Track 1 its value — without them, auto-rollback
169+
is an untested claim.
170+
171+
### 5.2 CI reality
172+
173+
Windows Sandbox is not available on GitHub-hosted runners. Options, in order of preference:
174+
175+
1. Keep integration tests **out** of the PR gate; run them on a self-hosted runner or
176+
manually before a release tag, and record the result in the release notes.
177+
2. Split the suite: the parts that need no live mutation (`-WhatIf`, exit codes, profile
178+
parsing) run in CI; the mutating parts stay manual.
179+
180+
Do not block this track on solving CI. A manually run suite with a documented procedure is
181+
worth more than no suite.
182+
183+
## 6. Track 4 — Field validation of the DISM fallback
184+
185+
The wildcard `/PackageName` bug fixed in #9 was never observed failing; it was diagnosed
186+
from DISM's documented contract and the tell-tale tolerance of exit code 87. The fix is
187+
reasoned, not measured.
188+
189+
Before tagging v3.4.0, on a real 24H2 machine with Copilot, Dev Home, or the new Teams
190+
still provisioned:
191+
192+
1. Confirm `Get-AppxProvisionedPackage -Online` returns the expected package names.
193+
2. Run the fallback and capture the actual `$LASTEXITCODE`.
194+
3. Confirm the package is gone from the provisioned list afterwards.
195+
4. Record the result in `CHANGELOG.md`.
196+
197+
If the fallback still fails, that is a v3.4.0 blocker, not a footnote — it is the path that
198+
exists specifically for the packages 24H2 is most aggressive about reinstalling.
199+
200+
## 7. Explicitly out of scope
201+
202+
Deferred so they are not relitigated mid-release:
203+
204+
- **Localization.** Upstream PRs #764 and #643 and issue #499 all want it. The WinSwift GUI
205+
has diverged from upstream and its strings are hardcoded, so neither PR ports cleanly.
206+
This is its own release track.
207+
- **Preset library.** Only `gaming-rig.json` exists. Cheap and user-visible, but it is
208+
breadth, not recoverability. Candidate headline for v3.5.0.
209+
- **The deferred `ef8811d` port backlog.** Nine files listed in `UPSTREAM.md`. Note that
210+
`InvokeChanges.ps1` appears on both that list and Track 1 of this plan — see §8.
211+
- **New tweaks from upstream enhancement issues** (#560, #482, #343, #267).
212+
213+
## 8. Sequencing and the one collision
214+
215+
```
216+
Track 2 (verification) ─┐
217+
Track 3 (integration) ─┼─> Track 1 (rollback) ─> Track 4 (field validation) ─> tag
218+
219+
```
220+
221+
Tracks 2 and 3 are independent and can run in parallel. Both should land **before** Track 1,
222+
not after: rollback is the change most likely to break something, and it should be built
223+
against a test suite and a verification engine that can actually catch the breakage.
224+
225+
**The collision:** Track 1 rewrites `InvokeChanges.ps1`, which is also on the deferred
226+
upstream port list in `UPSTREAM.md`. Decide before starting whether to port upstream's
227+
error handling for that file first or to write the rollback wiring against the current
228+
code and re-reconcile afterwards. Doing both independently will produce a conflict that is
229+
harder to resolve than either change alone. Recommendation: write rollback against the
230+
current code, then reconcile once, with the integration suite from Track 3 as the safety
231+
net.
232+
233+
## 9. Open decisions
234+
235+
These need answers before implementation, not during:
236+
237+
1. The failure policy table in §3.2 — specifically whether a registry import failure should
238+
prompt or roll back silently in interactive mode.
239+
2. The new exit code value for "failed and rolled back".
240+
3. Whether integration tests gate releases or merely inform them (§5.2).
241+
4. The `InvokeChanges.ps1` ordering question in §8.

0 commit comments

Comments
 (0)