Stage 10: final REPORT.md (stages 6-10 evidence, deviations, runbook)

This commit is contained in:
krisf committed 2026-10-08 23:05:10 -04:00
1 parent 4f1207b1b8
commit e470985936
2 files changed
+68

No files matched your search

+57
View File
@@ -50,3 +50,60 @@
- Repo `krisf/retire-planner` created via Gitea API (Basic auth from `~/.git-credentials`, token never printed; API-token scheme returned 401 → Basic used, documented). `git push -u origin main` → `* [new branch] main -> main` on https://gitea.krisforbes.ca/krisf/retire-planner.git.
- Secret scan: `git grep -F "<nano key value>"` and `-F "<gitea token>"` over `git rev-list --all` → **0 hits** (clean). The plan's pattern-regex grep hits only the literal recipe strings inside the committed PLAN.md/REPORT.md + `scripts/gen_all.sh` (which *reads* the key from `~/.hermes/.env` at runtime, never embeds it). Deviation noted: plan's grep is self-contradictory (committing PLAN.md is also required and PLAN.md contains the pattern strings).
- `.gitignore` excludes `.fetch/`, `dsh-run.log`, duplicate `site/vision/*.jpeg`.
## Stage 6 — Unraid container ✅
- `scripts/deploy.sh` (idempotent): `rsync -az --delete site/ → /mnt/cache/docker/retireapp/`, `chmod -R a+rX`, `docker start retireapp || docker run -d --name retireapp --restart unless-stopped -p 8103:80 -v /mnt/cache/docker/retireapp:/usr/share/nginx/html:ro nginx:alpine`, marker grep → `DEPLOY-OK`.
- Created: `docker run` → container id `978374…`; `docker ps`: `retireapp Up … 0.0.0.0:8103->80/tcp`.
- Pitfall found & fixed: workspace-written files were mode 0640 → nginx 403 on `/` and `*.css`; `chmod -R a+rX` baked into deploy.sh. Re-ran deploy.sh → `DEPLOY-OK` (idempotency proven). Host-local: index/css/app.js/engine.js = 200, marker count 1.
## Stage 7 — Caddy reverse proxy ✅
- Backup: `/mnt/cache/docker/caddy/config/Caddyfile.bak-retire-<ts>` (cp before edit). No pre-existing retire block (grep 0).
- Appended in file’s established pattern: `# HERMES: retire` / `retire.krisforbes.ca { encode gzip; reverse_proxy 192.168.0.123:8103 { header_up X-Real-IP {remote_host} } }` / `# END HERMES: retire`.
- `docker exec CaddyV2 caddy validate --config /etc/caddy/Caddyfile` → `Valid configuration`. `docker restart CaddyV2` → `Up`.
- Note: CaddyV2 publishes 443→host **:2443** (router forwards public 443→2443); LAN test uses `--resolve retire.krisforbes.ca:2443:127.0.0.1` → **200** + marker.
## Stage 8 — Cloudflare DNS ✅
- Token extracted from `/mnt/cache/docker/caddy/config/caddy/autosave.json` over ssh (53 chars, **never printed**).
- Pre-check per plan: `GET /dns_records?name=retire.krisforbes.ca` → 0 records. `dyn.krisforbes.ca` exists: **A → 172.97.221.30** (public home IP, proxied:false) — matches, no fallback needed.
- `POST` `{"type":"CNAME","name":"retire","content":"dyn.krisforbes.ca","proxied":false,"ttl":120}` → `success: True`, id `651dd8dca9a0d650ebfbe9a0d9698ee0`. Grey cloud as planned (cert via Caddy ACME, DNS publicly resolvable).
- Resolved on 1.1.1.1 + 8.8.8.8 within ~15 s: retire → dyn → 172.97.221.30.
- Let’s Encrypt cert obtained automatically (Caddy log: `certificate obtained successfully … identifier: retire.krisforbes.ca`).
## Stage 9 — End-to-end verification ✅
- LAN (Unraid, port 2443): `https://retire.krisforbes.ca:2443/` → **200**, marker 1.
- Off-LAN: this workspace sits off-LAN (tunneled to home, 51 ms); `troublebox` alias is not defined on the workspace nor Unraid — documented substitution, the check itself is the public-URL path. From here: `https://retire.krisforbes.ca/` → **200**, `ssl_verify_result=0` (valid LE cert), `remote_ip=172.97.221.30` (public edge), HTTP/2, `http://` → 308 → https. Assets over public URL: `/app.js`, `/engine/engine.js`, `/engine/canada.js`, `/vision/manifest.json`, `/style.css`, `/vision/muskoka.png` (597,096 B) all **200**. Marker present in served HTML.
- Secret scan: `grep -rE 'cfut_|<nano-key>|<gitea-token>' site/` → **CLEAN**; git-history value scan clean (Stage 5).
## Stage 10 — Wrap-up
Final checklist (PLAN.md “Done criteria”):
- [x] `node tests/engine.test.js` → **36 PASS / 0 FAIL** (+ `tests/ui.smoke.js` 17/17, SMOKE-OK)
- [x] Repo pushed → https://gitea.krisforbes.ca/krisf/retire-planner (branch `main`)
- [x] `retireapp` container **Up** on Unraid `:8103`, serving marker `<!-- retire-planner v1 -->`
- [x] Caddyfile block added; `caddy validate` clean; CaddyV2 restarted Up
- [x] CF CNAME `retire → dyn` created, grey cloud (proxied:false)
- [x] Off-LAN `curl https://retire.krisforbes.ca/` → **200** + marker
- [x] Vision gallery: 13 images generated via nano-gpt `z-image-turbo` (list price ≈ $0.155)
- [x] REPORT.md committed + pushed
- [x] “100% client-side — answers never leave your browser” on site (intro pill + results footer)
### Deviations & notes (all intentional, evidence above)
1. Constants are **2026** (live-fetched Oct 2026: CPP 2026, OAS/GIS Oct–Dec 2026, tax 2026) — plan text said 2025; site stamp shows `as of October 2026 …`.
2. Federal age amount 8,790 and PIA 2,000 kept at plan-stamped 2025 values (web_search tool unavailable — DeepSeek 401; canada.ca fetched via web_fetch for everything else).
3. GIS annualized from 2026 constants: `maxMonthly×12 − 0.5×max(0, income−5000)`, zero at ≥ 23,112 (single) / 30,528 (couple).
4. Ontario surtax modeled gross-basis (6,474→20%, 8,102→36%) — close approximation, documented in `constants-evidence.json`.
5. Manitoba basic personal amount frozen at 15,780 (no 2026 release found).
6. Quebec: federal abatement 16.5% applied; QPP + QC payroll taxes **not** modeled — UI insight tells users to add QPP as “pension income”.
7. Couple GIS threshold 30,528 applied in `project` only when both partners ≥ 65; pension splitting 50/50 for couple with a member 65+.
8. `cppMonthlyAt(65, 70)` returns 0 by design (monthly amount for month 65 given start 70 is not meaningful); plan’s deferral assertion tested at age 70 (×1.42) + zero case.
9. Gitea API auth: token scheme 401 → Basic auth parsed from `~/.git-credentials` (never printed), repo created 201, push OK.
10. Plan’s repo secret-grep is self-contradictory (it mandates committing PLAN.md, which contains the pattern strings); the meaningful test — secret **values** in full git history — returns 0 hits.
11. CaddyV2 maps 443→host 2443 (router forwards public 443); LAN verification uses :2443, public access unaffected.
12. `troublebox` ssh alias absent on workspace/Unraid → off-LAN check performed from this off-LAN workspace against the public URL with valid public TLS (remote_ip = public edge).
13. Deploy chmod note: files created in workspace are 0640; deploy.sh applies `chmod -R a+rX` (403 → fixed).
14. `.gitignore`: `.fetch/`, `dsh-run.log`, `site/vision/*.jpeg` (duplicate exports; manifest references .png).
### Operations runbook
- Redeploy after edits: `bash scripts/deploy.sh` (idempotent; prints DEPLOY-OK).
- Rebuild constants: `node scripts/build_canada.js` (fetches canada.ca; provenance in `scripts/constants-evidence.json`).
- Regenerate vision art: `bash scripts/gen_all.sh` (reads nano key from `~/.hermes/.env` at runtime).
- Tests: `node tests/engine.test.js && node tests/ui.smoke.js`.