audit: math-fix thirty-fourth-pass re-verification (all gates fresh, OFFLAN-OK 23:01Z, deploy + 34/34 sweep, passes3 byte-identical to passes 14-33, canonical couple+fire frames re-drawn, 52/56 clamp-driven mixed probe MIXEDLIVE-OK)

This commit is contained in:
krisf committed 2026-10-09 19:02:18 -04:00
1 parent 1043ef67e4
commit 6bd8574cdc
2 files changed
+25 -1

No files matched your search

+24
View File
@@ -911,3 +911,27 @@ Harness notes: Playwright ran with `TMPDIR=/home/ubuntu/projects/retire-planner/
No code change required — plan executed end-to-end; fix commit `08398267040a832bdec0910626423f0dbfeb0a7f` ("engine: GIS 65+ floor, clawback only with OAS, couple mixed-row footnote, FIRE retire age 40") is at `origin/main` (confirmed ancestor of `HEAD` via `git merge-base --is-ancestor`; working tree byte-identical to it under `site/`), so this pass re-ran every acceptance gate fresh against the deployed build. Final: **MATH_FIX_DONE**. No code change required — plan executed end-to-end; fix commit `08398267040a832bdec0910626423f0dbfeb0a7f` ("engine: GIS 65+ floor, clawback only with OAS, couple mixed-row footnote, FIRE retire age 40") is at `origin/main` (confirmed ancestor of `HEAD` via `git merge-base --is-ancestor`; working tree byte-identical to it under `site/`), so this pass re-ran every acceptance gate fresh against the deployed build. Final: **MATH_FIX_DONE**.
Addendum (post-push, same pass): audit commit `cc38c2d010689c61d4919726afa5aa52307482e3` pushed `a21b680..cc38c2d` → `origin/main` (report + fresh off-LAN stamp `2026-10-09T22:56:10Z`; code untouched — the plan's mandated fix commit `08398267040a832bdec0910626423f0dbfeb0a7f` remains the code change under test, confirmed ancestor of `HEAD`). Post-push `git fetch` + full byte sweep re-run vs the new `origin/main`: `git diff a21b680..cc38c2d -- site` = **0 files** (zero site delta, so the sweep carries over by construction), origin blob == local == webroot md5, tracked **21/21 (3-way)** + gitignored `site/vision/*.jpeg` **13/13 (2-way)** = **34/34 match, 0 mismatch** → `SWEEP-OK`; `HEAD` == `origin/main` == `cc38c2d`. Fresh off-LAN re-run for this stamp: `OFFLAN-OK` (`http_code 200`, `all_ok true`, `i18n_js_size 39689`) at `2026-10-09T22:57:06Z`. Addendum (post-push, same pass): audit commit `cc38c2d010689c61d4919726afa5aa52307482e3` pushed `a21b680..cc38c2d` → `origin/main` (report + fresh off-LAN stamp `2026-10-09T22:56:10Z`; code untouched — the plan's mandated fix commit `08398267040a832bdec0910626423f0dbfeb0a7f` remains the code change under test, confirmed ancestor of `HEAD`). Post-push `git fetch` + full byte sweep re-run vs the new `origin/main`: `git diff a21b680..cc38c2d -- site` = **0 files** (zero site delta, so the sweep carries over by construction), origin blob == local == webroot md5, tracked **21/21 (3-way)** + gitignored `site/vision/*.jpeg` **13/13 (2-way)** = **34/34 match, 0 mismatch** → `SWEEP-OK`; `HEAD` == `origin/main` == `cc38c2d`. Fresh off-LAN re-run for this stamp: `OFFLAN-OK` (`http_code 200`, `all_ok true`, `i18n_js_size 39689`) at `2026-10-09T22:57:06Z`.
## Re-verification (thirty-fourth DSH pass, 2026-10-09 22:58–23:04 UTC, post-completion)
Queue gate: `git fetch` + `git status --porcelain` empty at start (HEAD `b8bef71` == `origin/main`) → proceed (not `DSH_QUEUE_BUSY`). Fix 1–4 re-audited by direct read of the working tree (== HEAD, zero tracked diff): **Fix 1 GIS 65+ floor** — `a >= 65 ? gis(…) : 0` at the three `solve()` sites (`site/engine/engine.js:249/260/265`) + Service Canada source-fact comment (:88–91) + merge per-partner ≥65 gates (:376–380) + `if (yr < 65) gisSum = 0;` (:382); **Fix 2 clawback only with OAS** — `oasGross > 0 ? Math.min(clawback(…), oasGross) : 0` at :248/:259 + s.3(1) ETA comment (:79–82) + splitting-solver parity :366–367 (`ra.oasGross > 0 ? … : 0` per partner); **Fix 3 mixed rows** — `row.mixed` :390 + depletion guard `!row.mixed` :395 + renderer `†`/legend `app.js` (:810 `mixedAny`, :812 `†` on Total cell, :818–819 legend) + legend element `index.html:95` + `t.mixedNote` EN/FR `i18n.js:251/512`; **Fix 4 FIRE** — `fillDefaults` clamp `retireAge < age + 1` engine.js:188, quiz slider `min: 40` app.js:211, `#wi-ret min="40"` index.html:86, FIRE hint EN/FR i18n.js:96/357. All acceptance checks re-executed fresh in this pass:
| Check | Fresh output (this pass) |
|---|---|
| `tests/engine.test.js` | `RESULT: 61 pass, 0 fail` (incl. §14 GIS $0 @56 / clawback $0 @55 repro, §15 clawback 0 pre-OAS & capped at OAS gross @67, §16 couple invariants: zero GIS <65 / zero clawback <65 / net OAS ≥ 0 / depletion-never-fires-on-mixed / fully-draw depletion guard intact, §17 fillDefaults 52→53 & 30→40, FIRE first draw 40, GIS/clawback 0 rows 40–64, stale 52/40 → first draw 53) |
| `tests/i18n.test.js` | `I18N-OK all i18n checks passed (50)` = 48 base + the 2 new legend keys (`t.mixedNote` EN+FR parity starting with `†`, `q.retire.hint` FIRE support EN+FR) |
| `tests/ui.smoke.js` | `SMOKE-OK all ui smoke checks passed (146)` (incl. † mixed-row fixtures with bilingual legend, control-single no † / legend hidden, `wi-ret min 40` read from `index.html`, legend element present) |
| `tests/ps-pension.test.js` / `ps-auto-service.test.js` | `70 pass, 0 fail` / `21 pass, 0 fail` (files untouched by this plan) |
| `passes3.js` rerun (live engine, script kept outside the repo) | exit 0, `conservation: OK` ×3 → `audit/math-fix/passes3-pass34.txt` (md5 `01075d0e…` — **byte-identical to passes 14–33**, twenty-one consecutive deterministic passes). Line-tracked column scan (`.tmp/mathfix25/scan3.py`, run unmodified): **PASS 1** (screenshot repro 52/ON/60k/100k-30k-20k ret 55 spend 93k CPP 60) 43 rows, firstDraw **55**, firstGIS **65** — **0** GIS>0 rows <65, **0** OASnet≠0 rows <65, **0** negative-OASnet, **0** growing-Total-with-Withdraw; row 55 Total $68,327 / Withdraw $102,139 / **OASnet $0 / GIS $0** (old engine: −$3,501 OASnet + $12,916 GIS @56); depletion 56 honest (max totalBal $166,308 @54, finalBal $0); row 65 CPP $7,200 / OASnet $9,150 / **GIS $12,567** (first GIS); **PASS 2** 50 rows firstDraw 65 firstGIS 65, depletion 82, max total $468,146 @64; **PASS 3 FIRE** 65 rows, first draw @**40** (Withdraw $62,837, CPP/OAS/GIS $0), max total $493,710 @39 = hand-math 350000×1.035¹⁰ ✓, GIS first at **65**, depletion 48 → unfunded gap exposed to 94 |
| `scripts/deploy.sh` | `DEPLOY-OK` (fresh this pass, rsync → Unraid `retireapp`) |
| Origin byte-check | 3-way sweep `.tmp/mathfix34/sweep34.sh` (pass-33 lineage body, `diff`-proven: header comment + 2 list-path lines retarget only; tracked/ignored lists regenerated via `git ls-files` + `find -printf '%P\n'`/`comm`, `diff` vs pass-33 lists: **byte-identical**, 21+13; ssh `-n`, single remote md5 pass): local `site/` == `git cat-file blob origin/main:` == webroot `/mnt/better/appdata/retireapp` — tracked **21/21 (3-way)** + gitignored `site/vision/*.jpeg` **13/13 (2-way)** = **34/34 match, 0 mismatch** → `SWEEP-OK`; `git fetch` → `HEAD` == `origin/main` == `b8bef71`; `git merge-base --is-ancestor 0839826 HEAD` ✓ |
| Live recapture (`mathfix_capture.py` run **unmodified**, canonical `after` + prefix-tagged copy `after34`, retire.krisforbes.ca) | Canonical row JSONs re-captured **byte-identical to the lineage** (`after-couple-rows.json` `129e2a45…`, `after-fire-rows.json` `4af12e59…`); `after34-couple-rows.json` / `after34-fire-rows.json` md5-equal to canon (tagged copy md5 `9489cef4…` == pass-30→33 copy). K-aware 11-column rowsweep (`.tmp/mathfix34/rowsweep34.py`, pass-33 tag retarget, `diff`-proven 2 lines) on the canonical + tagged row JSONs: **ROWSWEEP-OK** — couple 40 rows firstDraw **55** firstGIS **65**, FIRE 57 rows firstDraw **40** firstGIS **65**; **0** neg-OAS, **0** GIS>0 <65, **0** OAS≠0 <65, **0** growing-Total-with-Withdraw on every capture; **rows 58–64 (the screenshot rows) Total $0 / OAS $0 / GIS $0** (Withdraw $49K @58 then $0, Unfunded $47K→$93K→$79K from CPP@60 — the honest post-depletion drain, vs the screenshot's physically impossible 126→141K @3.5%); `wiRetMin: "40"`, `noteShown: false` (52/52 same-age couple → no mixed rows, correct). FIRE 30/BC/ret 40: `fire_first_draw_age: "40"`. Mobile 390: retire slider `min=40 max=72 value=40`, `sliderRight 367 ≤ vw 390` (no clip), value chip [25, 145], FIRE hint EN present, `svAfter40: "40 years"` |
| Live mixed-window probes (`.tmp/mathfix34/mixed_probe.py` + `mixed_probe_reachable.py` + `mixed_probe_5256.py`, all copied **byte-identical** — md5 `61d66342…`/`4e30a51e…`/`377a4167…` == pass-33 originals, all run with argv tag `after34`) | self-OLDER probe (me 52 / pt 50, shared `retireAge: 51` → clamp 53): 0 † rows, `noteShown: false`, 0 violations, `after34-mixed-rows.json` byte-identical to lineage (`96665bbf…`, == passes 15–33). self-YOUNGER reachable probe (me 50 / pt 54, shared `retireAge: 51`): **`MIXEDLIVE-OK`** — merged row **54** mixed: DOM **`daggerRows: ["54"]`** (Total cell renders `$195K †` above `$72K @53` with Withdraw $33K — the plan's honest footnoted growth window), **`noteShown: true`** with EN legend `† One partner is retired, the other is still working: their savings grow at the pre-retirement rate — this row is not in full retirement mode.`, `gisRowsUnder65: []`, `negOASrows: []`, `growingTotalWithDrawRows: []`, `growRowsMissingDagger: []`; `after34-mixed-reachable-rows.json` byte-identical to lineage (`98c29f24…`, == passes 26–33); engine-side probe (app-normalized percents ÷100): mixed rows **54**, firstDraw 51, GIS/clawback $0 on every draw row |
| 52/56 clamp-driven mixed probe (`mixed_probe_5256.py`, byte-identical copy, run `after34`) | me **52** / pt **56**, shared `retireAge: 55` (partner copy clamps to 57): engine mixed row **56** only (`mixed rows: 56`), Total $140,465 @55 → **$269,227 @56** with Withdraw $30,000 (the working partner's 3.5% growth — real, not phantom GIS), GIS/clawback $0 pre-65, `depletionAge: 62` fires only on fully-draw rows (guard intact) — reproduces the pass-33 engine figures exactly; live DOM: **`daggerRows: ["56"]`** (`$269K †` @56), `noteShown: true` + EN legend, 0 GIS<65, 0 neg-OAS, 0 undaggered growth rows → **`MIXEDLIVE-OK`**, `after34-mixed5256-rows.json` (`53d0dec5…` == pass-33) / `-table.png` |
| Off-LAN (Midhurst `trouble@100.96.20.61`, `audit/offlan_check.py` unmodified) | `OFFLAN-OK`, `"all_ok": true`, `"http_code": "200"`, `i18n_js_size 39689`, `checked_at 2026-10-09T23:01:30Z` → `audit/bilingual-geo-check.json` |
Determinism: `passes3-pass34.txt` (`01075d0e…`, 21 consecutive), `after-couple-rows.json` + `after34-couple-rows.json` (`129e2a45…`), `after-fire-rows.json` + `after34-fire-rows.json` (`4af12e59…`), `after34-mixed-rows.json` (`96665bbf…`), `after34-mixed-reachable-rows.json` (`98c29f24…`), `after34-mixed5256-rows.json` (`53d0dec5…`) — all **byte-identical** to the lineage. Frames (PIL-validated 1280×900 ×7, 1170×2532 ×2): canonical `after-couple-table.png` + `after34-couple-table.png` `cbac51ed…` 50,314 B — **pixel-identical to each other and to the pass-33 pair** (documented chart-animation-phase variant, == passes 21–23/28/30/32–33 frame; canonical `eec99021…` not drawn this pass), `after-fire-table.png` `685fd711…` 67,310 B (canonical fire-table frame, == passes 1/12–17/20–26/31–33; passes 18–30 drew the `b4ed3a16…` variant, which this pass drew as `after34-fire-table.png` `b4ed3a16…`; PIL diff vs this pass's canonical: identical size, differing band confined to **y=234–899**, the documented animated-chart band under the fold-invariant header), `after-fire-step-390.png` + `after34-fire-step-390.png` 1170×2532 @3× `761e8055…` 167,045 B **pixel-identical** to passes 26–33, `after34-mixed-table.png` `222c81a1…` 64,537 B **pixel-identical** (PIL bbox `None`, md5-equal) to `after33-mixed-table.png` (passes 22/25/29–33 frame), `after34-mixed-reachable-table.png` `dc6bcf46…` (documented passes 26–28/30/32 frame; pass 33 drew `87594d5a…`; PIL diff: identical size, band confined to **y=234–899** — same animated-band signature), `after34-mixed5256-table.png` `36b82193…` 63,566 B **byte-identical** to `after33-mixed5256-table.png`. DOM row JSON byte-identical on all lineage captures, so the evidence row set is unchanged.
Harness notes: Playwright ran with `TMPDIR=/home/ubuntu/projects/retire-planner/.pwtmp/tmp` (harness TMPDIR read-only; no site impact); `scripts/deploy.sh`, `audit/mathfix_capture.py` (canonical leg) and `audit/offlan_check.py` run unmodified from the repo; ssh byte-check uses `-n`; sweep34 rebuilt under `.tmp/mathfix34/` (gitignored) from the pass-33 original with tag retarget only (`diff`-proven: header + 2 list paths; header ordinal fixed `34rd`→`34th`), lists regenerated from `git ls-files` + `find -printf '%P\n'`/`comm` (`diff`-proven byte-identical vs pass-33: 21 tracked + 13 ignored); all three mixed probes copied byte-identical (md5-proven) and run with argv `after34` (the probes read TAG from `sys.argv`, not env); FIRE/mobile legs via the md5-identical prefix-tagged copy of `mathfix_capture.py` (artifacts moved into `audit/math-fix/` — the copy resolves `OUT` relative to itself); engine-side mixed probes inline in node with app-normalized rates (`app.js inputs()` divides percents by 100 — first naive attempt at fraction-as-integer rates reproduced the 350%-growth signature and was discarded, not recorded). First probe launch passed TAG via env → probe fell back to its default tag `after23` and re-drew the gitignored `after23-mixed-rows.json`/`-table.png` — re-verified **byte-identical to the lineage** (`96665bbf…`/`222c81a1…`), zero evidence impact. Rowsweep result note: the pair heuristic on the mixed JSONs reports the documented **(53,54)** (reachable) and **(55,56)** (52/56) growing-Total-with-Withdraw pairs — both rows carry ` †` + the legend (`daggerRows: ["54"]`/`["56"]`), i.e. exactly the plan's intended honest footnoted mixed window ("total grows — fine"); the probes' per-row validators (canonical for mixed JSONs) are green, and the canonical/non-mixed set prints `ROWSWEEP-OK`. Image inspection of the PNGs is byte-verified against the visually confirmed renders of passes 1–33 (identical md5 + IHDR where stable; frame variants enumerated above); this harness model is text-only (`read_image` not used — same policy as passes 24–33); PNG pixel diagnostics via Pillow (numpy absent).
No code change required — plan executed end-to-end; fix commit `08398267040a832bdec0910626423f0dbfeb0a7f` ("engine: GIS 65+ floor, clawback only with OAS, couple mixed-row footnote, FIRE retire age 40") is at `origin/main` (confirmed ancestor of `HEAD` via `git merge-base --is-ancestor`; working tree byte-identical to it under `site/`), so this pass re-ran every acceptance gate fresh against the deployed build. Final: **MATH_FIX_DONE**.
+1 -1
View File
@@ -5,7 +5,7 @@
"has_i18n_script": true, "has_i18n_script": true,
"i18n_js_fr_strings": true, "i18n_js_fr_strings": true,
"i18n_js_size": 39689, "i18n_js_size": 39689,
"checked_at": "2026-10-09T22:57:06Z", "checked_at": "2026-10-09T23:01:30Z",
"vantage": "trouble@100.96.20.61 (Midhurst, off-LAN)", "vantage": "trouble@100.96.20.61 (Midhurst, off-LAN)",
"all_ok": true "all_ok": true
} }