feat(migration): gate Inno-to-Store migration preview and preserve the gateway on uninstall - #1461
Conversation
Add protected migration records, read-only source inventory, completion-aware Inno safeguards, exact installation discovery, and disabled-by-default Store startup admission. Consent, adoption, and finalization remain future work. Local build, focused tests, and x64 fixture proof passed before checkpoint; structured review remains deferred. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91896be-e75b-4a88-932e-61b78125ab4a
Keep the preview build-time gated and read-only. Consent only observes the Inno mutex and stops before adoption, completion, uninstall, or normal startup. Required validation and x64 packaged VM proof passed; structured review remains deferred. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91896be-e75b-4a88-932e-61b78125ab4a
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91896be-e75b-4a88-932e-61b78125ab4a
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91896be-e75b-4a88-932e-61b78125ab4a
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91896be-e75b-4a88-932e-61b78125ab4a
Hold the migration operation lock across Inno removal and cleanup, and re-detect the source under the completion lock. Use limited-information process queries to exclude proven unrelated processes while failing closed on uncertain source candidates. Add regression coverage and document the ownership guarantees. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91896be-e75b-4a88-932e-61b78125ab4a
Keep completed-receipt reads independent of the current wall clock while retaining write-time timestamp checks, intent policy, and all binding and structural validation. Add backward-clock regressions through the codec, Windows PowerShell checker, and cleanup preservation path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a91896be-e75b-4a88-932e-61b78125ab4a
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: blocked before merge. Reviewed September 25, 2026, 12:58 PM ET / 16:58 UTC (Revision 31). ClawSweeper reviewWhat this changesThe branch adds a gated Inno-to-Store migration preview, protected handoff records, startup guards, and uninstall checks that preserve the local WSL gateway when Store ownership may exist. Merge readiness⛔ Blocked before merge - 4 items remain The migration foundation is absent from current main and remains useful. The current branch addresses the earlier code findings, but its always-on uninstall safeguard changes unattended removal for users who have a Store package without a completed migration. That operator contract and release acceptance still need maintainer approval. Priority: P2 Review scores
Verification
How this fits togetherThe Windows installer and tray app share settings, credentials, and a managed WSL gateway across Inno and Store installations. Migration records and package registration guide startup and uninstall decisions about whether that gateway can be removed. flowchart LR
A[Inno installation] --> B[Migration records]
C[Store package registration] --> D[Uninstall preservation check]
B --> D
D --> E{Gateway removal safe?}
E -->|Yes| F[Remove local gateway]
E -->|No or uncertain| G[Preserve gateway and state]
Decision needed
Why: This changes the shipped enterprise removal contract outside the gated preview; source and local proof establish the behavior, but only maintainers can approve the operator tradeoff. Before merge
Agent review detailsSecurityNone. Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Keep the safety-first preservation path only with an explicitly approved unattended-removal contract, clear operator recovery guidance, and signed existing-user acceptance on both supported architectures before release. Do we have a high-confidence way to reproduce the issue? Not applicable as a feature PR. The changed uninstall behavior has concrete source paths and local Windows VM observations. Is this the best way to solve the issue? Unclear until the unattended-removal contract is approved. The guarded handoff is a plausible foundation, but its always-on installer behavior needs signed upgrade acceptance. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against 7d92747e193c. LabelsLabel changes: No label changes. Label justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (30 earlier review cycles; latest 8 shown)
|
Hold an Inno runtime reader until process exit and validate source activity under Store's exclusive migration lock. Share process inspection, preserve fail-closed startup guidance, and cover contention, exit, and completion retry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
Explain protected migration records, the Inno startup block after successful validation, manual source uninstall, and the Store restart requirement. Clarify retry guidance and cover all six locales with disclosure regression tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
Resolve additive architecture-ledger and six-locale resource conflicts while preserving migration safeguards and current-main chat changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
Remove action legends and refer directly to native Yes/No buttons in all six locales without changing consent or retry semantics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
…pers The checker exists because Add-Type can stall on a contended %TEMP% or an antivirus scan, and installer.iss waits for it with ewWaitUntilTerminated. Falling back to the inline check when the watchdog could not start reintroduced exactly that unbounded stall. Report the uncertain verdict instead; exit 2 already fails closed and preserves the gateway. The three uninstall helpers also carried uninsneveruninstall, but they run during usUninstall, which happens before Inno removes files. Generated app-state deletion only runs after successful gateway cleanup, so any uninstall that kept the local gateway, including the ordinary "No" choice unrelated to migration, left executable helpers behind in the former app directory. The retained-helper contract test pinned the flags it should have rejected, so it is inverted rather than deleted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
RequiresHardening skipped re-hardening whenever the DACL was protected and owned by the current user, without inspecting what those rules granted. A pre-existing store-migration directory can satisfy both conditions and still carry an Allow rule for an unrelated principal. That principal could delete completed.dpapi. Test-InnoMigration.ps1 maps a missing receipt to "no migration happened" and exits 0, which authorizes destructive local gateway cleanup during uninstall. The same receipt gates ordinary Inno startup. Before the hardening shortcut landed, SetAccessControl ran on every acquisition and would have removed such a grant. Restore that guarantee without reintroducing per-launch DACL churn by re-hardening only when a rule falls outside the owner, LocalSystem, and BuiltinAdministrators. RepeatAcquisition_LeavesAnAlreadyHardenedDirectoryUntouched used an Everyone: Read ACE as its no-rewrite canary, which is the grant this change must scrub. Its canary is now a trusted principal with distinct inheritance flags, so it still proves the absence of churn. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
Real uninstall proof on a guest without WSL surfaced two pre-existing defects in the gateway cleanup helper, both dating to openclaw#591. Neither was reachable before, because earlier proof runs declined gateway removal and never executed this branch. wsl.exe writes UTF-16LE on many Windows builds while the redirected pipe is decoded as 8-bit, so captured output arrived NUL-interleaved. Every pattern in Test-DistroNotFound matched against that text and none could ever fire, leaving the "already unregistered" success path unreachable in practice. Strip NUL in the captured output instead of pinning an encoding that varies by Windows version. A host with no WSL, or with WSL but no distributions, cannot be holding our gateway. Treat both as nothing to remove rather than surfacing "OpenClaw could not remove the local WSL gateway. Exit code: 1", which offered Retry against a condition no retry can fix. The new test executes the real functions extracted from the script rather than asserting on source text, since a source-text contract cannot detect an encoding defect. Reverting either half fails it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
The Inno uninstall checker decided whether to clean up gateway state based on a receipt file it did not verify it could trust. A non-trusted principal who could write the receipt (or its directory) could suppress or forge the cleanup decision. Trust is now established before the receipt is read: - Owner must be the current user, SYSTEM, or Administrators. - Any foreign Allow ACE that can mutate the receipt breaks authority. Read/list grants are tolerated, since installer.iss creates the state directory with plain ForceDirectories and inheritable read ACEs are common in managed fleets. - Generic rights (GENERIC_ALL, GENERIC_WRITE) count as mutating. They intersect no named FileSystemRights bit but the kernel maps them onto FILE_ALL_ACCESS / FILE_ADD_FILE, so a named-bit mask alone missed them. - Inherit-only ACEs are skipped; they do not apply to the object itself. When authority cannot be established the checker exits 2 and the uninstaller preserves state, rather than silently proceeding. Also replaces Get-Acl and Get-ChildItem with .NET types throughout. The uninstaller launches PowerShell in a constrained environment where Microsoft.PowerShell.Security fails to autoload, which made the check throw for every user and strand cleanup unconditionally. Residual, deliberately not sealed: a receipt file's own owner can grant itself DELETE and remove the receipt without altering the directory, which leaves nothing observable to the checker. Inherited Modify grants held by a domain administrators group will likewise strand cleanup rather than authorize it. Both fail closed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
Real VM proof on Windows 11 24H2 with WSL 2.7.14 showed every wsl.exe call in the gateway cleanup failing with exit 127: wsl.exe --list --quiet exited 127. /bin/sh: --list: not found wsl.exe matches its control flags against the raw command line and does not strip quotes, so a quoted "--unregister" is not recognized as a flag and is executed as a command inside the distro instead. The gateway distro was therefore never unregistered, and uninstall reported succeeded=false with exit 127. This regressed in 6a8aede, which replaced Start-Process -ArgumentList (an array, never quoted) with ProcessStartInfo (a single string) and quoted every argument to build it. The migration-checker call site was unaffected because it already passes bare flags and quotes only values. Quote only values that need it. The escaping rules for values that do need quoting are unchanged, including doubling trailing backslashes. Reproduced independently against real WSL on a developer host: wsl.exe --list --quiet -> exit 0, lists distros wsl.exe "--list" "--quiet" -> exit 127, /bin/bash: --list: not found Proof: real WSL2 gateway cleanup in a Hyper-V VM, before and after. before: exit 127, "Failed to unregister WSL distro", still registered after: exit 0, "Local WSL gateway removed", unregistered, unrelated bystander distro preserved, second run idempotent The new contract test executes the real function rather than asserting on source text, matching the existing WSL-less-host test. Source-text assertions cannot catch an argv-shape bug. Verified non-vacuous: the previous implementation turns --list into "--list" and fails it. Validation: build.ps1, Shared 4087 passed, Tray 3103 passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
…eanup The Inno uninstaller decided whether it could destroy the local WSL gateway from a single signal: the migration completion receipt. Deleting that receipt made a completed handoff indistinguishable from no migration, and the checker returned the cleanup-authorizing exit 0. A cleanup utility sweeping stale AppData, or an attacker, could therefore arrange for the uninstaller to delete the gateway the Store app was actively using. Silent uninstall destroys on exit 0 with no prompt at all. Add a second, independent signal. Cleanup is now authorized only when the receipt is absent AND the packaged-app registration is absent: - Get-StorePackageState returns Present, Absent, or Indeterminate rather than a boolean, because the caller has to say something truthful to the user. - New exit 11 means the Store app is registered without a valid receipt: preserve, and tell the user explicitly not to run wsl --unregister. - An unreadable or empty Packages key is treated as tampering, not absence. That key is user-writable and every real profile has hundreds of subkeys, so an empty enumeration cannot be evidence that nothing is installed. Exit 2 reaches the same preserve decision but previously told the user to run wsl --unregister, which destroys exactly what was just preserved. That advice is reachable on a machine that has already migrated, through an undecryptable receipt after a DPAPI master key loss, a watchdog that cannot start or times out, an Add-Type failure, an unavailable migration lock, or a missing PowerShell. StoreAppRegistered in installer.iss now gates the message itself, so every route to that single advice site is covered rather than the one branch where the checker managed to report exit 11. Destruction is still authorized only by exit 0. Package presence changes what the user is told, never what is deleted. Accepted residual, documented in the checker: a same-user attacker who deletes only the OpenClawFoundation.OpenClaw_* subkey and leaves the rest can still reach exit 0. Closing that needs a signal outside the user's write scope, and %ProgramFiles%\WindowsApps cannot serve because an unelevated process may not enumerate it. Validation: build.ps1, Connection 1113, Shared 4087, Tray 3106, all green. The new guard is mutation-checked: removing it fails both contract tests. installer.iss compiles under ISCC. Runtime VM proof of the regression and the security property is still outstanding and is tracked for this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
P1 VM proof run: verdicts and screenshotsCovers Case A, regression: 8 of 8 passed
Case B, package-presence security: 11 of 11 passed
ScreenshotsCase A: ordinary uninstall still shows the gateway prompt, with No as the focused default. Case A verdict: 8 of 8 passed. Case B positive control: real completed.dpapi present, checker exits 10. Proves the checker can still authorize on this machine. Case B, the P1: the entire store-migration directory is deleted and the checker exits 11. Before this fix it exited 0, which authorizes unregistering the distro. Case B: no gateway prompt. The new dialog states the Store app owns the gateway and warns against running wsl --unregister. Case B verdict: 11 of 11 passed. All 18 captures and the raw fingerprint JSON live on the evidence-only branch |
The uninstall path collapsed three distinct states into one boolean, so an unreadable or empty Appx package registry produced a dialog asserting that the Store app is installed and is using the gateway. That claim is unknown, and it contradicted scripts/Test-InnoMigration.ps1, which deliberately models Present, Absent, and Indeterminate as separate results. StoreAppRegistered is now StorePackagePresence, returning the same three states. A new ReportStoreAppStateUnknown preserves the gateway, makes no ownership claim, withholds the destructive wsl --unregister advice, and names the distro and preserved path in its log. Also addresses three review findings on that change: - Destructive unregister advice was the unguarded fall-through, so any unexpected presence value routed to data-loss guidance. Preservation is now the default and only a positively observed Absent reaches that advice. - Inserting the new procedure widened the slice of Installer_DoesNotAdviseUnregisteringWhenTheStoreAppOwnsTheGateway, which stopped isolating the ownership message. The slice now terminates at the new procedure. - The unknown-state log omitted the distro name and path, leaving an incomplete silent-uninstall audit trail. Validated: build.ps1, Shared 4087 passed, Tray 3107 passed, InnoMigrationContractTests 43/43, and an ISCC compile of installer.iss (mutation-verified that the compile actually rejects bad Pascal). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5c700f0-7fa2-46ed-bd1a-10a11521b63a
|
This is the related second half of the migration work, (draft, needs to be rebased to main after this goes in) for the real UI changes : natalie-aguinaldo#2 Attaching for reference |
Adversarial review verdictNo confirmed actionable defects after independent reviews by Claude Opus 4.7 and GPT-5.3-Codex, followed by source-level cross-reference. Reviewed commit: Both models agree: HIGH consensusNo jointly identified defects. Codex reported no actionable findings. Opus-only observations: LOW consensusThe severities below are the reviewer's initial classifications, not confirmed defects.
No fix-confidence rating is assigned because no corrective change is justified by the evidence. Enterprise ACL behavior remains an unverified edge case. Validation performed
Review only: no code changes were made. |
Brings in openclaw#1515 (fix(setup): retry guarded restart after reload owner handoff), which addresses the post-wizard serving-owner restart refusal introduced by Gateway 2026.9.6 and failing CI on every PR.






Related: #1374
PR 1 of 2: migration foundation and gated end-to-end preview. Production Store migration remains disabled in this PR. PR 2 is planned to complete #1374, including shipping UX and production enablement once release prerequisites and acceptance proof are satisfied.
Current-main integration and proof refresh:
1e504ceba488f04d616f12d086374c25847eaff6mergesmainatc7063081. Seven additive conflicts were resolved by preserving the migration and chat architecture rows and all six locales' resource entries. The full non-Dev Release Inno app and gated Debug MSIX were rebuilt after this commit, then exercised in the isolated Windows VM. Source tree:ff8c281a013e6f7ee1e979cf79fc50eaa8db1b67. Both earlier review fixes remain intact.Latest head:
71c91d05supersedese47197b6. Two commits follow it.f91e6895rejects foreign mutating grants on the uninstall receipt, including generic rights, and checks receipt ownership before trusting it.71c91d05fixes a regression this branch introduced in6a8aeded: the gateway cleanup quoted everywsl.exeargument, so control flags such as--unregisterwere executed as commands inside the distro and the gateway distro was never unregistered. Both are proven at runtime below.The long-standing "no registered WSL distro" gap is now closed. Earlier revisions could only prove the no-distro cleanup branch. A real registered WSL2 distro is now created, unregistered by the shipped helper, and confirmed gone, with an unrelated bystander distro preserved.
Product sources:
22db82746143542f3d69f69813f6884b43a3b780removes the extra action legends from both preview dialogs. The body now refers directly to the native Yes/No buttons in all six locales, preserving disclosures, default No, and behavior. Required validation passed on these sources; the packaged journey and both screenshots below were freshly captured from this committed head using test package2026.9.20.0. Source tree:da67d55bf6b3634a96841bedd8583034f39b8d30.What Problem This Solves
Inno and Store installations share user state and cannot safely operate as independent production copies. Inno uninstall can also remove the gateway that the Store app needs to preserve.
User Impact
Ordinary Store builds do not enter migration. An explicitly enabled Debug package exercises a same-user, matching-architecture handoff while preserving existing state in place.
Ordinary Inno builds include the completion guard and uninstall-preservation safeguards. Normal Inno startup acquires a read-shared migration handle before checking completion and retains it until process exit. A contended handle proves a live migration and blocks startup. An unreadable one does not, so startup continues: the completion receipt check stays authoritative and still fails closed. A valid completed receipt blocks normal Inno startup; uninstall preserves generated state. Uninstall is never refused because migration state is unreadable. Only a contended lock stops it, and any other uncertainty suppresses destructive gateway cleanup instead of cancelling removal.
Why This Change Was Made
Review fixes
e547733c: normal Inno retainsprepare.lockthrough process exit, including failed startup/shutdown. Store preparation/completion recheck source activity across sessions while holding the exclusive file lock. Busy completion supports Retry. The session-local mutex is no longer treated as sufficient cross-session ownership.46fd8b6e: consent in all six locales discloses protected records, the Inno startup block, required manual uninstall, Store restart, preservation, and no automatic uninstall. Close/Retry no longer promises that nothing has changed.7aef6b20: the preservation checker no longer falls through to an unbounded inline watchdog when its own watchdog cannot start; it warns and exits2(uncertain, preserve). Uninstall helpers no longer carryuninsneveruninstall, so they are removed with the app instead of being stranded on disk.4c5899b8: the migration directory DACL is now scrubbed of foreign grants on every acquisition, and explicit ACEs on the receipt file are scrubbed too. A previous churn optimization had skipped re-hardening for any protected, owner-matching DACL without inspecting its rules, which left a pre-existing directory able to grantEveryone: FullControl. A foreign principal deletingcompleted.dpapiwould have been read as an absent receipt and allowed destructive gateway cleanup.e47197b6: fixes a pre-existing uninstall defect that predates this PR (introduced in Add uninstall choice for local WSL gateway #591). Two problems, both in the gateway cleanup helper this PR already reworks:wsl.exewrites UTF-16LE on many Windows builds while the redirected pipe was decoded as 8-bit, so captured output arrived NUL-interleaved. Every pattern inTest-DistroNotFoundwas matchingT\0h\0e\0...and none could ever fire, leaving the "already unregistered, nothing to do" success path unreachable in practice.OpenClaw could not remove the local WSL gateway. Exit code: 1with a Retry button against a condition no retry can fix.Why it is in this PR rather than a separate one: it is the blocker for the required
windows-clean-installer-upgradeproof. The matrixremovecases exist specifically to exercise the no-distro cleanup branch, and that branch could not complete until this was fixed. It was invisible until now because earlier proof runs declined gateway removal and never executed the branch.How it was found: the real installer matrix on a WSL-less guest, not review. Neither ClawSweeper nor the human review caught it. The fix is covered by a test that executes the real functions extracted from the script rather than asserting on source text, because a source-text contract cannot detect an encoding defect; reverting either half fails it.
78d83cba: fixes the ClawSweeper P1. A deleted completion receipt no longer authorizes destroying the local WSL gateway.4c5899b8hardened the migration directory so a foreign principal could not deletecompleted.dpapiunnoticed, but every ACL trace that check reads lives inside that directory. Deleting the directory itself erased the evidence, and the checker fell through toexit 0, the code that authorizes unregistering the distro. The installed Store package outlives the entire migration directory, so it is now consulted first:Test-InnoMigration.ps1gainsGet-StorePackageState, a three-state read ofHKCU\...\AppModel\Repository\Packagesfiltered to theOpenClawFoundation.OpenClawprefix plus the_family anchor. Present yields the newexit 11(preserve: receipt missing, but the Store app is here). Indeterminate, meaning an unreadable hive, yieldsexit 2(preserve: uncertain). Absent falls through to the pre-existing authority check.installer.isspins the identity in one place via#define MyStorePackageName, adds a package-presence read that fails closed on an unreadable or empty repository key, and handles11in bothRunLocalGatewayCleanupand the result dispatch.WarnMigrationCheckUnavailablepreviously printed "runwsl --unregister OpenClawGateway" on the uncertain path, which would have destroyed exactly what the new branch just preserved.ReportStoreAppOwnsGatewaynow answers first.Uninstall-LocalGateway.ps1maps11explicitly to preserve rather than inheriting a default.Why the package check rather than more ACL hardening: an attacker who can delete the directory can delete its ACLs. The package registration sits outside that blast radius.
Accepted residual, documented in code: a same-user attacker who deletes only the
OpenClawFoundation.OpenClaw_*package subkeys can still reachexit 0. That requires the ability to unregister the Store app, which already defeats the migration.Known trade-off, flagged for maintainer acceptance: preservation now triggers on the Store app being registered, not on this machine having migrated. A silent or unattended uninstall therefore leaves the
OpenClawGatewaydistro and generated state in place on any machine where the Store app is deployed, including machines that never migrated, with no override switch. UnderUninstallSilent()this is logged rather than shown. This is consistent with the recorded decision that checker uncertainty preserves, but it is a behavior change for enterprise removal and is called out deliberately rather than discovered post-merge.fa89486f: fixes a late review finding on the commit above. The package check was a Boolean, so an unreadable or empty package registry produced the dialog asserting the Store app is installed and is using the gateway. That state is unknown, and it contradictedTest-InnoMigration.ps1, which deliberately modelsPresent,Absent, andIndeterminateseparately for the same hive. It is nowStorePackagePresencereturning those three states, and a newReportStoreAppStateUnknownpreserves the gateway without claiming ownership and without handing overwsl --unregister. Preservation is also now the fall-through: only a positively observedAbsentreaches the destructive advice.Captured on current head in the guest, with the checker removed and the package hive made unreadable so the uncertain branch is the one actually taken:
The uninstall log recorded
Store app registration could not be read: preserving the local WSL gateway. The OpenClawGateway WSL distro and C:\Users\MigrationTest\AppData\Local\OpenClawTray\wsl\OpenClawGateway were left in place.The gateway removal prompt did not appear, and the capture fails the proof if the dialog claims ownership or nameswsl --unregister.Maintainer decisions recorded
STORE_MIGRATION_PREVIEWgate. An Inno user who never opts into the preview can still have a completed Store migration, so gating the safeguard would let uninstall destroy preserved gateway state.wsl --unregister OpenClawGateway. The silent path now logs the leftover distro and directory so an enterprise admin reading the uninstall log has something actionable.Scope and review guide
InnoInstallationDetector, version policy, startup coordinator: no guessed source or production opt-inMigrationRecordCodec,MigrationInventory,MigrationPreparation: bounded same-user state, atomic records, source state preservedMigrationOperationLock,InnoSourceActivityVerifier, startup guard: process-lifetime reader versus exclusive Store writer; uncertain process inspection blocksinstaller.iss,Test-InnoMigration.ps1, cleanup helper: preservation check and destructive work cannot race completionApponly composes and retains lifetime ownershipGatewayRegistry,CredentialResolver, andAutoStartManagerremain canonical owners. There is no new node command, MCP tool, gateway client, or payload-relocation path.Follow-ups, intentionally not included
PR 2 (complete #1374): shipping UI, Open Installed apps, Inno Store-launch/consent handoff, graceful shutdown/manual fallback, activation/accessibility coverage, and production enablement after identifying a verified safe Inno release and satisfying release prerequisites and acceptance proof.
The fixture's
2026.9.5.0minimum is not a production compatibility claim. The eventual minimum must include the process-lifetime lock; process scanning alone cannot prevent an older nonparticipating binary starting after inspection.Evidence
Merged-source automated validation and the rebuilt, locally test-signed x64 packaged journey passed. Separate actual Release-app proof crossed Windows sessions 0 and 1. These are isolated local proofs, not official signed-release or Store-distributed acceptance.
Change Type
Scope
Required proof pools
windows-clean-installer-upgrade: passed, 8 of 8 cases on current headfa89486f. Every case reachedexitCode 0with visible uninstall completion, and the guest was returned to baseline with matching hashes. The real uninstaller invoked the changed preservation checker in all eight runs (Migration preservation check returned 0.). Inputs are official signedv2026.9.4and a full current-head unsigned local Release Inno installer. Because the installer is unsigned, this is not shipping-signature proof. This matrix first ran onf91e6895, was rerun to completion on71c91d05(8 of 8,results-71c91d05-r1), was rerun on78d83cba(8 of 8,results-78d83cba-r2), and was rerun again on current headfa89486f(8 of 8,results-fa89486f-r1, guest summarystage=complete,commit: fa89486f3932ddf57b8a6b22766172423ca5aaff).windows-wsl-gateway-e2e: passed, 3 of 3 cases on71c91d05. Real WSL2 distro registered, then removed by the shippedUninstall-LocalGateway.ps1: distro unregistered, install directory removed, unrelated bystander distro preserved, second run idempotent. Synthetic Alpine rootfs rather than a provisioned gateway, so gateway runtime behavior and live pairing are still not claimed.windows-winui-interactive: current-source English packaged consent/Retry captures and blocked Inno startup captures passed. Full shipping UX, localized layouts, and accessibility remain outstanding.windows-11-arm64: not verified / blocked. No native ARM64 host used.These are applicable host classes, not claims that maintainer-scheduled proof pools completed. No MXC execution, GPU, signing-policy, or Smart App Control change is claimed.
Validation
.\build.ps122db8274dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore22db8274sourcesdotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore22db8274sourcesdotnet test .\tests\OpenClaw.Connection.Tests\OpenClaw.Connection.Tests.csproj --no-restore22db8274sourcesFullyQualifiedName~InnoMigrationContractTests|FullyQualifiedName~LocalizationValidationTests2026.9.22.0, rebuilt from committed22db82742026.9.20.0, built from committed22db8274git diff --check78d83cbaP1 revalidation:.\build.ps1passed; Connection 1,113 passed; Shared 4,087 passed; Tray 3,106 passed; 0 failed. Total 8,306 passed. Connection rises by 9 (StoreMigrationPackagePresenceTests, covering the prefix/underscore family anchor, absent and unreadable repository keys, and the three-state contract). Tray rises by 2 installer contract tests pinning that the Store-app guard precedes thewsl --unregisteradvice and that the package identity is defined once. Both new installer tests are mutation checked: deleting theStoreAppRegisteredguard block frominstaller.issfails both, so they are not vacuous.installer.isscompiles clean under ISCC.fa89486frevalidation:.\build.ps1passed; Shared 4,087 passed; Tray 3,107 passed, 0 failed. Tray rises by 1: a contract test pinning that an unreadable or empty package registry is described as uncertain rather than owned.InnoMigrationContractTestspasses 43 of 43.installer.isscompiles clean under ISCC, and that gate was verified non-vacuous by a deliberate Pascal mutation, which ISCC rejects withIdentifier expected.Earlier head
71c91d05revalidation:.\build.ps1passed; Shared 4,087 passed / 33 skipped; Tray 3,103 passed; 0 failed. The Tray count rises by one againstf91e6895: a new contract test asserts thatwsl.execontrol flags are passed unquoted. It is verified non-vacuous, since the previous implementation turns--listinto"--list"and fails it.On
f91e6895:.\build.ps1passed; Shared 4,087 passed / 33 skipped; Tray 3,102 passed; Connection 1,104 passed / 1 skipped; 0 failed. Connection rises by 24 overe47197b6, covering foreign-owner rejection, generic-rights masks, inherit-only ACE handling, and receipt ownership.Earlier head
e47197b6revalidation:.\build.ps1passed; Shared 4,087 passed / 33 skipped; Connection 1,077 passed / 1 skipped; Tray 3,102 passed; 0 failed.ActivationRouterTestspassed 36/36 on five consecutive runs on this head, supporting a baseline-flake reading rather than a regression from this branch. New coverage pins the recovery contracts: contended versus unreadable lock handling, uninstall never cancelling on unreadable state, directory security applied only on create or drift, and the preservation notice carrying its own removal instructions.Clock regressions execute the real Windows PowerShell 5.1 preservation checker and isolated cleanup helper without changing the host clock. Completed receipts survive simulated backward-clock correction; corrupt records, mismatched bindings, and invalid intents remain rejected. Cross-process tests cover shared readers, exclusive contention, normal/crash handle release, and source restart between preparation and completion.
Historical failures are not hidden: the original pre-merge full Connection run had two checker failures, followed by isolated and full-suite passes. An initial cross-session-development Shared run timed out waiting for the Piper extractor fixture PID; it passed alone, then complete required validation reruns passed. The current merged-source runs above passed. Skips are not counted as proof.
Rubber-duck / code review
Earlier non-Codex whole-branch/scoped reviews found and drove fixes for uninstall/completion serialization, excessive process-query access, and clock-rollback receipt rejection. The two latest feedback fixes received direct source review and regression/runtime proof.
ClawSweeper revision 4 reviewed
46fd8b6e, accepted both code fixes and the refreshed runtime evidence, and reported no actionable code or security findings. It requested current-main conflict resolution, installed-user compatibility proof, and maintainer acceptance of the always-on safeguards.The additive merge resolution received direct review and exact three-way resource comparison. External structured-review attempt:
python .\.agents\skills\autoreview\scripts\autoreview --mode local --engine claude --no-web-search --no-tools .... The merge bundle exceeded the helper's size limit. The subsequent narrow Yes/No bundle passed that gate but was blocked by the unavailable Claude executable. No independent post-merge or Yes/No model-review result is claimed, and no Codex fallback was run. This is not a security-audit certification.Real behavior proof
Environment: disposable local IXPTools / Hyper-V Windows 11 x64 VM; same-user synthetic state; interactive non-elevated app. Provider: local Hyper-V. Cloud lease ID and run URL: N/A. Harnesses are session-local, not committed repository entry points.
Actual Inno runtime, across Windows sessions
Command:
pwsh -NoProfile -File run-cross-session-vm.ps1from an elevated host controller. The full non-Dev Release x64 self-contained unpackaged app was rebuilt from1e504ceb, launched in guest session 1, and checked from guest session 0. Output:cross-session-vm-results-r4.Six cases passed: normal startup with no receipt; corrupt-receipt compatibility; exclusive-owner blocked startup; valid-completion blocked startup; unreadable-receipt blocked startup; reparse lock-path rejection without modifying the target. A running app excluded the other session's exclusive handle while allowing uninstall-style readers. Handles were available after process exit. Normal-app cases used forced process exit; blocked dialogs exited by dismissal. Graceful full-app shutdown is not separately claimed.
This cross-session evidence is from the merge commit, not a rerun on
22db8274. The later change only alters Store preview copy/formatting, its tests, and documentation; the Inno runtime guard is unchanged.Actual packaged consent and migration journey
Commands:
pwsh -NoProfile -File build-migration-startup-preview.ps1, thenpwsh -NoProfile -File run-migration-startup-vm.ps1 -Resume.OpenClawFoundation.OpenClaw_2026.9.20.0_x64__rfcbke2p71se2, locally test-signed inside the VM and launched from its installed package location.22db82746143542f3d69f69813f6884b43a3b780. This is a post-commit rebuild, not a source-equivalence claim.1475B50CEDDA7AFE21DCF061064E9807E6DB18137FD4E9C9DA17E210E09DC036.6C98E61317C1BCB84D96DCA029DB0731424D3B075CB3FB52D3998E07F5890770. It uses current installer/codec/helper sources and a versioned test apphost, not an official released full installer.AutoStart=false, cleared records, and continued normal packaged startup. Process identity/logs, not a normal-window screenshot, establish that final step.Copied current packaged-run result (
startup-preview-results-20260923-002654):{ "declinePreservedSetupWithoutRecords": true, "restartPreservedReceipt": true, "blockedUninstallExitCode": 1, "exclusiveLockBlockedUninstall": true, "uninstallPreservedState": true, "sourceSettingsUnchanged": true, "preparedIntentWritten": true, "finalized": true, "success": true, "cases": ["consent-decline", "consent-retry", "restart-waiting", "finalize"] }Screenshots were visually inspected locally. They are GitHub-hosted attachments uploaded through the contributor's public fork, where the account has the push access required for attachment upload. No credentials, raw settings/identity files, or unrelated desktop content are published.
All three image URLs were verified after publication with unauthenticated HTTP 200 responses and SHA-256 matches against the inspected local captures.
Actual Inno install/uninstall matrix, eight cases
Command:
Run-InnoMatrixVm.ps1 -Execute -RunId 71c91d05-r1 -ExpectedManifestSha256 8260C3D0...from an elevated host controller against a disposable local Hyper-V guest. Payload pinned by manifest SHA2568260C3D0CBB4E33282A6DC684CC687677574D9983B15317EDE16079CD5BE6576, built from71c91d05. Output:results-71c91d05-r1. The guest summary recordscommit: 71c91d059fd4c9865c70e3975bfaeadb4c99aaa0, so that matrix ran on71c91d05rather than a prior head.Rerun on current head: the same matrix was rerun as
78d83cba-r2, payload pinned by manifest SHA25655FA61C16836F24788062207B969F35F66E1BB4994416F79BE060B79541FE12F, built from78d83cba. Output:results-78d83cba-r2. The guest summary recordssuccess: true,stage: complete, andcommit: 78d83cba5661e75ae2f6217c5e0a0ec47f2b693f. All eight cases reachedexitCode 0withvisibleCompletion: true, andMigration preservation check returned 0.appears in all eight uninstall logs. Rerun again on current head:fa89486f-r1, payload pinned by manifest SHA256846562BD0AC64C787EB5450EC224A2843CA7C98432B0BAE0415016BCA9033098, built fromfa89486f. Output:results-fa89486f-r1. The guest summary recordssuccess: true,stage: complete,commit: fa89486f3932ddf57b8a6b22766172423ca5aaff, all eight cases atexitCode 0withvisibleCompletion: true,Migration preservation check returned 0.in all eight uninstall logs, andrestoration: restored-with-matching-hashes. No case reached the new uncertainty path, which is expected and is the point: the checker is present and no Store package is registered, so this run is a regression gate on the restructured uninstall logic rather than new evidence for it. The screenshots below are from the71c91d05-r1run.8 of 8 cases passed. Each case reached
exitCode 0withvisibleCompletion: trueunder an elevated same-user interactive task, and the guest was restored to baseline with matching hashes (restoration: restored-with-matching-hashes).The real uninstaller invoked the changed preservation checker in all eight runs, evidenced by
Migration preservation check returned 0.appearing in every one of the eightuninstall.logfiles.fresh-autostart-false-retainfresh-autostart-false-removefresh-autostart-true-retainfresh-autostart-true-removeupgrade-autostart-false-retainupgrade-autostart-false-removeupgrade-autostart-true-retainupgrade-autostart-true-removePer-case evidence includes install logs, startup/registration state, UI screenshots, and UIA accessibility dumps for the uninstall confirmation and the gateway retain/remove choice.
upgrade-autostart-true-remove: the previous-release upgrade path with autostart enabled and the gateway remove choice. The real Yes/No gateway prompt is presented during uninstall and clicked by the harness.upgrade-autostart-true-remove: the same previous-release upgrade path reaches visible successful completion, exit code 0, no error dialog.The four
removecases each ran the real gateway cleanup helper end to end. The earliere47197b6-r1run exposed the pre-existing uninstall defect fixed ine47197b6: on the prior head the firstremovecase blocked on acould not remove the local WSL gatewaydialog and timed out. The71c91d05-r1run confirms that fix stays in place, with the same case passing with no error dialog.That run also demonstrated why the watchdog in
7aef6b20matters: on a WSL-less hostwsl.exeemits an interactive "Press any key to install... this prompt will time out in 60 seconds" prompt during uninstall. The 120 s bound contains it; without a bound it is an indefinite hang in an uninstaller.Deviations, stated plainly: the installer under test was unsigned, so this is not shipping-signature proof. Prerequisite install used an explicitly approved elevated step; first-time UAC remains unproven. The matrix guest has no registered WSL distro, so its
removecases prove the real no-distro cleanup branch; live distro deletion is proved separately below.Actual WSL gateway removal against a real registered distro
This closes the long-standing "no registered WSL distro" gap in the matrix above.
Command:
Run-GatewayProofVm.ps1 -Execute -RunId gateway-proof-r7against a disposable local Hyper-V guest with real WSL2 installed (wsl.2.7.14.0.x64.msi, SHA-256DB084E53...). Output:results-gateway-proof-r7. The proof invokes the shippedscripts/Uninstall-LocalGateway.ps1directly, which is exactly how Inno invokes it.3 of 3 cases passed on
71c91d05.registered0,stillRegistered: false,installDirRemoved: truebystanderOpenClawProofBystanderstill registeredidempotent0Registration state is read from the
Lxssregistry, the same key the product's ownership gating reads. Before cleanup:[ { "Name": "OpenClawProofBystander", "BasePath": "...\\OpenClawProofTray\\wsl\\OpenClawProofBystander" }, { "Name": "OpenClawProofGateway", "BasePath": "...\\OpenClawProofTray\\wsl\\OpenClawProofGateway" } ]After cleanup, only the bystander remains:
[ { "Name": "OpenClawProofBystander", "BasePath": "...\\OpenClawProofTray\\wsl\\OpenClawProofBystander" } ]Copied result summary:
{ "success": true, "stage": "complete", "wslVersion": 2, "cases": [ { "case": "registered", "pass": true, "exitCode": 0, "stillRegistered": false, "installDirRemoved": true, "reportedMessage": "Local WSL gateway 'OpenClawProofGateway' removed." }, { "case": "bystander", "pass": true, "stillRegistered": true }, { "case": "idempotent", "pass": true, "exitCode": 0 } ] }This proof caught a real regression that this branch introduced. The first run against a real distro failed every
wsl.execall:6a8aededon this branch replacedStart-Process -ArgumentList(an array, never quoted) with aProcessStartInfocommand string, and quoted every argument to build it.wsl.exematches its control flags against the raw command line without stripping quotes, so"--unregister"was passed through to the distro's shell and executed as a command. This was reproduced independently on a real WSL host outside the VM:wsl.exe --list --quietexits 0 and lists distros,wsl.exe "--list" "--quiet"exits 127.71c91d05narrowsConvertTo-ProcessArgumentto quote only values containing whitespace or quotes. After the fix, the same log reads:The regression is now locked by
GatewayUninstall_PassesWslControlFlagsUnquoted, which executes the real argument-building path rather than asserting on source text. It is verified non-vacuous: the previous implementation turns--listinto"--list"and fails the test.Deviations, stated plainly: the rootfs is a minimal Alpine image, not a provisioned gateway, so this proves cleanup and bystander preservation, not gateway runtime behavior or live pairing.
Uninstall-LocalGateway.ps1is invoked directly; SetupEngine ownership gating is covered by unit tests, not by this proof. The matrix cases above were rerun to completion on current headfa89486f(8 of 8,results-fa89486f-r1).Actual foreign-principal receipt authority, six cases
Command:
Invoke-ForeignReceiptProof.ps1from an elevated host controller. The proof creates a real local principal, applies real ACLs, and runs the shipped Windows PowerShell 5.1 preservation checker. Exit2means "preserve the gateway", exit0means "cleanup authorized".6 of 6 cases matched expectations.
cleanforeign-daclforeign-ownedresidualforeign-readforeign-genericGENERIC_ALLforeign-genericis the case that matters most: the raw mask on the ACE is0x001F01FF,0x10000000, which intersects no namedFileSystemRightsbit, yet the kernel maps it toFILE_ALL_ACCESS. A naive named-bit check would have passed it as harmless.Two limits are disclosed rather than hidden.
residualreturns0because nothing observable remains, so the checker genuinely cannot distinguish a deleted receipt from one that never existed. A domain administrator holding inherited Modify rights will likewise strand cleanup at exit2. Both are accepted properties of a same-user, filesystem-observable design, not defects the checker can close.Actual uninstall authority, real distro, control and refusal
Command:
Run-AuthorityProofVm.ps1 -Execute -RunId authority-proof-r2against a disposable Hyper-V guest. Real current-head Inno install, a real registered WSL2OpenClawGatewaydistro, and the real interactive uninstaller in both arms.25 of 25 assertions passed.
control-removed0authority-refused2The control arm exists so the refusal is not vacuous: the same harness, same installer, and same real distro demonstrably do perform a destructive unregister when authority checks out.
The foreign principal is genuinely distinct from the uninstalling user. The receipt owner is
...-1002(OpenClawAuthProof), while the signed-in user is...-1000, recorded with full SDDL inforeign-receipt.json.In the refusal arm the uninstaller logged its own decision and left recovery instructions:
The refusal is structural rather than a silently failed deletion:
noWslCleanupLogExists count=0, so the cleanup script was never invoked and nowslcall was attempted at all.HKCU\...\Lxssstill containsOpenClawGatewayafterward, and the install directory is intact.Control arm, the real destructive choice is offered, with No focused by default:
Refusal arm, same installer and same point in the flow, the destructive choice is replaced by an acknowledgement-only warning:
Disclosed limits. The fixture imports a minimal Alpine rootfs rather than a provisioned gateway runtime, so this proves registration and directory lifecycle for the exact
OpenClawGatewaytarget, not gateway runtime behavior. Both uninstalls ran through an elevated interactive task, matching the matrix harness shape.Existing gateway state survives Inno removal and is adopted by the Store build
Run on a clean Windows VM at head
71c91d05. Inno2026.9.23.0installed over a pre-existing gateway fixture, uninstalled with No to the local gateway prompt, then the current-head Store package installed and launched.fixture-had-statedurable-state-byte-identicalvolatile-churn-reportedopenclaw-tray.log,Logs\diagnostics.jsonl,store-migration\prepare.lock.distro-still-registereddistro-enterableopenclaw-distro-okstore-distro-registeredStore package verified as
2026.9.23.0, commit71c91d059f..., sha25652E9DF49.... Across the Store leg onlyopenclaw-tray.log,Logs\diagnostics.jsonl, andext4.vhdxchanged; nothing was added or removed.gateways.json,settings.json, and the device key are byte-identical from the pre-uninstall baseline through to a running Store app.User answers No to the local gateway prompt during Inno uninstall:
The current-head Store build lists the preserved gateway:
Final verdict, all checks pass:
Limits. Credentials are synthetic and
gateway.example.testdoes not resolve, so the Connection screenshot showsAuthentication failedby design. This proves preserved state is read and adopted; it does not claim a live gateway handshake. Volatile files are reported by name rather than excluded.Real gateway migration on a developer host
Run on a developer machine at head
71c91d05against a real gateway: paired via device token, running for 3 days, with real credentials, device keys, and a registered WSL distro. This is the live counterpart to the synthetic VM run above, where the preserved state was a fixture.completed.dpapi694 b,intent.dpapi6,550 bgateways.json130374774Bbefore and aftersettings.json33AEB8D49Dbefore and afterA156E37B91OpenClawGateway-Devstill registeredcompleted.dpapiandintent.dpapiremovedThe missing gateway prompt is the designed path rather than luck.
CheckCompletedStoreMigrationreturnsMigrationResult = 10, which setsLocalGatewayCleanupRequested := Falseand exits before the prompt atinstaller.iss:354.The Store app reports
Install type: Packaged (MSIX)and version2026.9.23.0, so the connected session is the Store package and not a surviving Inno process.Consent dialog before any change
Inno uninstall completes with no local gateway prompt
Store build connected using the preserved device token
Packaged identity of the connected app
Limits and disclosures.
Test-InnoMigration.ps1. The path proven here is receipt-present (decode, then exit 10), which the fix leaves unchanged.settings.jsonis byte identical and containsEnableNodeMode: False, a value that was set about six minutes before migration began. The individual capability toggles (NodeCanvasEnabled,NodeScreenEnabled,NodeSystemRunEnabled) all carried over asTrue.OpenClawGateway-DevunderOpenClawTray-Dev. The production uninstaller targetsOpenClawGateway, so managed-distro ownership transfer is still not proven. This is a handoff and adoption proof, not an ownership-transfer proof.Actual package-presence refusal after receipt deletion, real Store package
Full per-check verdict tables and screenshots are posted in this comment. All 18 captures and the raw fingerprint JSON live on the evidence-only branch
proof/p1-package-presencein the fork, kept out of this PR's diff.Environment: local Hyper-V guest
OC-AUTO-A91896, userMigrationTest, non-elevated interactive uninstall. Real registered WSL distroOpenClawGatewayat%LOCALAPPDATA%\OpenClawTray\wsl\OpenClawGateway. Real preview MSIX carrying the production identityOpenClawFoundation.OpenClaw2026.9.23.0, built from71c91d05, test signed and installed in the guest only. Provider: local Hyper-V. Cloud lease ID and run URL: N/A.The MSIX predates current head
fa89486f, which is not a gap: the Inno-side commits changeinstaller.iss,Test-InnoMigration.ps1,Uninstall-LocalGateway.ps1, and tests, and touches no file undersrc\. The Store side is byte-equivalent to current head.Case A, regression. 8 of 8 passed. Ordinary uninstall, no Store package present.
0, cleanup authorized, unchangedCase B, the P1. 11 of 11 passed. Same machine, real migration performed through the Store app.
11completed.dpapiandintent.dpapipresent10%APPDATA%\OpenClawTray\store-migrationdirectory deleted110.The uninstall that followed showed no gateway prompt, and instead the new dialog: "OpenClaw from the Microsoft Store is installed on this PC and is using the local WSL gateway ... Do not run
wsl --unregister OpenClawGateway." Afterwards the distro was still registered and enterable, withdestructiveChanges=0across preserved state.The mid-migration window is the strongest real-world case. Completion is only written after the Inno app is uninstalled and the Store app reopened, so the documented flow guarantees a period in which the Store package is registered and no completion receipt exists. Pre-fix, that window returned
0. A silent or unattended uninstall during it would have destroyed the gateway mid-migration, with no attacker involved.Negative control, unplanned and real. A second package,
OpenClawFoundation.OpenClaw.Dev_2026.9.5.1_x64__h1swxthnmjtwj, was installed throughout both cases and correctly never matched. Of 135 registered packages, exactly one matched. This is live evidence that the_family anchor is load bearing: a bare prefix match would have matched the Dev identity.Non-vacuity. Every fingerprint recorded the SHA256 of the
Test-InnoMigration.ps1that the real uninstaller actually invoked. All runs observedB8F08113882F83C938A90C87DD23FE74B5DB861538883443A8CD81A22FC181E2, matching the committed checker. Had the guest been running a stale checker, the run would have been rejected rather than silently passing.Does not prove: the installer is an unsigned local Release build, so this is not shipping-signature proof. The live
ext4.vhdxis recorded as present rather than hashed, because a running WSL distro both locks and mutates it; deletion would still have been detected as a removed entry. No ARM64 host. The uninstall was interactive, not silent, so the silent-path trade-off above is reasoned from the code rather than executed.Artifacts: six
fingerprint-*.jsonstate captures,p1-verdict-regression.json(8 pass),p1-verdict-security.json(11 pass), and screenshots of both dialogs, both verdicts, and the exit 10 to exit 11 transition.Not verified / blocked
fa89486f(see above), covering the Inno uninstall changes ininstaller.iss. Remaining gaps in that area: the installer under test is unsigned, and no logoff/reboot startup proof is claimed.AutoStart=true, startup-platform refusal, real multi-user access-denied classification, and the complete interruption/failure matrix.openclaw:acceptance, shipping accessibility, non-English visual layout, and interactive Windows Settings uninstall. Fixture removal was silent Inno uninstall.InitializeUninstallis Inno Pascal and no executing unit test runs it. Its runtime behavior is now exercised by the eight-case matrix and the two-arm authority proof above rather than by unit tests, so regressions in the Pascal path depend on that VM harness, which is session-local and not a committed repository entry point.Security Impact
Compatibility and Migration
--uninstall --confirm-destructiveremains deliberate full removal and is never invoked by migration.docs/uninstall-portable.md.Review Conversations
Both reported code findings are addressed by
e547733cand46fd8b6e: cross-session source exclusion and inaccurate consent copy. Maintainer acceptance of always-on Inno safeguards and release compatibility proof remain distinct rollout requirements. This PR is no longer a draft and is open for team review; maintainer edits remain enabled.