fix: four defects an adversarial audit found in the merged v1.44 work - #392
Merged
Conversation
All three are mine, from #388/#389. 1. TWENTY-SIX HEALTHY COLLECTORS WERE LABELLED "No data returned" `CollectorDataKind` has three cases and I applied a counter predicate to all of them. `.credits` is "a credit-based balance"; `.statusOnly` is "local status only, no quota model". Those collectors put their payload in `status_text` — Groq returns "12.5 req/min · 4300 tok/min", OpenRouter "$12.34 / $50.00" — and hardcode today/week/quota/remaining to zero-or-nil on their SUCCESS path. So `classify` called them `.ranButEmpty`, and the provider card rendered a permanent orange "No data returned — the check ran but came back empty" directly underneath the live numbers it had just fetched. 16 `.statusOnly` + 10 `.credits` collectors. Two knock-ons from the same call: `producedValue` requires a `.producedData`, so a user whose enabled providers are all credits/status-only would be shown real numbers and never get the W5 login item; and every report to `devices.collector_status` logged them "empty", poisoning the silent-zero diagnostic this release exists to power. I excluded `status_text` from the predicate on purpose — for quota providers it is always populated and made the check a tautology. That was right for `.quota` and wrong to generalise. The fix trusts the collector's own declared kind rather than reintroducing a string test. 2. A CANCELLED REFRESH PUBLISHED ITS RESULTS `requestRefresh` cancels the in-flight task; that propagates into every collector and `URLSession` throws `URLError(.cancelled)` (-999), which `categorize` correctly reads as `.network`. Neither publish site checked cancellation, so a refresh the user themselves superseded — ⌘R, which is not gated on `isLoading`, or the helper's sync notification — painted every network-backed provider red. The replacement pass then returns early on `isLoading`, so nothing repaired it until the next 60-120s tick. The telemetry Task spawned from the same callback is unstructured and does NOT inherit cancellation, so it uploaded `failed_network` for the whole fleet row and cached it as last-reported. 3. `brew uninstall --zap` LEFT EVERYTHING BEHIND The cask listed "~/Library/Application Support/CLI Pulse". The app writes "CLIPulse", no space — `DailyUsageArchive.defaultRoot()` appends that literal, and the pet ledger/state share the directory. Verified on this machine: the real path holds 60K of usage history and pet state; the path the cask named does not exist at all. So `--zap` reported success and removed nothing, and a reinstall came back with every day of history intact. Also missing: ~/Library/Preferences/group.yyh.CLI-Pulse.plist. The app-group defaults suite backs to a plist NEXT TO the group container, not inside it, and unsandboxed (which is what brew installs) that is where provider configs, helper status and cached collector results live. All four paths in the new list verified present on disk. Mutation-verified: removing the `dataKind` guard reddens exactly the two new classification tests. CLIPulseCore 2273 passed / 0 failed (default), 2308 / 0 (-DDEVID_BUILD); macOS + iOS + watchOS BUILD SUCCEEDED; `brew style --cask` clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…inding 4)
`.onChange(of: launchAtLogin) { _ in LaunchAtLogin.toggle() }` threw the new
binding value away, and `toggle()` inferred direction from `isEnabled` — the
LIVE status — while the switch renders from a `@State` snapshot taken when
SettingsTab was built (SettingsTab.swift:13).
Once anything changes registration behind that snapshot the control inverts:
switch reads OFF, reality is ON, user taps to turn it "on", inferred direction
unregisters instead. And since the tap also writes `userTouchedToggleKey`, W5
is locked out and never repairs it.
The inference is pre-existing — SettingsTab and AdvancedSection are unchanged
since f9f15bc, and both refuters correctly said so. What is new is that W5
registers in the background mid-session, so a user who opens Settings during
the first collector pass (5-30s: ~48 providers, 8 concurrent, network
timeouts) can hit it without doing anything wrong. Shipping a feature that
makes a latent inversion reachable, and then permanent, is not a defensible
place to leave it.
`setEnabled(_:)` takes the desired state, so tapping a stale switch converges
on what its label says instead of doing the opposite. `toggle()` remains as
`setEnabled(!isEnabled)` for callers with no desired-state to pass.
macOS + iOS BUILD SUCCEEDED.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A 5-lens adversarial audit of
f9f15bc..HEAD(#387–#390) raised 8 findings; 4 survived two rounds of refutation. All four are mine.1 — Twenty-six healthy collectors were labelled "No data returned" (high)
CollectorDataKindhas three cases and I applied a counter predicate to all of them..credits— "a credit-based balance".statusOnly— "local status only, no quota model"Those collectors put their payload in
status_text— Groq returns"12.5 req/min · 4300 tok/min", OpenRouter"$12.34 / $50.00"— and hardcode today/week/quota/remaining to zero-or-nil on their success path.So
classifycalled them.ranButEmpty, and the card rendered a permanent orange "No data returned — the check ran but came back empty" directly underneath the live numbers it had just fetched. 16.statusOnly+ 10.credits.Two knock-ons from the same call:
producedValueneeds a.producedData, so a user whose providers are all credits/status-only would see real numbers and never get the W5 login item; and every report logged them"empty", poisoning the silent-zero diagnostic this release exists to power.I excluded
status_textfrom the predicate on purpose — for quota providers it is always populated and made the check a tautology. Right for.quota, wrong to generalise. The fix trusts the collector's declared kind rather than reintroducing a string test.2 — A cancelled refresh published its results (medium)
requestRefreshcancels the in-flight task → propagates into every collector →URLSessionthrowsURLError(.cancelled)(-999) →categorizecorrectly reads.network. Neither publish site checked cancellation, so a refresh the user themselves superseded (⌘R, which is not gated onisLoading) painted every network-backed provider red. The replacement pass returns early onisLoading, so nothing repaired it for 60–120s.The telemetry
Task {}is unstructured and does not inherit cancellation — it uploadedfailed_networkfor the whole fleet row and cached it as last-reported.3 —
brew uninstall --zapleft everything behind (medium)The cask listed
~/Library/Application Support/CLI Pulse. The app writesCLIPulse, no space —DailyUsageArchive.defaultRoot()appends that literal and the pet ledger/state share the directory.Verified on this machine: the real path holds 60K of usage history and pet state; the path the cask named does not exist at all.
--zapreported success and removed nothing, and a reinstall came back with every day of history.Also missing
~/Library/Preferences/group.yyh.CLI-Pulse.plist— the app-group defaults suite backs to a plist next to the container, not inside it, and that is where provider configs and helper status live. All four new paths verified present on disk.This one is already live in the published tap, so the cask needs re-pushing after merge.
4 — The Settings launch-at-login switch could invert (high)
.onChange { _ in LaunchAtLogin.toggle() }threw the binding value away and inferred direction from live status, while the switch renders from a@Statesnapshot. Once anything changes registration behind that snapshot: switch reads OFF, reality is ON, user taps to turn it "on", it unregisters instead — and the same tap writesuserTouchedToggleKey, locking W5 out from ever repairing it.The inference is pre-existing (both refuters correctly said so). What is new is that W5 registers in the background mid-session, so a user opening Settings during the first collector pass hits it without doing anything wrong.
setEnabled(_:)now takes the desired state.Verification
-DDEVID_BUILDbrew style --caskMutation-verified: removing the
dataKindguard reddens exactly the two new classification tests.🤖 Generated with Claude Code