windowsweep 1.3.2 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,215 @@
1
+ # self_test_1_4.ps1 - self-test group [19]: the P9 behaviour found on a real machine (P9, 2026-11-02).
2
+ # Called by Invoke-SelfTestExtra with its fixture root; verdict lines only. ASCII only.
3
+
4
+ function New-AgedFiles {
5
+ <# .SYNOPSIS Create Count files under Dir whose write and creation times are WrittenDaysAgo old; the first
6
+ FreshReads of them were "read" just now, the rest were read when written. Returns the directory. #>
7
+ param([string] $Dir, [int] $Count, [int] $WrittenDaysAgo, [int] $FreshReads)
8
+ New-Item -ItemType Directory -Force -Path $Dir | Out-Null
9
+ $old = [datetime]::UtcNow.AddDays(-1 * $WrittenDaysAgo)
10
+ for ($i = 0; $i -lt $Count; $i++) {
11
+ $f = Join-Path $Dir ("f{0:D3}.bin" -f $i)
12
+ [IO.File]::WriteAllText($f, 'x')
13
+ [IO.File]::SetCreationTimeUtc($f, $old)
14
+ [IO.File]::SetLastWriteTimeUtc($f, $old)
15
+ if ($i -lt $FreshReads) { [IO.File]::SetLastAccessTimeUtc($f, [datetime]::UtcNow) } else { [IO.File]::SetLastAccessTimeUtc($f, $old) }
16
+ }
17
+ return $Dir
18
+ }
19
+
20
+ function Invoke-SelfTest14 {
21
+ <# .SYNOPSIS Run group [19]. Returns an object with Checks and Fails counts. #>
22
+ param([string] $Fixture)
23
+ $checks = 0; $fails = 0
24
+ $root = Join-Path $Fixture 'g19'
25
+ Reset-AccessVerdicts
26
+ try {
27
+ Write-Section '[19] P9: idle basis when last-access is live (D82), one-line --json, orphan claims'
28
+
29
+ # 19a - the verdict rule itself
30
+ $checks++
31
+ $a = Test-AccessSampleLive -Old 30 -FreshAccess 28
32
+ $b = Test-AccessSampleLive -Old 30 -FreshAccess 20
33
+ $c = Test-AccessSampleLive -Old 10 -FreshAccess 10
34
+ if ($a -and -not $b -and -not $c) { Write-Ok 'live verdict: 28 of 30 old files read today is live; 20 of 30 is not; 10 files are too few to judge' } else { Write-Err "live verdict wrong: 28/30=$a 20/30=$b 10/10=$c"; $fails++ }
35
+
36
+ # 19b - a root where every old file was "read" today: the idle gate falls back to write time
37
+ $checks++
38
+ $live = New-AgedFiles -Dir (Join-Path $root 'live') -Count 30 -WrittenDaysAgo 60 -FreshReads 30
39
+ $isLive = Test-LastAccessLive -Root $live
40
+ # Assigned directly: @() around a member-accessed List[object] throws "Argument types do not match" on PS 5.1.
41
+ $stale = (Get-StaleFiles -Root $live -Days 30).Files
42
+ if ($isLive -and $stale.Count -eq 30) { Write-Ok 'live last-access: the root is detected and all 30 files written 60 days ago are idle' } else { Write-Err "live root: detected=$isLive stale=$($stale.Count) of 30"; $fails++ }
43
+
44
+ # 19c - an honest root (read when written): behaviour unchanged
45
+ $checks++
46
+ $honest = New-AgedFiles -Dir (Join-Path $root 'honest') -Count 30 -WrittenDaysAgo 60 -FreshReads 0
47
+ $isLive = Test-LastAccessLive -Root $honest
48
+ $stale = (Get-StaleFiles -Root $honest -Days 30).Files
49
+ if (-not $isLive -and $stale.Count -eq 30) { Write-Ok 'honest last-access: not flagged, and the 30 old files are idle as before' } else { Write-Err "honest root: detected=$isLive stale=$($stale.Count) of 30"; $fails++ }
50
+
51
+ # 19d - a mixed root: the files really read recently are still kept (the conservative rule holds)
52
+ $checks++
53
+ $mixed = New-AgedFiles -Dir (Join-Path $root 'mixed') -Count 30 -WrittenDaysAgo 60 -FreshReads 5
54
+ $isLive = Test-LastAccessLive -Root $mixed
55
+ $stale = (Get-StaleFiles -Root $mixed -Days 30).Files
56
+ if (-not $isLive -and $stale.Count -eq 25) { Write-Ok 'mixed root: 5 of 30 read today is a real signal - those 5 are kept, 25 are idle' } else { Write-Err "mixed root: detected=$isLive stale=$($stale.Count) (expected 25)"; $fails++ }
57
+
58
+ # 19e - a root too small to judge: conservative alone, live only when its volume was found live
59
+ $checks++
60
+ Reset-AccessVerdicts
61
+ $small1 = New-AgedFiles -Dir (Join-Path $root 'small1') -Count 3 -WrittenDaysAgo 60 -FreshReads 3
62
+ $idleAlone = Get-IdleDays $small1
63
+ $null = Test-LastAccessLive -Root $live
64
+ $small2 = New-AgedFiles -Dir (Join-Path $root 'small2') -Count 3 -WrittenDaysAgo 60 -FreshReads 3
65
+ $idleInherited = Get-IdleDays $small2
66
+ if ($idleAlone -eq 0 -and $idleInherited -ge 59) { Write-Ok 'small root: 0 days idle on its own, 60 once its volume is known to have live last-access' } else { Write-Err "small root: alone=$idleAlone inherited=$idleInherited"; $fails++ }
67
+
68
+ # 19g - the --json summary names the volume whose idle age used write/creation time
69
+ $checks++
70
+ $vol = (Get-VolumeKey $live).TrimEnd('\').ToUpperInvariant()
71
+ $doc = (Get-JsonSummary) | ConvertTo-Json -Depth 5 -Compress
72
+ $named = @(($doc | ConvertFrom-Json).access_live_volumes)
73
+ if ($named -contains $vol -and -not $doc.Contains("`n")) { Write-Ok "--json names the live-access volume ($vol) in access_live_volumes, still on one line" } else { Write-Err "access_live_volumes wrong: [$($named -join ',')] expected $vol"; $fails++ }
74
+
75
+ # 19h-19l - section 23 claims learned from the P52 (every case erred towards calling a live folder orphaned)
76
+ $set = New-Object System.Collections.Generic.HashSet[string]([StringComparer]::Ordinal)
77
+ foreach ($name in 'windowsweep', 'DBeaverCorp.DBeaverCE', 'Internet Download Manager') { Add-MatchTokens -Set $set -Text $name }
78
+ Add-CommandLineTokens -Set $set -CommandLine 'node C:/Users/x/.claude/plugins/cache/superpowers-chrome/3.0.2/mcp/dist/index.js'
79
+ $index = [pscustomobject]@{ Tokens = $set; RegistryCount = 3; Rows = @() }
80
+ $checks++
81
+ $dnsClaimed = -not (Test-OrphanFolderName -Name 'com.aoneahsan.windowsweep' -Index $index)
82
+ $dnsOrphan = Test-OrphanFolderName -Name 'com.example.zqxwvu' -Index $index
83
+ if ($dnsClaimed -and $dnsOrphan) { Write-Ok 'reverse-DNS data folder: com.aoneahsan.windowsweep is claimed by "windowsweep"; an unknown app id stays an orphan' } else { Write-Err "reverse-DNS claim wrong: claimed=$dnsClaimed unknownIsOrphan=$dnsOrphan"; $fails++ }
84
+ $checks++
85
+ $stem = -not (Test-OrphanFolderName -Name 'DBeaverData' -Index $index)
86
+ $other = Test-OrphanFolderName -Name 'ChessBase' -Index $index
87
+ if ($stem -and $other) { Write-Ok 'vendor stem: DBeaverData is claimed by the DBeaverCorp.DBeaverCE Store app; ChessBase is not' } else { Write-Err "vendor stem wrong: DBeaverData claimed=$stem ChessBase orphan=$other"; $fails++ }
88
+ $checks++
89
+ if (-not (Test-OrphanFolderName -Name 'IDM' -Index $index)) { Write-Ok 'acronym: IDM is claimed by Internet Download Manager' } else { Write-Err 'acronym claim missing: IDM offered as orphaned'; $fails++ }
90
+ $checks++
91
+ $bare = New-Object System.Collections.Generic.HashSet[string]([StringComparer]::Ordinal)
92
+ Add-MatchTokens -Set $bare -Text 'windowsweep'
93
+ Add-CommandLineTokens -Set $bare -CommandLine 'bash -c "grep -c vega-desktop-updater log.txt"'
94
+ $bareIndex = [pscustomobject]@{ Tokens = $bare; RegistryCount = 1; Rows = @() }
95
+ $pathClaims = -not (Test-OrphanFolderName -Name 'superpowers' -Index $index)
96
+ $wordIgnored = Test-OrphanFolderName -Name 'vega-desktop-updater' -Index $bareIndex
97
+ if ($pathClaims -and $wordIgnored) { Write-Ok 'command line: a folder in a running PATH (superpowers) is claimed; a bare word in a shell command line claims nothing' } else { Write-Err "command-line claim wrong: pathClaims=$pathClaims bareWordIgnored=$wordIgnored"; $fails++ }
98
+ $checks++
99
+ $why = Get-ProtectionReason (Join-Path $env:LOCALAPPDATA 'com.aoneahsan.windowsweep\EBWebView')
100
+ if ($why) { Write-Ok "the desktop app's own data folder is protected ($why)" } else { Write-Err "DANGEROUS: the desktop app's data folder is not protected"; $fails++ }
101
+
102
+ # 19f - nothing a section emits can reach --json stdout
103
+ $checks++
104
+ $runner = Get-Content -LiteralPath (Join-Path $Script:WS_ROOT 'modules\runner.ps1') -Raw
105
+ $admin = Get-Content -LiteralPath (Join-Path $Script:WS_ROOT 'modules\system_admin.ps1') -Raw
106
+ $runnerOk = $runner -match '\$null = & \$sec\.Fn'
107
+ $doLines = @($admin -split "`n" | Where-Object { $_ -match 'Delete-DeliveryOptimizationCache -Force -ErrorAction' })
108
+ $doOk = ($doLines.Count -ge 1) -and (@($doLines | Where-Object { $_ -notmatch '\*>&1' }).Count -eq 0)
109
+ if ($runnerOk -and $doOk) { Write-Ok 'section output is discarded by the runner, and the Delivery Optimization cmdlet is captured on every stream' } else { Write-Err "stdout guards missing: runner=$runnerOk deliveryOptimization=$doOk"; $fails++ }
110
+ } catch {
111
+ Write-Err "group [19] crashed: $($_.Exception.Message) | at $(($_.ScriptStackTrace -split "`n" | Select-Object -First 3) -join ' <- ')"; $fails++
112
+ } finally {
113
+ Reset-AccessVerdicts
114
+ try { if (Test-Path -LiteralPath $root) { [IO.Directory]::Delete((Get-LongPath $root), $true) } } catch { $null = $_ }
115
+ }
116
+ return [pscustomobject]@{ Checks = $checks; Fails = $fails }
117
+ }
118
+
119
+ function Invoke-SelfTest21 {
120
+ <# .SYNOPSIS Group [21]: section 29 (the Chrome on-device AI policy) - never written by a dry-run, never batched. #>
121
+ $checks = 0; $fails = 0
122
+ $ws = $Script:WS
123
+ $savedDry = $ws.DryRun
124
+ $key = 'HKCU:\Software\windowsweep-selftest-' + [guid]::NewGuid().ToString('N')
125
+ try {
126
+ Write-Section '[21] P9: the Chrome on-device AI policy (section 29)'
127
+ # 21a - a dry-run prints the change and writes nothing
128
+ $checks++
129
+ $ws.DryRun = $true
130
+ $dry = Set-BrowserAiPolicy -KeyPath $key
131
+ if ($dry -eq 'would-set' -and -not (Test-Path -LiteralPath $key)) { Write-Ok 'dry-run: the policy is described, no registry key is written' } else { Write-Err "dry-run wrote the policy or answered '$dry'"; $fails++ }
132
+ # 21b - a real run writes exactly the value Chrome reads (on a throwaway key, never the machine policy)
133
+ $checks++
134
+ $ws.DryRun = $false
135
+ $real = Set-BrowserAiPolicy -KeyPath $key
136
+ $state = Get-BrowserAiPolicyState -KeyPath $key
137
+ if ($real -eq 'set' -and $state -eq 1) { Write-Ok "a real run sets $($Script:WS_BROWSERAI_POLICY_VALUE) = 1" } else { Write-Err "policy write wrong: result='$real' value='$state'"; $fails++ }
138
+ # 21c - it changes a browser's configuration: deep, admin, config tier, in no batch or profile
139
+ $checks++
140
+ $s = Get-Section 29
141
+ $batched = @($Script:WS_SAFE_BATCH + $Script:WS_SAFE_BATCH_ADMIN + @($Script:WS_PROFILES.Values | ForEach-Object { $_ }) + $Script:WS_WALKTHROUGH + $Script:WS_WALKTHROUGH_ADMIN) -contains 29
142
+ if ($s -and $s.Batch -eq 'deep' -and $s.Admin -and $s.Tier -eq 'config' -and -not $batched) { Write-Ok 'section 29 is deep, admin and config, and no batch, profile or walkthrough runs it' } else { Write-Err "section 29 contract wrong: batch=$($s.Batch) admin=$($s.Admin) tier=$($s.Tier) batched=$batched"; $fails++ }
143
+ } catch {
144
+ Write-Err "group [21] crashed: $($_.Exception.Message) | at $(($_.ScriptStackTrace -split "`n" | Select-Object -First 3) -join ' <- ')"; $fails++
145
+ } finally {
146
+ $ws.DryRun = $savedDry
147
+ try { if (Test-Path -LiteralPath $key) { Remove-Item -LiteralPath $key -Recurse -Force } } catch { $null = $_ }
148
+ }
149
+ return [pscustomobject]@{ Checks = $checks; Fails = $fails }
150
+ }
151
+
152
+ function Invoke-SelfTest20 {
153
+ <# .SYNOPSIS Group [20]: section 28 (superseded releases, Node pins) and the D79 exception rules. #>
154
+ param([string] $Fixture)
155
+ $checks = 0; $fails = 0
156
+ $root = Join-Path $Fixture 'g20'
157
+ $savedExclude = $Script:WS_EXCLUDE
158
+ try {
159
+ Write-Section '[20] P9: superseded releases (section 28) and narrow exceptions (D79)'
160
+ $rel = Join-Path $root 'codex\releases'
161
+ foreach ($v in '0.1.0-x86_64-pc-windows-msvc', '0.2.0-x86_64-pc-windows-msvc', '0.3.0-x86_64-pc-windows-msvc', '0.4.0-x86_64-pc-windows-msvc', 'notes') { New-Item -ItemType Directory -Force -Path (Join-Path $rel $v) | Out-Null }
162
+ $link = Join-Path $root 'codex\current'
163
+
164
+ # 20b first, while the link is absent - no readable current release means nothing is dropped
165
+ $checks++
166
+ $none = Get-CodexReleasePlan -Root $rel -CurrentLink $link
167
+ if ($none.Reason -and @($none.Drop).Count -eq 0) { Write-Ok 'no current-release link: nothing is offered (fail closed)' } else { Write-Err "missing link not fail-closed: drop=[$($none.Drop -join ',')]"; $fails++ }
168
+
169
+ # 20a - current 0.3.0 keeps itself, the newer 0.4.0, the rollback 0.2.0 and the odd folder; only 0.1.0 goes
170
+ $checks++
171
+ New-Item -ItemType Junction -Path $link -Target (Join-Path $rel '0.3.0-x86_64-pc-windows-msvc') | Out-Null
172
+ $plan = Get-CodexReleasePlan -Root $rel -CurrentLink $link
173
+ $dropNames = @($plan.Drop | ForEach-Object { Split-Path -Leaf $_ })
174
+ $keepNames = @($plan.Keep | ForEach-Object { Split-Path -Leaf $_ } | Sort-Object)
175
+ if (-not $plan.Reason -and ($dropNames -join ',') -eq '0.1.0-x86_64-pc-windows-msvc' -and $keepNames.Count -eq 4) { Write-Ok 'Codex plan: current, newer, rollback and an odd folder kept; only the oldest release goes' } else { Write-Err "Codex plan wrong: drop=[$($dropNames -join ',')] keep=[$($keepNames -join ',')] reason='$($plan.Reason)'"; $fails++ }
176
+
177
+ # 20c - the live exceptions are exactly the live plan's Drop folders, and the kept ones stay protected
178
+ $checks++
179
+ $live = Get-CodexReleasePlan
180
+ $codexPrefix = (Join-Path $Script:P.U '.codex') + '\'
181
+ $ex = @($Script:WS_PROTECT.Exceptions | Where-Object { $_.StartsWith($codexPrefix, [StringComparison]::OrdinalIgnoreCase) } | Sort-Object)
182
+ $want = @($live.Drop | Sort-Object)
183
+ $keptOpen = @($live.Keep | Where-Object { -not (Get-ProtectionReason $_) })
184
+ if ((($ex -join '|') -eq ($want -join '|')) -and $keptOpen.Count -eq 0) { Write-Ok "D79: $($ex.Count) Codex exception(s), exactly the superseded releases; every kept release still refused" } else { Write-Err "D79 exceptions wrong: exceptions=[$($ex -join ',')] plan=[$($want -join ',')] keptButAllowed=[$($keptOpen -join ',')]"; $fails++ }
185
+
186
+ # 20d - an exception never lifts the user's own --exclude-path
187
+ $checks++
188
+ $tempEx = $Script:WS_PROTECT.Exceptions | Select-Object -First 1
189
+ $inside = Join-Path $tempEx 'probe.bin'
190
+ $open = Get-ProtectionReason $inside
191
+ $Script:WS_EXCLUDE = @{ Prefixes = @($tempEx.TrimEnd('\') + '\'); Roots = @($tempEx) }
192
+ $closed = Get-ProtectionReason $inside
193
+ if (-not $open -and $closed -like 'excluded: *') { Write-Ok 'inside an exception, the path is allowed - until the user excludes it' } else { Write-Err "exception vs exclusion wrong: open='$open' closed='$closed'"; $fails++ }
194
+ $Script:WS_EXCLUDE = $savedExclude
195
+
196
+ # 20e - Node: the active version, exact pins and the newest of a pinned major stay; nothing else is needed
197
+ $checks++
198
+ $pins = @('24.13.0', '24', '22', '20', 'v24.16.0' | ForEach-Object { ConvertTo-NodePin $_ })
199
+ $np = Get-NodeVersionPlan -Installed @('v22.22.0', 'v24.10.0', 'v24.13.0') -Active 'v24.13.0' -Pins $pins
200
+ $odd = Get-NodeVersionPlan -Installed @('v22.22.0', 'v24.10.0') -Active 'v22.22.0' -Pins @((ConvertTo-NodePin 'banana'))
201
+ if (($np.Drop -join ',') -eq 'v24.10.0' -and @($np.Keep).Count -eq 2 -and @($np.Unmet).Count -eq 2 -and $odd.Reason -and @($odd.Drop).Count -eq 0) { Write-Ok 'Node plan: only v24.10.0 is unrequested; 20 and 24.16.0 are reported as missing; an unreadable request keeps everything' } else { Write-Err "Node plan wrong: drop=[$($np.Drop -join ',')] keep=[$($np.Keep -join ',')] unmet=[$($np.Unmet -join ',')] oddDrop=[$($odd.Drop -join ',')]"; $fails++ }
202
+
203
+ # 20f - version requests as projects write them
204
+ $checks++
205
+ $kinds = @('v24.16.0', '24', '>=22.12.0', '^24', 'lts/*', 'banana' | ForEach-Object { $n = ConvertTo-NodePin $_; "$($n.Kind):$($n.Major)" })
206
+ if (($kinds -join ' ') -eq 'exact:24 major:24 major:22 major:24 newest:0 unknown:0') { Write-Ok 'version requests: exact, major, ranges, lts and nonsense each read correctly' } else { Write-Err "version request parsing wrong: $($kinds -join ' ')"; $fails++ }
207
+ } catch {
208
+ Write-Err "group [20] crashed: $($_.Exception.Message) | at $(($_.ScriptStackTrace -split "`n" | Select-Object -First 3) -join ' <- ')"; $fails++
209
+ } finally {
210
+ $Script:WS_EXCLUDE = $savedExclude
211
+ try { $j = Join-Path $root 'codex\current'; if (Test-Path -LiteralPath $j) { [IO.Directory]::Delete($j, $false) } } catch { $null = $_ }
212
+ try { if (Test-Path -LiteralPath $root) { [IO.Directory]::Delete((Get-LongPath $root), $true) } } catch { $null = $_ }
213
+ }
214
+ return [pscustomobject]@{ Checks = $checks; Fails = $fails }
215
+ }