|
1 | 1 | # TreeMap — session handoff |
2 | 2 |
|
| 3 | +## Session 9 — the seven number fixes, a CHANGELOG, and v5.0.0 (2 September 2026) |
| 4 | + |
| 5 | +Shipped as **v5.0.0**, built, verified inside the asar, and installed at |
| 6 | +`/Applications/TreeMap.app`. The previous build is parked at |
| 7 | +`release/TreeMap-4.2.0-previous.app`. Gate: **2,412 tests · 0 fail · 3 |
| 8 | +skipped**, typecheck clean, `build-ui --check` matches (112 parts). |
| 9 | + |
| 10 | +**v5.0.0 is not yet pushed and not yet published.** The owner pushes; the |
| 11 | +GitHub release is theirs to publish and needs a `v5.0.0` tag before the |
| 12 | +in-app updater can see it. |
| 13 | + |
| 14 | +### The seven, and what each one was actually saying |
| 15 | + |
| 16 | +Every one was the app stating something untrue. All seven are red-first and |
| 17 | +mutation-proven; 47 mutants, 47 caught (7 needed a second attempt because the |
| 18 | +first version of the test was not evidence). |
| 19 | + |
| 20 | +1. **Fleet pairing was brute-forceable.** The doc comment claimed "a million |
| 21 | + possibilities against a three-minute window and a rate limiter"; there was |
| 22 | + no rate limiter, and an audit had measured 33,966 guesses in three seconds. |
| 23 | + Now five wrong codes per machine and fifty in total, checked BEFORE the |
| 24 | + offer is looked up so a sixth attempt is refused even when it is right. |
| 25 | + Addresses are normalised (`::ffff:` stripped) or a dual-stack socket buys a |
| 26 | + second allowance. A withdrawn offer raises a line in the Fleet panel. |
| 27 | +2. **Offload said "need 0.0 GB"** about 30 MB. Both figures go through |
| 28 | + formatBytes now. The same line had a second untruth: the check refuses at |
| 29 | + bytesTotal × 1.02 but named the raw total, so inside that window it stated |
| 30 | + its own contradiction. The headroom is said out loud. |
| 31 | +3. **The Trends dashed line and the date beside it were two different fits.** |
| 32 | + The chart ran its own unweighted linreg while the date came from the |
| 33 | + server's recency-weighted one — and with status 'ok' the server slope is |
| 34 | + guaranteed positive while linreg could still come back NEGATIVE, so the |
| 35 | + chart could draw a descending tail, clamped to zero, beside "disk full |
| 36 | + ~<date>". `forecastOk` (a boolean) became `forecastRate` (bytes/day, or |
| 37 | + null); the tail rises at exactly that rate. |
| 38 | +4. **Sparse files claimed space the disk does not hold** and the gap was |
| 39 | + blamed on clones. See the two-sided note below — this one was shipped |
| 40 | + wrong first and repaired. |
| 41 | +5. **Duplicates promised bytes trashing would not free.** The clone caveat is |
| 42 | + on screen on darwin, and after a trash the MEASURED delta is reported. |
| 43 | +6. **"Used" meant two different things.** Half was already done in 9c14572; |
| 44 | + six UI/report/tray sites still derived `total − free`. They read the |
| 45 | + published `usedDisk` now, and the receipt says "Free to you" and draws the |
| 46 | + reserve as its own band. |
| 47 | +7. **Two hard-link counts wore near-identical labels.** Relabelled, not |
| 48 | + recounted. A third row now reconciles them outright. |
| 49 | + |
| 50 | +### What the adversarial pass found in work already committed |
| 51 | + |
| 52 | +A 16-agent recon ran plan-then-verify on all seven. The verifiers were worth |
| 53 | +more than the planners, because they audited SHIPPED code: |
| 54 | + |
| 55 | +- **The sparse correction was one-sided and therefore wrong.** It subtracted |
| 56 | + only the shortfall and ignored block slack — a small file OCCUPIES more than |
| 57 | + it claims. Measured: `/usr/bin` claims 224.0 MB and holds 84.1 MB (slack |
| 58 | + 0.3 MB, so it was nearly right), but 3,000 fifty-byte files claim 0.14 MB |
| 59 | + and hold 11.72 MB, where the scan UNDER-counts by 11.58 MB and the one-sided |
| 60 | + line said nothing — leaving the whole difference in Unaccounted under the |
| 61 | + clone explanation the fix exists to stop over-using. The error equalled the |
| 62 | + slack on every tree tested. Now a signed delta, and the receipt line is the |
| 63 | + NET, which is also what the allocation panel two clicks away has always |
| 64 | + reported. |
| 65 | +- **The fleet alarm hid itself.** `renderFleet` early-returns for the disabled |
| 66 | + state and the alarm was only in the enabled branch — so turning the fleet |
| 67 | + off, the natural reaction, deleted the explanation. |
| 68 | +- **The 429 named the wrong machine.** That text is read on the machine that |
| 69 | + TYPED the code. |
| 70 | +- **A comment the fix made untrue.** `verifyPairingCode` still promised a |
| 71 | + wrong code never clears the window — true of one guess, false of fifty. |
| 72 | +- **The duplicates measurement skipped the commonest roots** (`/`, `/Users` |
| 73 | + fail a one-directional `startsWith(home)`) and survived into a cloud scan. |
| 74 | + |
| 75 | +### Verified in the INSTALLED app, not only in tests |
| 76 | + |
| 77 | +Driven over CDP against `/Applications/TreeMap.app` (never the dev Electron |
| 78 | +binary — Gatekeeper deletes it). A synthetic tree claiming 512.27 MB while |
| 79 | +holding 1.81 MB, built in the scratchpad; the owner's real folders were never |
| 80 | +scanned. |
| 81 | + |
| 82 | +- `/api/capabilities` → 5.0.0; `/api/system` publishes `usedDisk`. |
| 83 | +- A real scan on the **gdu-turbo** engine (the default) reported |
| 84 | + `sparseFiles 1 · sparseBytes 512.00 MB · slackBytes 1.55 MB` — matching |
| 85 | + `lstat` ground truth exactly, with the symlink correctly excluded. |
| 86 | +- The receipt printed `sparseFiles −510.45 MB` against a `scanned` line of |
| 87 | + 512.27 MB, so the statement now says the tree holds 1.8 MB — which is what |
| 88 | + `du -sh` says. `used + free + reserved === total` held. |
| 89 | +- Dashboard: "1 file claims 512.0 MB more than it takes" (verb agreeing), and |
| 90 | + on a three-name inode "2 extra file names (8.0 MB)". |
| 91 | +- Legend: "Free to you — 280.6 GB". No reserved band on APFS, correctly. |
| 92 | +- Duplicates: the clone caveat rendered under "up to 4.0 MB reclaimable". |
| 93 | +- Fleet alarm rendered in BOTH branches, in both themes, and vanished once a |
| 94 | + fresh code was on screen. |
| 95 | +- Offload against a real 20 MB volume: "this needs 30.0 MB plus a little room |
| 96 | + to spare, and only 19.3 MB is free". |
| 97 | +- No horizontal overflow at 640 / 900 / 1440, no `will-change` at rest, no |
| 98 | + backdrop-filter on the new surfaces, no console errors. |
| 99 | + |
| 100 | +### Traps this round paid for |
| 101 | + |
| 102 | +- **`asar extract-file` writes to the CURRENT DIRECTORY**, not stdout. A |
| 103 | + verification script that redirects its output silently overwrote the repo's |
| 104 | + `package.json` with the asar's stripped copy (no scripts — `npm test` |
| 105 | + became "Missing script: test") and littered eight extracted `.js` files into |
| 106 | + the repo root. Read an asar by parsing its header in Node instead; there is |
| 107 | + a working reader in this session's scratchpad. |
| 108 | +- **Replacing every `"version": "4.2.0"` in package-lock.json bumps |
| 109 | + DEPENDENCIES.** Six third-party packages were moved to a version that does |
| 110 | + not exist. Only two nodes describe TreeMap: the lock root and |
| 111 | + `packages[""]`. |
| 112 | +- **`shasum -a 256 $FILES` with an unquoted zsh variable is one bogus |
| 113 | + filename.** shasum errors, and the empty-string digest is compared to |
| 114 | + itself — a restore check that always passes. This is the same trap the |
| 115 | + prompt warned about, met in a new place. |
| 116 | +- **A mutant that "passes" may not have been applied.** One gdu mutant |
| 117 | + reported green because its anchor did not match and the file was never |
| 118 | + changed. Assert the anchor count inside the mutation script. |
| 119 | +- **`-0 !== 0` under `assert/strict`** (Object.is). A measured zero produced |
| 120 | + by negation must be normalised. |
| 121 | +- **`renderFleet()` takes no arguments** — it reads `state.fleet.data`. And |
| 122 | + `switchView('fleet')` re-fetches, overwriting anything you just stubbed. |
| 123 | + |
| 124 | +### Left undone, deliberately |
| 125 | + |
| 126 | +- **A fast rescan under-counts the claimed-versus-held line.** Unchanged |
| 127 | + folders are read from the previous scan and not re-measured, and no |
| 128 | + per-node allocation is persisted. The line says it is a floor. Persisting |
| 129 | + allocation per node is the real fix. |
| 130 | +- **`reclaimableCaveat` / `reclaimableIsUpperBound` are still dead.** The |
| 131 | + server computes them; `/api/duplicates` and the MCP tool both drop them. |
| 132 | + The clone line on screen is the client's own darwin check, so those two |
| 133 | + fields remain unreachable. Merging them would mean two mechanisms for one |
| 134 | + sentence. |
| 135 | +- **Decimal vs binary bytes** and **Empty Folders listing `~/.Trash`** remain |
| 136 | + the owner's product decisions, per session 8. |
| 137 | + |
| 138 | + |
3 | 139 | ## Session 8 — the polish round (2 September 2026) |
4 | 140 |
|
5 | 141 | Owner: "pushed it, check the ci. and polish anything you need to. Make this |
|
0 commit comments