Skip to content

feat(migration): gate Inno-to-Store migration preview and preserve the gateway on uninstall - #1461

Merged
karkarl merged 24 commits into
openclaw:mainfrom
natalie-aguinaldo:user/natalie-aguinaldo/inno-to-store-migration
Sep 25, 2026
Merged

karkarl merged 24 commits into
openclaw:mainfrom
natalie-aguinaldo:user/natalie-aguinaldo/inno-to-store-migration

Conversation

@natalie-aguinaldo

@natalie-aguinaldo natalie-aguinaldo commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

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: 1e504ceba488f04d616f12d086374c25847eaff6 merges main at c7063081. 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: 71c91d05 supersedes e47197b6. Two commits follow it. f91e6895 rejects foreign mutating grants on the uninstall receipt, including generic rights, and checks receipt ownership before trusting it. 71c91d05 fixes a regression this branch introduced in 6a8aeded: the gateway cleanup quoted every wsl.exe argument, so control flags such as --unregister were 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: 22db82746143542f3d69f69813f6884b43a3b780 removes 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 package 2026.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

  • Detect the exact supported per-user Inno installation. Block ambiguous registrations, unsupported versions/layouts, and architecture mismatches.
  • Capture bounded metadata and hashes under exclusive ownership. Protect intent and completion with current-user DPAPI, without exporting credentials or moving gateway/Local AI payloads.
  • Record completion only after fresh inventory validation and canonical active-gateway credential resolution. This proves local credential availability, not a live gateway connection.
  • Serialize runtime state writers, Store migration, and Inno removal across Windows sessions. Retain completion until removal, inventory revalidation, and startup-preference application succeed.
  • Preserve completion across clock rollback and fail closed on uncertain source processes, inaccessible state, and operational errors.

Review fixes

  • e547733c: normal Inno retains prepare.lock through 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 exits 2 (uncertain, preserve). Uninstall helpers no longer carry uninsneveruninstall, 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 grant Everyone: FullControl. A foreign principal deleting completed.dpapi would 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.exe writes UTF-16LE on many Windows builds while the redirected pipe was decoded as 8-bit, so captured output arrived NUL-interleaved. Every pattern in Test-DistroNotFound was matching T\0h\0e\0... and none could ever fire, leaving the "already unregistered, nothing to do" success path unreachable in practice.
    • A host with no WSL at all was treated as a hard failure, surfacing OpenClaw could not remove the local WSL gateway. Exit code: 1 with 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-upgrade proof. The matrix remove cases 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. 4c5899b8 hardened the migration directory so a foreign principal could not delete completed.dpapi unnoticed, but every ACL trace that check reads lives inside that directory. Deleting the directory itself erased the evidence, and the checker fell through to exit 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.ps1 gains Get-StorePackageState, a three-state read of HKCU\...\AppModel\Repository\Packages filtered to the OpenClawFoundation.OpenClaw prefix plus the _ family anchor. Present yields the new exit 11 (preserve: receipt missing, but the Store app is here). Indeterminate, meaning an unreadable hive, yields exit 2 (preserve: uncertain). Absent falls through to the pre-existing authority check.
    • installer.iss pins the identity in one place via #define MyStorePackageName, adds a package-presence read that fails closed on an unreadable or empty repository key, and handles 11 in both RunLocalGatewayCleanup and the result dispatch.
    • The guard runs before any destructive advice. WarnMigrationCheckUnavailable previously printed "run wsl --unregister OpenClawGateway" on the uncertain path, which would have destroyed exactly what the new branch just preserved. ReportStoreAppOwnsGateway now answers first.
    • Uninstall-LocalGateway.ps1 maps 11 explicitly 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 reach exit 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 OpenClawGateway distro and generated state in place on any machine where the Store app is deployed, including machines that never migrated, with no override switch. Under UninstallSilent() 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 contradicted Test-InnoMigration.ps1, which deliberately models Present, Absent, and Indeterminate separately for the same hive. It is now StorePackagePresence returning those three states, and a new ReportStoreAppStateUnknown preserves the gateway without claiming ownership and without handing over wsl --unregister. Preservation is also now the fall-through: only a positively observed Absent reaches 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:

    Uninstall dialog stating Setup could not check whether OpenClaw from the Microsoft Store is installed, that the local WSL gateway and its generated state were left in place, and offering only the in-app removal route

    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 names wsl --unregister.

Maintainer decisions recorded

  • Inno safeguards stay always-on, outside the STORE_MIGRATION_PREVIEW gate. 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.
  • The silent/unattended uninstall path keeps suppression by design. Checker uncertainty deliberately preserves the gateway rather than removing it, no uninstaller override is offered, and the removal path is Settings -> Local Gateway -> Remove Local Gateway, or 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

Responsibility Owner / invariant
Eligibility and admission InnoInstallationDetector, version policy, startup coordinator: no guessed source or production opt-in
Records and inventory MigrationRecordCodec, MigrationInventory, MigrationPreparation: bounded same-user state, atomic records, source state preserved
Cross-session exclusion MigrationOperationLock, InnoSourceActivityVerifier, startup guard: process-lifetime reader versus exclusive Store writer; uncertain process inspection blocks
Consent and completion Consent/preparation/completion coordinators: explicit informed consent, fresh inventory, canonical credential resolution
Uninstall preservation installer.iss, Test-InnoMigration.ps1, cleanup helper: preservation check and destructive work cannot race completion
Finalization Removal verifier/finalizer: verify removal, apply saved startup preference, delete receipt last
Bootstrap Narrow guards run before ordinary activation/settings/services; App only composes and retains lifetime ownership

GatewayRegistry, CredentialResolver, and AutoStartManager remain 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.0 minimum 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

  • Feature
  • Bug fix
  • Tests or validation
  • Docs or instructions
  • Security hardening

Scope

  • Tray or WinUI UX
  • Gateway, connection, or pairing
  • Setup or onboarding
  • Permissions, privacy, or security
  • Tests, CI, or docs

Required proof pools

  • windows-clean-installer-upgrade: passed, 8 of 8 cases on current head fa89486f. Every case reached exitCode 0 with 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 signed v2026.9.4 and a full current-head unsigned local Release Inno installer. Because the installer is unsigned, this is not shipping-signature proof. This matrix first ran on f91e6895, was rerun to completion on 71c91d05 (8 of 8, results-71c91d05-r1), was rerun on 78d83cba (8 of 8, results-78d83cba-r2), and was rerun again on current head fa89486f (8 of 8, results-fa89486f-r1, guest summary stage=complete, commit: fa89486f3932ddf57b8a6b22766172423ca5aaff).
  • windows-wsl-gateway-e2e: passed, 3 of 3 cases on 71c91d05. Real WSL2 distro registered, then removed by the shipped Uninstall-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

Command Result / provenance
.\build.ps1 Passed on final sources committed as 22db8274
dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore 4,087 passed; 33 skipped; 0 failed on 22db8274 sources
dotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore 3,086 passed; 0 failed on 22db8274 sources
dotnet test .\tests\OpenClaw.Connection.Tests\OpenClaw.Connection.Tests.csproj --no-restore 1,068 passed; 1 skipped; 0 failed on 22db8274 sources
Tray filter FullyQualifiedName~InnoMigrationContractTests|FullyQualifiedName~LocalizationValidationTests 38 passed, including plain Yes/No assertions
Six-locale format and action-legend regressions Seven checks failed against the old implementation, then passed with the fix
Full non-Dev Release x64 self-contained app and Inno installer Passed; local proof version 2026.9.22.0, rebuilt from committed 22db8274
Gated Debug x64 production-identity MSIX publish Passed; package 2026.9.20.0, built from committed 22db8274
Six-locale three-way XML entry comparison Passed; 2,477 keys per locale, including both parents' additions
git diff --check Passed

78d83cba P1 revalidation: .\build.ps1 passed; 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 the wsl --unregister advice and that the package identity is defined once. Both new installer tests are mutation checked: deleting the StoreAppRegistered guard block from installer.iss fails both, so they are not vacuous. installer.iss compiles clean under ISCC.

fa89486f revalidation: .\build.ps1 passed; 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. InnoMigrationContractTests passes 43 of 43. installer.iss compiles clean under ISCC, and that gate was verified non-vacuous by a deliberate Pascal mutation, which ISCC rejects with Identifier expected.
Earlier head 71c91d05 revalidation: .\build.ps1 passed; Shared 4,087 passed / 33 skipped; Tray 3,103 passed; 0 failed. The Tray count rises by one against f91e6895: a new contract test asserts that wsl.exe control flags are passed unquoted. It is verified non-vacuous, since the previous implementation turns --list into "--list" and fails it.

On f91e6895: .\build.ps1 passed; Shared 4,087 passed / 33 skipped; Tray 3,102 passed; Connection 1,104 passed / 1 skipped; 0 failed. Connection rises by 24 over e47197b6, covering foreign-owner rejection, generic-rights masks, inherit-only ACE handling, and receipt ownership.

Earlier head e47197b6 revalidation: .\build.ps1 passed; Shared 4,087 passed / 33 skipped; Connection 1,077 passed / 1 skipped; Tray 3,102 passed; 0 failed. ActivationRouterTests passed 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

UI scope: These native dialogs are intentional PR 1 preview surfaces for validating migration behavior and safety, not the final shipping design. PR 2 will replace the Store consent and close-Inno dialogs with dedicated migration UI and clearly labeled actions. The Inno-side blocked-startup guidance also needs explicit UX treatment in PR 2. Consent disclosures and startup safety checks will remain intact.

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.ps1 from an elevated host controller. The full non-Dev Release x64 self-contained unpackaged app was rebuilt from 1e504ceb, 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.

Merged-head Release Inno app blocked while another Windows session owns the exclusive migration lock

Actual packaged consent and migration journey

Commands: pwsh -NoProfile -File build-migration-startup-preview.ps1, then pwsh -NoProfile -File run-migration-startup-vm.ps1 -Resume.

  • Package: OpenClawFoundation.OpenClaw_2026.9.20.0_x64__rfcbke2p71se2, locally test-signed inside the VM and launched from its installed package location.
  • Clean build head: 22db82746143542f3d69f69813f6884b43a3b780. This is a post-commit rebuild, not a source-equivalence claim.
  • Unsigned package SHA-256: 1475B50CEDDA7AFE21DCF061064E9807E6DB18137FD4E9C9DA17E210E09DC036.
  • Inno fixture SHA-256: 6C98E61317C1BCB84D96DCA029DB0731424D3B075CB3FB52D3998E07F5890770. It uses current installer/codec/helper sources and a versioned test apphost, not an official released full installer.
  • Declining exited without creating intent/completion or changing synthetic settings/gateway catalog.
  • Accepting showed close-Inno/Retry, then wrote intent/completion only after release and validation. Restart retained the same receipt.
  • Actual silent Inno uninstall was refused with exit 1 under the exclusive lock, then succeeded after release while preserving state.
  • Store finalization applied saved AutoStart=false, cleared records, and continued normal packaged startup. Process identity/logs, not a normal-window screenshot, establish that final step.

Current-head packaged consent uses plain Yes and No without action legends and retains all migration disclosures

Current-head close-Inno guidance uses Yes to retry and No to exit without colon legends

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 SHA256 8260C3D0CBB4E33282A6DC684CC687677574D9983B15317EDE16079CD5BE6576, built from 71c91d05. Output: results-71c91d05-r1. The guest summary records commit: 71c91d059fd4c9865c70e3975bfaeadb4c99aaa0, so that matrix ran on 71c91d05 rather than a prior head.

Rerun on current head: the same matrix was rerun as 78d83cba-r2, payload pinned by manifest SHA256 55FA61C16836F24788062207B969F35F66E1BB4994416F79BE060B79541FE12F, built from 78d83cba. Output: results-78d83cba-r2. The guest summary records success: true, stage: complete, and commit: 78d83cba5661e75ae2f6217c5e0a0ec47f2b693f. All eight cases reached exitCode 0 with visibleCompletion: true, and Migration preservation check returned 0. appears in all eight uninstall logs. Rerun again on current head: fa89486f-r1, payload pinned by manifest SHA256 846562BD0AC64C787EB5450EC224A2843CA7C98432B0BAE0415016BCA9033098, built from fa89486f. Output: results-fa89486f-r1. The guest summary records success: true, stage: complete, commit: fa89486f3932ddf57b8a6b22766172423ca5aaff, all eight cases at exitCode 0 with visibleCompletion: true, Migration preservation check returned 0. in all eight uninstall logs, and restoration: 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 the 71c91d05-r1 run.

8 of 8 cases passed. Each case reached exitCode 0 with visibleCompletion: true under 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 eight uninstall.log files.

Case Kind AutoStart Gateway choice Exit Visible completion
fresh-autostart-false-retain fresh false retain 0 yes
fresh-autostart-false-remove fresh false remove 0 yes
fresh-autostart-true-retain fresh true retain 0 yes
fresh-autostart-true-remove fresh true remove 0 yes
upgrade-autostart-false-retain upgrade false retain 0 yes
upgrade-autostart-false-remove upgrade false remove 0 yes
upgrade-autostart-true-retain upgrade true retain 0 yes
upgrade-autostart-true-remove upgrade true remove 0 yes

Per-case evidence includes install logs, startup/registration state, UI screenshots, and UIA accessibility dumps for the uninstall confirmation and the gateway retain/remove choice.

Previous-release upgrade gateway removal prompt presented during uninstall

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.

Previous-release upgrade remove case completing successfully

upgrade-autostart-true-remove: the same previous-release upgrade path reaches visible successful completion, exit code 0, no error dialog.

The four remove cases each ran the real gateway cleanup helper end to end. The earlier e47197b6-r1 run exposed the pre-existing uninstall defect fixed in e47197b6: on the prior head the first remove case blocked on a could not remove the local WSL gateway dialog and timed out. The 71c91d05-r1 run confirms that fix stays in place, with the same case passing with no error dialog.

That run also demonstrated why the watchdog in 7aef6b20 matters: on a WSL-less host wsl.exe emits 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 remove cases 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-r7 against a disposable local Hyper-V guest with real WSL2 installed (wsl.2.7.14.0.x64.msi, SHA-256 DB084E53...). Output: results-gateway-proof-r7. The proof invokes the shipped scripts/Uninstall-LocalGateway.ps1 directly, which is exactly how Inno invokes it.

3 of 3 cases passed on 71c91d05.

Case What it proves Result
registered A genuinely registered distro is terminated, unregistered, and its install directory removed exit 0, stillRegistered: false, installDirRemoved: true
bystander Cleanup removes only the distro it was asked to remove unrelated OpenClawProofBystander still registered
idempotent A second run on already-clean state is not an error exit 0

Registration state is read from the Lxss registry, 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.exe call:

[2026-09-23T20:42:50.5586866-07:00] wsl.exe --list --quiet exited 127.
/bin/sh: --list: not found
[2026-09-23T20:42:50.9269799-07:00] wsl.exe --terminate OpenClawProofGateway exited 127.
/bin/sh: --terminate: not found
[2026-09-23T20:42:53.2837185-07:00] wsl.exe --unregister OpenClawProofGateway exited 127.
/bin/sh: --unregister: not found

6a8aeded on this branch replaced Start-Process -ArgumentList (an array, never quoted) with a ProcessStartInfo command string, and quoted every argument to build it. wsl.exe matches 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 --quiet exits 0 and lists distros, wsl.exe "--list" "--quiet" exits 127.

71c91d05 narrows ConvertTo-ProcessArgument to quote only values containing whitespace or quotes. After the fix, the same log reads:

[2026-09-23T20:54:26.8293752-07:00] wsl.exe --list --quiet exited 0.
OpenClawProofGateway
OpenClawProofBystander
[2026-09-23T20:54:27.0066321-07:00] wsl.exe --terminate OpenClawProofGateway exited 0.
[2026-09-23T20:54:29.2162236-07:00] wsl.exe --unregister OpenClawProofGateway exited 0.

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 --list into "--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.ps1 is invoked directly; SetupEngine ownership gating is covered by unit tests, not by this proof. The matrix cases above were rerun to completion on current head fa89486f (8 of 8, results-fa89486f-r1).

Actual foreign-principal receipt authority, six cases

Command: Invoke-ForeignReceiptProof.ps1 from an elevated host controller. The proof creates a real local principal, applies real ACLs, and runs the shipped Windows PowerShell 5.1 preservation checker. Exit 2 means "preserve the gateway", exit 0 means "cleanup authorized".

6 of 6 cases matched expectations.

Case Setup Expected Actual
clean No receipt, no foreign access 0 0
foreign-dacl Foreign principal can modify the state directory 2 2
foreign-owned Receipt genuinely owned by the foreign principal 2 2
residual Acknowledged residual: owner deletes and leaves no trace 0 0
foreign-read Foreign principal has read/list only 0 0
foreign-generic Foreign principal holds GENERIC_ALL 2 2

foreign-generic is the case that matters most: the raw mask on the ACE is 0x001F01FF,0x10000000, which intersects no named FileSystemRights bit, yet the kernel maps it to FILE_ALL_ACCESS. A naive named-bit check would have passed it as harmless.

Two limits are disclosed rather than hidden. residual returns 0 because 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 exit 2. 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-r2 against a disposable Hyper-V guest. Real current-head Inno install, a real registered WSL2 OpenClawGateway distro, and the real interactive uninstaller in both arms.

25 of 25 assertions passed.

Arm Receipt state Gateway prompt Checker Cleanup script Distro after
control-removed none appeared, answered Yes returned 0 invoked unregistered, directory removed
authority-refused owned by a foreign principal never appeared returned 2 never invoked still registered, directory intact

The 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 in foreign-receipt.json.

In the refusal arm the uninstaller logged its own decision and left recovery instructions:

Migration preservation check returned 2.
Migration preservation check unavailable: skipping destructive gateway cleanup.
The OpenClawGateway WSL distro and ...\wsl\OpenClawGateway were left in place.

The refusal is structural rather than a silently failed deletion: noWslCleanupLogExists count=0, so the cleanup script was never invoked and no wsl call was attempted at all. HKCU\...\Lxss still contains OpenClawGateway afterward, and the install directory is intact.

Control arm, the real destructive choice is offered, with No focused by default:

Control arm gateway removal prompt appears 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:

Authority-refused uninstall shows an OK-only warning and offers no removal choice

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 OpenClawGateway target, 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. Inno 2026.9.23.0 installed over a pre-existing gateway fixture, uninstalled with No to the local gateway prompt, then the current-head Store package installed and launched.

Check Result Establishes
fixture-had-state PASS, 7 files Non-vacuous. Real state existed to lose.
durable-state-byte-identical PASS No gateway, settings, or device-key file changed or was removed.
volatile-churn-reported 2 changed, 1 added openclaw-tray.log, Logs\diagnostics.jsonl, store-migration\prepare.lock.
distro-still-registered PASS Managed WSL distro survived uninstall.
distro-enterable PASS, openclaw-distro-ok Distro still boots, not merely registered.
store-distro-registered PASS Store build sees the same distro.

Store package verified as 2026.9.23.0, commit 71c91d059f..., sha256 52E9DF49.... Across the Store leg only openclaw-tray.log, Logs\diagnostics.jsonl, and ext4.vhdx changed; 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:

uninstall gateway prompt answered No

The current-head Store build lists the preserved gateway:

Store build adopted the preserved gateway

Final verdict, all checks pass:

final existing-state verdict

Limits. Credentials are synthetic and gateway.example.test does not resolve, so the Connection screenshot shows Authentication failed by 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 71c91d05 against 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.

Stage Result Establishes
Consent Preview dialog shown and accepted Migration is gated and explicit, not silent
Completion recorded completed.dpapi 694 b, intent.dpapi 6,550 b Receipt written only after consent
Inno uninstall Completed, no local gateway prompt Receipt suppressed the destructive prompt
gateways.json 130374774B before and after Gateway records byte identical
settings.json 33AEB8D49D before and after Settings byte identical
Device keys 3 keys unchanged, including A156E37B91 Pairing identity preserved
WSL distro OpenClawGateway-Dev still registered Uninstall did not touch the distro
Store adoption Connected, paired via device token, 1 client Preserved credential completed a real handshake
Finalization completed.dpapi and intent.dpapi removed Records cleaned only after Inno removal was verified

The missing gateway prompt is the designed path rather than luck. CheckCompletedStoreMigration returns MigrationResult = 10, which sets LocalGatewayCleanupRequested := False and exits before the prompt at installer.iss:354.

The Store app reports Install type: Packaged (MSIX) and version 2026.9.23.0, so the connected session is the Store package and not a surviving Inno process.

Consent dialog before any change

migration consent preview dialog

Inno uninstall completes with no local gateway prompt

inno uninstall complete, no gateway prompt

Store build connected using the preserved device token

store app connected via preserved device token

Packaged identity of the connected app

store app info, packaged MSIX 2026.9.23.0

Limits and disclosures.

  • Head. This ran before the completion-attestation fix. That fix changes only the receipt-absent branch of Test-InnoMigration.ps1. The path proven here is receipt-present (decode, then exit 10), which the fix leaves unchanged.
  • Node mode. The Store screenshot shows node mode disabled. Migration did not change this setting. settings.json is byte identical and contains EnableNodeMode: False, a value that was set about six minutes before migration began. The individual capability toggles (NodeCanvasEnabled, NodeScreenEnabled, NodeSystemRunEnabled) all carried over as True.
  • Distro ownership. The distro here is OpenClawGateway-Dev under OpenClawTray-Dev. The production uninstaller targets OpenClawGateway, so managed-distro ownership transfer is still not proven. This is a handoff and adoption proof, not an ownership-transfer proof.
  • Chat. Chat fails on this host with a Copilot HTTP 401 raised by the gateway's own token, predating this work and unrelated to migration.

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-presence in the fork, kept out of this PR's diff.

Environment: local Hyper-V guest OC-AUTO-A91896, user MigrationTest, non-elevated interactive uninstall. Real registered WSL distro OpenClawGateway at %LOCALAPPDATA%\OpenClawTray\wsl\OpenClawGateway. Real preview MSIX carrying the production identity OpenClawFoundation.OpenClaw 2026.9.23.0, built from 71c91d05, 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 change installer.iss, Test-InnoMigration.ps1, Uninstall-LocalGateway.ps1, and tests, and touches no file under src\. The Store side is byte-equivalent to current head.

Case A, regression. 8 of 8 passed. Ordinary uninstall, no Store package present.

Observation Value
Checker exit 0, cleanup authorized, unchanged
Gateway prompt appeared, with No as the focused default
After choosing No distro registered and enterable, install removed

Case B, the P1. 11 of 11 passed. Same machine, real migration performed through the Store app.

Step Checker exit Meaning
Migration prepared, completion not yet written 11 genuine mid-migration window, see below
Migration completed, completed.dpapi and intent.dpapi present 10 positive control: the checker does authorize on this machine
Entire %APPDATA%\OpenClawTray\store-migration directory deleted 11 the P1. Pre-fix this was 0.

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, with destructiveChanges=0 across 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.ps1 that the real uninstaller actually invoked. All runs observed B8F08113882F83C938A90C87DD23FE74B5DB861538883443A8CD81A22FC181E2, 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.vhdx is 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-*.json state 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

  • The installer/upgrade matrix passes 8 of 8 on current head fa89486f (see above), covering the Inno uninstall changes in installer.iss. Remaining gaps in that area: the installer under test is unsigned, and no logoff/reboot startup proof is claimed.
  • Matrix installation/uninstallation required an elevated test controller after the bundled VC runtime requested secure-desktop UAC. Apps ran with a limited token; UAC stayed enabled. First-time UAC interaction is not proven.
  • Independent structured review of the merged head: helper blocked before model invocation. ClawSweeper's accepted review applies to the preceding head.
  • Official signed x64/ARM64 artifacts, Store-distributed acceptance, and native ARM64 behavior.
  • Real managed-WSL preservation/continued management and live remote-only connectivity. Synthetic credentials, no live gateway handshake.
  • Runtime AutoStart=true, startup-platform refusal, real multi-user access-denied classification, and the complete interruption/failure matrix.
  • Full normal-launch/auto-start/openclaw: acceptance, shipping accessibility, non-English visual layout, and interactive Windows Settings uninstall. Fixture removal was silent Inno uninstall.
  • InitializeUninstall is 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.
  • New MCP discovery/invocation proof is N/A: no node capability or MCP surface changes.

Security Impact

  • New permissions/capabilities: No.
  • Secrets: Existing same-user state is read through canonical owners. Protected records contain metadata/hashes, not a credential export.
  • Network: No migration-specific network call or live-connectivity prerequisite.
  • Execution: Inno invokes the shipped read-only preservation checker and existing cleanup helper. No new remote execution or node-command surface.
  • Data access: Bounded reads and atomic record writes. Reparse paths, mismatched bindings, and ambiguous sources are rejected. Same-user protection does not defend against malicious code already running as that user.

Compatibility and Migration

  • Ordinary Store migration remains disabled. Preview requires explicit build opt-in, a test minimum, Debug packaging, and production identity. No runtime setting enables it.
  • State/payloads stay in place. Credentials retain existing ownership and precedence.
  • Explicit --uninstall --confirm-destructive remains deliberate full removal and is never invoked by migration.
  • Checker uncertainty deliberately preserves the local gateway and offers no uninstaller override, because at that point nothing on the machine distinguishes a broken check from a real migration. The uninstall still completes. The preserved distro and directory are named in the notice, in the uninstall log for silent runs, and in docs/uninstall-portable.md.
  • Production enablement and completion of Migrate from the Inno (.exe) app to the Store MSIX #1374 belong to PR 2 after a verified safeguard-containing Inno release and required acceptance evidence.

Review Conversations

Both reported code findings are addressed by e547733c and 46fd8b6e: 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.

natalie-aguinaldo and others added 8 commits September 21, 2026 14:25
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
@clawsweeper

clawsweeper Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Sep 22, 2026
@clawsweeper

clawsweeper Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codex review: blocked before merge. Reviewed September 25, 2026, 12:58 PM ET / 16:58 UTC (Revision 31).

ClawSweeper review

What this changes

The 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
Reviewed head: 33d283a1b0aa6cdd61a5af1719c0840c7106d534
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The branch has substantial focused code and real x64 behavior proof, while the unattended-uninstall contract and signed upgrade compatibility remain unresolved.
Proof confidence 🦞 diamond lobster (5/6) ✨ media proof bonus Sufficient (screenshot): Local Windows x64 proof exercises the introduced installer checker and gateway decision through real uninstall: a clean case retains the removal choice, while receipt loss with Store registration returns a preserve verdict and leaves the registered gateway enterable. The inspected paths are unchanged since that proof; signed-release and ARM64 upgrade acceptance remain separate gaps, so stored-state compatibility is not yet fully verified.
Patch quality 🦐 gold shrimp (3/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (screenshot): Local Windows x64 proof exercises the introduced installer checker and gateway decision through real uninstall: a clean case retains the removal choice, while receipt loss with Store registration returns a preserve verdict and leaves the registered gateway enterable. The inspected paths are unchanged since that proof; signed-release and ARM64 upgrade acceptance remain separate gaps, so stored-state compatibility is not yet fully verified.
Evidence reviewed 9 items Applicable repository policy: The full root AGENTS.md was read. Its architecture and proof guidance applies to the tray startup and uninstall changes; this read-only review made no code change requiring local validation.
Introduced uninstall contract: The introduced installer checks migration state before the existing silent-cleanup choice. A nonzero checker result suppresses gateway removal; only a zero result reaches automatic removal during silent uninstall.
Store-presence decision: With a missing completion receipt, the new checker preserves the gateway when the Store package is registered, including when no migration occurred.
Findings None None.
Security None None.

How this fits together

The 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]
Loading

Decision needed

Question Recommendation
Should an unattended Inno uninstall preserve the local gateway whenever the Store package is registered, even if no migration occurred and no uninstall override exists? Accept preservation: Approve the safety-first silent behavior and its documented operator recovery path, subject to signed upgrade acceptance.

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

  • Resolve merge risk (P1) - A registered Store package suppresses local gateway removal during unattended Inno uninstall even on a machine that never migrated. The uninstaller has no override or visible prompt in that path, so enterprise removal can leave the WSL distro and generated state behind without explicit maintainer acceptance of the changed contract.
  • Resolve merge risk (P1) - The always-on Inno safeguards have local x64 upgrade proof, but signed existing-user x64 and native ARM64 upgrade and recovery acceptance remains unverified.
  • Complete next step (P2) - Obtain maintainer approval of the unattended-uninstall preservation contract and signed x64/ARM64 existing-user upgrade acceptance before merge.
  • Resolve maintainer decision - Resolve the maintainer decision shown above before merge.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Code growth production +3,820/-38; tests +4,575/-0; docs +214/-0 The large production addition is scoped to the migration foundation and has substantial focused coverage, but raises the cost of upgrade review.
Changed paths 51 files Installer, tray, connection, localization, documentation, and tests all participate in the handoff.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #1374
Summary: The open migration issue defines the broader handoff; this PR supplies its gated foundation, while the earlier merged uninstall-choice PR defines a related existing contract.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge-risk options

Maintainer options:

  1. Approve the removal contract (recommended)
    Explicitly accept gateway preservation for unattended removal when Store registration exists, then complete signed x64 and ARM64 upgrade acceptance.
  2. Revise before release
    Design an approved narrower uninstall policy that still prevents gateway deletion during a pending or completed handoff.

Technical review

Best 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.

Labels

Label changes:

No label changes.

Label justifications:

  • P2: This is a bounded migration foundation with meaningful compatibility decisions before release.
  • merge-risk: 🚨 compatibility: Store registration changes unattended Inno gateway removal even when no migration completed.
  • merge-risk: 🚨 availability: The always-on startup and uninstall guards can block or alter ordinary installed-user recovery paths when migration state is unavailable.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (screenshot): Local Windows x64 proof exercises the introduced installer checker and gateway decision through real uninstall: a clean case retains the removal choice, while receipt loss with Store registration returns a preserve verdict and leaves the registered gateway enterable. The inspected paths are unchanged since that proof; signed-release and ARM64 upgrade acceptance remain separate gaps, so stored-state compatibility is not yet fully verified.
  • proof: sufficient: Contributor real behavior proof is sufficient. Local Windows x64 proof exercises the introduced installer checker and gateway decision through real uninstall: a clean case retains the removal choice, while receipt loss with Store registration returns a preserve verdict and leaves the registered gateway enterable. The inspected paths are unchanged since that proof; signed-release and ARM64 upgrade acceptance remain separate gaps, so stored-state compatibility is not yet fully verified.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. Local Windows x64 proof exercises the introduced installer checker and gateway decision through real uninstall: a clean case retains the removal choice, while receipt loss with Store registration returns a preserve verdict and leaves the registered gateway enterable. The inspected paths are unchanged since that proof; signed-release and ARM64 upgrade acceptance remain separate gaps, so stored-state compatibility is not yet fully verified.

Evidence

What I checked:

  • Applicable repository policy: The full root AGENTS.md was read. Its architecture and proof guidance applies to the tray startup and uninstall changes; this read-only review made no code change requiring local validation. (AGENTS.md:1, 33d283a1b0aa)
  • Introduced uninstall contract: The introduced installer checks migration state before the existing silent-cleanup choice. A nonzero checker result suppresses gateway removal; only a zero result reaches automatic removal during silent uninstall. (installer.iss:434, 33d283a1b0aa)
  • Store-presence decision: With a missing completion receipt, the new checker preserves the gateway when the Store package is registered, including when no migration occurred. (scripts/Test-InnoMigration.ps1:300, 33d283a1b0aa)
  • Earlier uninstall behavior: Current main automatically requests local gateway cleanup during silent Inno uninstall. The merged uninstall-choice work is Add uninstall choice for local WSL gateway #591. (installer.iss:184, 7d92747e193c)
  • Main-branch necessity: The migration guard, checker, and Store-presence path are absent from fetched main; no release tag contains the reviewed head. (7d92747e193c)
  • Current-head continuity: The final merge commit has the prior reviewed head and fetched main as parents. The inspected migration and uninstall source paths have no diff from the prior reviewed head. (33d283a1b0aa)

Likely related people:

  • ranjeshj: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • Natalie Aguinaldo: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • karkarl: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Obtain explicit maintainer acceptance of the unattended-uninstall contract.
  • Provide signed existing-state x64 and ARM64 upgrade and recovery results.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (30 earlier review cycles; latest 8 shown)
  • reviewed 2026-09-24T06:22:01.345Z sha 71c91d0 :: needs real behavior proof before merge. :: [P1] Keep lost completion receipts from authorizing gateway cleanup
  • reviewed 2026-09-24T07:07:01.391Z sha 71c91d0 :: needs real behavior proof before merge. :: [P1] Keep lost completion receipts from authorizing gateway removal
  • reviewed 2026-09-24T17:45:54.302Z sha 78d83cb :: blocked before merge. :: none
  • reviewed 2026-09-24T18:08:55.568Z sha 78d83cb :: blocked before merge. :: none
  • reviewed 2026-09-24T19:03:30.382Z sha 78d83cb :: blocked before merge. :: [P2] Describe an unreadable package registry as uncertain
  • reviewed 2026-09-24T19:42:51.193Z sha fa89486 :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-24T20:01:53.320Z sha fa89486 :: blocked before merge. :: none
  • reviewed 2026-09-24T20:18:03.871Z sha fa89486 :: blocked before merge. :: none

@clawsweeper clawsweeper Bot added status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed proof: sufficient Contributor real behavior proof is sufficient. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Sep 22, 2026
natalie-aguinaldo and others added 2 commits September 22, 2026 12:14
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
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. labels Sep 22, 2026
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
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Sep 22, 2026
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
natalie-aguinaldo and others added 3 commits September 23, 2026 14:29
…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
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Sep 23, 2026
natalie-aguinaldo and others added 3 commits September 23, 2026 17:38
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
@natalie-aguinaldo

Copy link
Copy Markdown
Contributor Author

P1 VM proof run: verdicts and screenshots

Covers 78d83cba. Guest OC-AUTO-A91896, real registered OpenClawGateway distro, real preview MSIX with production identity OpenClawFoundation.OpenClaw 2026.9.23.0. Every run confirmed the real uninstaller invoked the committed checker by hash (B8F08113...81E2), so neither case is vacuous.

Case A, regression: 8 of 8 passed

Check Result Detail
fixture-has-distro PASS registered=True base=C:\Users\MigrationTest\AppData\Local\OpenClawTray\wsl\OpenClawGateway
fixture-has-no-receipt PASS receiptDirExists=False
fixture-has-no-store-package PASS matching=
repository-populated-but-unrelated PASS readable=True total=134
checker-authorizes-cleanup PASS exit=0 (expected 0)
gateway-prompt-appeared PASS observed=True
inno-removed PASS installPresent=False
installed-checker-is-fixed-build PASS expected=B8F08113882F83C938A90C87DD23FE74B5DB861538883443A8CD81A22FC181E2 observed=B8F08113882F83C938A90C87DD23FE74B5DB861538883443A8CD81A22FC181E2

Case B, package-presence security: 11 of 11 passed

Check Result Detail
migration-completed PASS entries=completed.dpapi,intent.dpapi,prepare.lock
receipt-authority-before-tamper PASS exit=10 (expected 10, confirmed migration)
store-package-present PASS matching=OpenClawFoundation.OpenClaw_2026.9.23.0_x64__rfcbke2p71se2
receipt-actually-deleted PASS exists=False entries=
checker-refuses-after-receipt-loss PASS exit=11 (expected 11, Store package present, preserve)
no-gateway-prompt PASS promptAppeared=False (expected false)
inno-removed PASS installPresent=False
gateway-survived-registration PASS registered=True base=C:\Users\MigrationTest\AppData\Local\OpenClawTray\wsl\OpenClawGateway
gateway-survived-enterable PASS enterable=True exit=0
state-not-destroyed PASS preservedFiles=4; destructiveChanges=0;
installed-checker-is-fixed-build PASS expected=B8F08113882F83C938A90C87DD23FE74B5DB861538883443A8CD81A22FC181E2 observed=B8F08113882F83C938A90C87DD23FE74B5DB861538883443A8CD81A22FC181E2

Screenshots

Case A: ordinary uninstall still shows the gateway prompt, with No as the focused default.

case-a-gateway-prompt-no-default.png

Case A verdict: 8 of 8 passed.

case-a-verdict-8-pass.png

Case B positive control: real completed.dpapi present, checker exits 10. Proves the checker can still authorize on this machine.

case-b-07-positive-control-exit10.png

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-08-P1-receipt-deleted-exit11.png

Case B: no gateway prompt. The new dialog states the Store app owns the gateway and warns against running wsl --unregister.

case-b-09-store-owns-gateway-dialog.png

Case B verdict: 11 of 11 passed.

case-b-10-verdict-11-pass.png

All 18 captures and the raw fingerprint JSON live on the evidence-only branch proof/p1-package-presence in the fork, deliberately outside this PR's diff.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Sep 24, 2026
@natalie-aguinaldo natalie-aguinaldo changed the title feat(migration): add gated Inno-to-Store migration preview feat(migration): gate Inno-to-Store migration preview and preserve the gateway on uninstall Sep 24, 2026
@clawsweeper clawsweeper Bot added status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Sep 24, 2026
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
@clawsweeper clawsweeper Bot added status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. proof: sufficient Contributor real behavior proof is sufficient. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed proof: sufficient Contributor real behavior proof is sufficient. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Sep 24, 2026
@natalie-aguinaldo

Copy link
Copy Markdown
Contributor Author

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

@karkarl

karkarl commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Adversarial review verdict

No confirmed actionable defects after independent reviews by Claude Opus 4.7 and GPT-5.3-Codex, followed by source-level cross-reference.

Reviewed commit: fa89486f3932ddf57b8a6b22766172423ca5aaff against merge base c7063081a2a5dd096d79a308f992db141cf0a7f9. This verdict applies to that revision, not any subsequent changes.

Both models agree: HIGH consensus

No jointly identified defects. Codex reported no actionable findings.

Opus-only observations: LOW consensus

The severities below are the reviewer's initial classifications, not confirmed defects.

Observation Opus severity Cross-reference assessment
Finalization errors could be mislabeled as lock failures MEDIUM Dismissed: current inventory, autostart, cleanup, and record-reading paths handle the cited exceptions separately.
Inno startup continues after non-contention lock failures MEDIUM Unconfirmed: deliberate availability policy; no concrete concurrent-writer bypass demonstrated.
Read-only foreign ACL grants trigger hardening MEDIUM Unconfirmed compatibility risk: stricter repair policy is intentional; failure under enterprise policy was not reproduced.
Inventory sanitization excludes InvalidDataException LOW Dismissed: current validators use fixed, non-sensitive messages. Concern depends on future changes.
Watchdog child does not receive TimeoutSeconds LOW Dismissed: child uses -NoWatchdog; parent enforces and reports the supplied timeout.
Uninstall path rejection lacks an immediate error dialog LOW Dismissed: subsequent preservation handling displays the interactive warning.

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

  • Connection tests filtered to FullyQualifiedName~Migration|FullyQualifiedName~Inno: 366 passed, 1 skipped, 0 failed. The skip was an unrelated cross-volume Local AI migration test.
  • Tray tests filtered to FullyQualifiedName~InnoMigrationContractTests, with isolated settings: 43 passed, 0 failed.
  • No full application build, live Store migration, or destructive uninstall proof was performed. This review is not a substitute for those release/proof gates.

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.
@karkarl
karkarl merged commit 5a59535 into openclaw:main Sep 25, 2026
39 of 41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants