From 2b8a18cf09367674169421613a25282c3120a0c5 Mon Sep 17 00:00:00 2001 From: krisf Date: Fri, 9 Oct 2026 18:25:55 -0400 Subject: [PATCH] audit: math-fix twenty-sixth-pass re-verification (all gates fresh, OFFLAN-OK 22:23Z, deploy + 34/34 byte sweep, passes3 + DOM rows byte-identical to passes 14-25, NEW live reachable-mixed probe: dagger + legend on deployed site closes Deviation 2) --- MATH-FIX-REPORT.md | 26 ++++++++++++++++++++++++++ audit/bilingual-geo-check.json | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/MATH-FIX-REPORT.md b/MATH-FIX-REPORT.md index a6c1524..0e13954 100644 --- a/MATH-FIX-REPORT.md +++ b/MATH-FIX-REPORT.md @@ -704,3 +704,29 @@ Harness notes: Playwright runs with `TMPDIR=/home/ubuntu/projects/retire-planner No code change required — plan executed end-to-end; fix commit `0839826` at `origin/main` (ancestor of HEAD, confirmed via `git merge-base --is-ancestor`); all gates fresh this pass. Final: **MATH_FIX_DONE**. Addendum (post-push, same pass): audit commit `d915d8aa981fb026fd974ae0749ec60df87deaf8` pushed `a315c71..d915d8a` → `origin/main` (report + fresh off-LAN stamp; 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`: origin blob == local == webroot md5, tracked **21/21 (3-way)** + gitignored `site/vision/*.jpeg` **13/13 (2-way)** = **34/34 match, 0 mismatch**; `HEAD` == `origin/main` (this addendum commit adds only report text — zero site-file delta, so the sweep carries over by construction). + +--- + +## Re-verification (twenty-sixth DSH pass, 2026-10-09 22:21–22:26 UTC, post-completion) + +Queue gate: `git status --porcelain` empty at start (HEAD `107262e` == `origin/main`, post-fetch) → proceed (not `DSH_QUEUE_BUSY`). Fix 1–4 re-audited by direct read of the working tree (== HEAD, zero tracked diff): 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/:379–380) + `if (yr < 65) gisSum = 0;` (:382); clawback only with OAS — `oasGross > 0 ? Math.min(clawback(…), oasGross) : 0` at :248/:259 + s.3(1) ETA comment :79–83 + splitting-solver parity :366–367; mixed rows — `row.mixed` :390 + depletion guard `!row.mixed` :395 + renderer `†`/legend `app.js:807–819` (:812 `†`, :818–819 legend) + legend element `index.html:95` + `t.mixedNote` EN/FR `i18n.js:251/512`; FIRE — `fillDefaults` clamp `retireAge < age + 1` engine.js:187–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 + depletion-never-fires-on-mixed, §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)` (incl. `t.mixedNote` EN+FR parity starting with `†`, `q.retire.hint` FIRE EN+FR) | +| `tests/ui.smoke.js` | `SMOKE-OK all ui smoke checks passed (146)` (incl. † mixed-row fixture ×3 with bilingual legend EN+FR, control-single no † / legend hidden, `wi-ret min 40` from index.html, legend element present) | +| `tests/ps-pension.test.js` / `ps-auto-service.test.js` | `70 pass, 0 fail` / `21 pass, 0 fail` (unchanged files) | +| `passes3.js` rerun (live engine) | exit 0, `conservation: OK` ×3 → `audit/math-fix/passes3-pass26.txt` (md5 `01075d0e…` — **byte-identical to passes 14–25**, thirteen consecutive deterministic passes). Line-tracked column scan (`scan3.py`): PASS 1 (screenshot repro) 43 rows firstDraw 55 — **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**; first GIS row **65** (CPP $7,200, OASnet $9,150, GIS $12,567), depletion 56 honest (max totalBal $166,308 @54, finalBal $0); 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 $13,667 first at **65**, depletion 48 → unfunded gap exposed to 94 | +| `scripts/deploy.sh` | `DEPLOY-OK` (fresh this pass) | +| Origin byte-check | 3-way sweep `.tmp/mathfix26/sweep26.sh` (pass-25-lineage script, tag retarget only; tracked/ignored lists regenerated via `git ls-files` + `find`/`comm`, `diff` vs pass-25 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`; `HEAD` == `origin/main` == `107262e` | +| Live recapture (`mathfix_capture.py after26` + FIRE/mobile via prefix-tagged copy, retire.krisforbes.ca) | couple 52/52 ret 55 spend 93k CPP 60, K-aware 11-column parse (`.tmp/mathfix26/rowsweep.py`): **ROWSWEEP-OK** on all three row JSONs — 40 rows, first draw **55**, first GIS **65**, **0** neg-OAS, **0** GIS>0 <65, **0** OAS≠0 <65, **0** growing-Total-with-Withdraw; rows 58–64 Total $0 / OAS $0 / GIS $0 (Withdraw $49K @58 then $0, Unfunded $47K→$93K→$79K from CPP@60 — honest post-depletion drain, vs the screenshot's impossible 126→141K @3.5%); `wiRetMin: "40"`, `noteShown: false`. FIRE 30/BC/ret 40 (`after26-fire-rows.json`, 57 rows): `fire_first_draw_age: "40"`, first GIS 65, **0** violations. Mobile 390: retire slider `min=40 max=72 value=40`, `sliderRight 367 ≤ vw 390` (no clip), chip [25, 145], FIRE hint EN present, `svAfter40: "40 years"` | +| Live mixed-window probe (`after26-mixed-*`, pass-14-lineage) | couple me 52 / pt 50, `retireAge: 51` → `fillDefaults` clamps self to 53, partner inherits the shared clamped 53 → both draw from 53: 0 † rows, `noteShown: false`, 0 GIS>0 <65, 0 OAS≠0 <65, 0 neg-OAS — reconfirms the self-OLDER probe scenario cannot produce mixed rows (age-axis merge + shared clamped `retireAge`) | +| **Live reachable-mixed probe (NEW, `.tmp/mathfix26/mixed_probe_reachable.py` → `MIXEDLIVE-OK`)** | **Closes the Deviation-2 residual**: the self-YOUNGER path is live-reachable. Couple me 50 / pt 54, shared `retireAge: 51` (valid for self: 51 ≥ 50+1; `fillDefaults` clamps the partner's copy to 55 = 54+1) → merged axis row **54**: self draws, partner accumulates → `row.mixed` → DOM: **`daggerRows: ["54"]`** (Total cell carries ` †`), **`noteShown: true`** with EN legend text `† One partner is retired, the other is still working: … not in full retirement mode.`, **0** GIS>0 <65, **0** neg-OAS, **0** growing-Total-with-Withdraw rows missing `†`. Artifacts: `audit/math-fix/after26-mixed-reachable-rows.json` (md5 `98c29f24…`), `after26-mixed-reachable-table.png` (1280×900, IHDR-validated, md5 `87594d5a…`). Deviation 2 amended: mixed draw windows ARE reachable live when the younger partner's shared `retireAge` falls between the two ages; † + bilingual legend proven live on the deployed site, not only via fixtures | +| Off-LAN (Midhurst `trouble@100.96.20.61`) | `OFFLAN-OK`, `"all_ok": true`, `"http_code": "200"`, `checked_at 2026-10-09T22:23:02Z` → `audit/bilingual-geo-check.json` | + +Determinism: `passes3-pass26.txt`, `after26-couple-rows.json` (== canonical `after-couple-rows.json` `129e2a45…`), `after26-mixed-rows.json` (== `after15–25` `96665bbf…`), `after26-fire-rows.json` (== canonical `after-fire-rows.json` `4af12e59…`) all **byte-identical** to the lineage. Frames: the `after26` couple capture landed on the known chart-animation-phase frame `eec99021c08c…` 63,753 B (pass-11/13–20/22/24/25 variant); the FIRE-script-path couple capture landed on the canonical frame `cbac51ed5089…` 50,314 B — byte-identical to the canonical renders of passes 1/8–19/21/23; `after26-fire-table.png` `685fd711…` 67,310 B and `after26-fire-step-390.png` 1170×2532 @3× `761e8055…` 167,045 B stable. All PNGs IHDR-validated; new reachable-mixed render `87594d5a…` is a first-appearance frame (scenario is new). + +Harness notes: Playwright runs with `TMPDIR=/home/ubuntu/projects/retire-planner/.tmp/pw` (harness TMPDIR read-only; no site impact); ssh byte-check uses `-n`; this pass rebuilt sweep + K-aware rowsweep under `.tmp/mathfix26/` (gitignored) from the pass-25 originals with tag retargeting only (`mathfix25→mathfix26`, `after25→after26`), lists regenerated from `git ls-files` + `find`/`comm` (21 tracked + 13 ignored, `diff`-proven byte-identical), FIRE/mobile legs via a prefix-tagged copy of `mathfix_capture.py` (`TAG == 'after'` → `TAG.startswith('after')`, no other edit), plus the new `mixed_probe_reachable.py`. Column mapping re-verified against the renderer (`app.js:809–813`): 0 age, 4 Total(†), 5 Withdraw, 6 CPP, 7 OASnet, 8 GIS, 9 Tax+clw, 10 Unfunded — rowsweep indices correct (the pass-14-lineage probe's `firstDrawAge`/`gisRowsUnder65` read CPP/tax columns; its K-aware re-parse by `rowsweep.py` is authoritative, as in passes 21–25). Renderer skips non-draw rows unless `age % 5 == 0` (`app.js:809`) — explains the lineage probes' row-age gaps (e.g. 51/52 accum rows skipped). `audit/math-fix/` is gitignored by design. Image inspection of the PNGs is byte-verified against the visually confirmed renders of passes 1–25 (identical md5 + IHDR); this harness model is text-only (`read_image` rejected: model declares no image input — recorded, same policy as pass 24 note). + +No code change required — plan executed end-to-end; fix commit `0839826` at `origin/main` (ancestor of HEAD, confirmed via `git merge-base --is-ancestor`); all gates fresh this pass; Deviation-2 gap closed with live `†` evidence. Final: **MATH_FIX_DONE**. diff --git a/audit/bilingual-geo-check.json b/audit/bilingual-geo-check.json index a806277..95b7eb6 100644 --- a/audit/bilingual-geo-check.json +++ b/audit/bilingual-geo-check.json @@ -5,7 +5,7 @@ "has_i18n_script": true, "i18n_js_fr_strings": true, "i18n_js_size": 39689, - "checked_at": "2026-10-09T22:19:57Z", + "checked_at": "2026-10-09T22:23:02Z", "vantage": "trouble@100.96.20.61 (Midhurst, off-LAN)", "all_ok": true } \ No newline at end of file