Repository navigation
Sep26 - #322
Sep26#322
Conversation
…iod" This reverts commit 9d87a96.
runPool now returns a Left from allocateDefaultByAmt when the pool-level DefaultByAmt total is greater than the sum of current balances, instead of silently dropping the excess. Also fixes the Data.Ord Down/Direction name clash in prorataFactors that broke compilation.
…ded by #345 ServerConfig)
…t in-band (Left) or with HTTP 400; legacy RootFind shapes handled; CI pattern lock; bump to 0.52.10
…mpty, CashFlow zero rows)
Total review — PR #322 (
|
| commit | area | verdict / notes |
|---|---|---|
| b00df76 | .gitignore |
fine |
| 391afe9 | pool DefaultByAmt pro-rata allocation |
correct core (largest-remainder via prorataFactors); mortgage-only — catch-all still applies the full total per asset for other asset types (see M3); tests added (PoolTest) |
| 9d87a96 | fee double-count fix (calcDueFee: drop + fd) |
superseded |
| 639b85f | Revert of 9d87a96 (incl. its tests) | net-zero; undocumented revert — if the double-count is real it remains open (see M2) |
| 0c3bfcd | integer-cents largest-remainder prorataFactors; getCurrentBal ScheduleMortgageFlow |
good (no Rational rounding drift; zero-total safe; residual-tie by index documented); removed unused Lens imports; tests added |
| 023a2af | DefaultByAmt > Σ balances → descriptive Left |
good; small edge: negative balances unguarded (L3) |
| 52b58bf | PrepaymentABS (monthly-only) + monthsBetween |
HIGH severity finding H1 (division by zero / negative SMM beyond 1/r months); periodsBetween removed from Lib exports → library-API break, not in CHANGELOG (M4); basic tests added |
| 225b152 / aa5ec33 | version bumps 0.52.6/0.52.7 | fine |
| 1742d65 | Docker/CI arm64 | HIGH — change does not produce working arm64 images (H2) |
| 8d1209c | #327 priceBonds unknown bond → Left |
good; UT added (DealTest +20) |
| 7a7888e | #331 mflow* accessors total for non-mortgage rows |
good; UT (testMflowAccessors) |
| e1b8634 | #325 Weekly/BiWeekly/Daily period handling |
good; UT added |
| cb73353 | head/tail hardening (Util/DateUtil/Stmt/Analytics/Asset/Liability/…) | good direction; wide blast radius but all via defaults with UTs; many -Wx-partial sites remain repo-wide (tracked) |
| 6f573a0 | #333 lookupM/lookupVs + pre-run name validation |
good; behavior change = previously-crashing inputs now descriptive; UT added |
| d85cd94 | #335 seeded foldl for agg rules |
good; UT added |
| cf050b2 | #346 boundedParMap + worker cap |
good; UT added; see H3 interplay (boundary forcing) |
| 1ee1ff9 | #348 version from Paths_Hastructure |
good; committed swagger.json must be regenerated on bumps (L4) |
| f046cb6 / a6554a8 | #352 delete Validation.hs; cabal dedupe |
fine |
| f8b5966 | "UT for #4321" | tests fine; message references a non-existent issue (L1) |
| ea87daa | bump 0.52.8 | fine |
| d0f2674 (empty msg) | #353 NonPerfAssumption defaults + -Werror=missing-fields + fixtures |
good; UT (nonPerfAssumptionTest); empty message (L1) |
| bc85499 (empty msg) | #346 follow-up (resolveWorkerCap/env) |
superseded by 6ea703b; empty message (L1) |
| 59623c2 (empty msg) | #328 strict-canonical TxnComment codec + roundtrip tests |
good; wire-visible canonicalization of 4 tags + strict rejection of legacy forms — documented in CHANGELOG; empty message (L1) |
| 6ea703b (empty msg) | #345 ServerConfig + config.yml schema + per-endpoint caps + worker precedence |
good, well-tested pure logic; config.yml is a deployment interface change (M5); empty message (L1) |
| a48a6c6 | #344 Warp JSON error boundary | good first install, but covers app-phase only — value-level escapes still abort mid-stream; no automated UT; CHANGELOG overstates (H3) |
| b627079 | #329 handlers total over request types; -Werror=incomplete-patterns on exe; 400s for map endpoints |
good and compile-enforced; but 400 bodies are plain text while all other errors are JSON (M1); no transport-level UTs for the new paths |
| (local) 67c7d64 | #332 totalize cashflow aggregation | not pushed yet — include before merge |
High severity
H1. PrepaymentABS divides by zero / produces negative SMM for normal inputs
src/Asset.hs:191-201: smm m = r / (1 - r * fromIntegral (pred m)) with Rate = Rational (division by zero = runtime exception, not a nice error).
- The formula is the correct conversion if
ris a monthly rate applied to the original balance — but then the outstanding balance implied by1 - r*(m-1)hits zero at month1 + 1/r:r = 2%→ month 51: denominator exactly0→ crash (Ratio has zero denominator).r = 6%→ month 18: denominator-0.02→SMM = -3(silently corrupt projection!).r = 10%→ month 11 crash;r = 1.0(allowed by the guard) → month 2 crash.
- Current UTs only exercise months 1–4 (
absDs= Jul–Oct 2021) — the failing region is untested.
Recommend: validate r * (maxMonthAge - 1) < 1 at buildPrepayRates time (return descriptive Left), and/or clamp SMM to (0,1]; document r semantics; add tests crossing 1/r months (e.g. 2% with a 60-month schedule).
H2. Docker/CI arm64 path does not produce working arm64 images
.github/workflows/docker-image-dev-by-tag.yml (single ubuntu-latest = amd64 runner, platforms linux/amd64,linux/arm64) + Dockerfile:
- The workflow passes
build-args: CFLAGS=…, CPPFLAGS=…but the Dockerfile never declaresARG CFLAGS/CPPFLAGS— the args are ignored. - Even if declared, the values are set via
exportinside aRUN case …— shell exports do not persist to the laterRUN cabal installlayer. FROM --platform=$BUILDPLATFORM haskell:… AS buildpins the build stage to the build platform (amd64 onubuntu-latest) → GHC produces an amd64 binary for both targets, and the per-targetubuntufinal stage copies that amd64 binary into the arm64 image →exec format errorat runtime.
Recommend: either drop the--platform=$BUILDPLATFORMpin (build under QEMU per target) or split into a native per-arch matrix (ubuntu-24.04-arm) with a manifest merge; declareARG/ENV CFLAGS CPPFLAGSproperly; add a CI smoke step (docker runthe arm64 image and hit/version).
H3. #344 boundary covers the app-phase only; the dominant engine-error class still aborts mid-stream
Verified against warp-3.4.9 sources: istatus flips before sendResponse. Exceptions while forcing a lazily-built response body (which is where wrapRun/runDeal Eithers are evaluated — Aeson forces them during body writing) cannot be re-rendered — the connection aborts, only the log records it. Consequences:
- The header/CHANGELOG claim ("exceptions escaping the request pipeline now answer 500…") is broader than what is achieved; No exception boundary at the server: pure errors escape as generic HTTP 500 #344 was closed with this gap open.
- The proposed completion (force responses to WHNF inside the handler action —
return $! wrapResult/forceRespMapfor theboundedParMapmaps) is not part of this PR; a self-review addendum is posted on the issue. - No automated UT for the boundary itself.
Recommend: implement the WHNF-forcing pass (small, ~11 sites), correct the CHANGELOG wording, add a unit/golden test forerrorBoundary(e.g. viaresponseToStream), or reopen No exception boundary at the server: pure errors escape as generic HTTP 500 #344 until done.
Medium
- M1. Plain-text 400 bodies.
enforceRunCapandbadRequestuseerr400 { errBody = cs msg }— Servant renders that as a plain body with no JSON envelope, while every other error path is JSON ({"Left":…}in-band, and the No exception boundary at the server: pure errors escape as generic HTTP 500 #344 500 envelope). Clients now see three error encodings. Recommend a shared helper that JSON-encodes the message ({"Left":…}/errHeaders=[json]). - M2. Reverted fee double-count fix.
9d87a96(drop+ fdin the twocalcDueFeeFeeFlow branches) was fully reverted with its tests. If the double count is real, it is unfixed and untested; if the diagnosis was wrong, the revert should say so. Please open/close an issue either way. - M3. Pool
DefaultByAmtfix is mortgage-only.allocateDefaultByAmt's catch-allreplicate (length balances) assumpsstill applies the full total per asset for Loan/Installment/Lease/Receivable/FixedAsset/ProjectedCashFlow pools — the over-allocation the commit fixes for mortgages persists there (if those types acceptDefaultByAmt). Extend or document. - M4.
periodsBetweenremoved fromLibexports (replaced bymonthsBetween). Source-breaking for library consumers; CHANGELOG doesn't mention it (and the CPPR/ABS semantics ofmonthsBetween— clipped partial months — deserve a line too). - M5.
config.ymlschema change is a deployment interface change.maxScenario/maxDealNumare gone (silently ignored now via generic aeson), and invalid configs now abort startup instead of falling back to 8081 (intended, changelogged as BEHAVIOR CHANGE ✓). Consider logging ignored legacy keys; document that defaults are now per-endpoint caps of 1000.
Low / hygiene
- L1. Four empty commit messages (
d0f2674,bc85499e,59623c2,6ea703b7) and one referencing a non-existent issue (f8b5966"UT for #4321"). Squash/describe before merge. - L2.
UT.LedgerTestis imported bytest/MainTest.hsbut missing from the cabalother-modules—-Wmissing-home-moduleson every test build; add it. - L3.
prorataFactorswith negative individual balances produces negative allocations (only total ≤ 0 is guarded). Edge case; decide semantics. - L4.
swagger.jsonis a committed, runtime-generated artifact: version bumps require regeneration (currently0.52.10✓). A CI check (git diff --exit-code swagger.jsonafter a build) would prevent drift.
Servant API — what absbox needs to adapt
- No route/schema breaks: paths, request/response types and JSON shapes for existing inputs are unchanged. Happy-path clients need no changes.
- New behaviors to handle (optional client work):
- HTTP 400 responses (plain text today) for: per-request run caps (
config.ymlmaxScenario/maxDealNumare ignored; scenario/deal combos are unbounded #345) and misrouted payloads (app/Main.hsscenario/multi-deal handlers are non-exhaustive overRunDealReq#329). - HTTP 500 with JSON
{"Left":"engine error: …"}+ CORS for app-phase exceptions (No exception boundary at the server: pure errors escape as generic HTTP 500 #344) — plus the documented mid-stream abort residual (H3). - New
PrepaymentABSvariant accepted in prepayment assumptions (monthly pools only). - Deployment: new
config.ymlkeys (maxRunNum.<endpoint>,workerNum); old keys ignored; invalid config aborts startup. - Worker cap precedence:
HASTRUCTURE_WORKERS>workerNum>-N.
- HTTP 400 responses (plain text today) for: per-request run caps (
UT coverage — assessment
Well covered: prorata (LibTest), pool DefaultByAmt incl. exceeds-balance (PoolTest +155), PrepaymentABS basic vectors/validation (AssetTest), mflow* accessors, NonPerfAssumption defaults, resolveWorkerNum/checkRunNumLimit/run-limits (UtilTest), #327/#325/#331/#333/#335/#346/#328 (roundtrip over 38 witnesses) and #332 (8 cases) — 520/520 green.
Missing / edge cases to add:
PrepaymentABSbeyond1/rmonths (the crash/negative-SMM region) — H1; highest-value missing test.- No exception boundary at the server: pure errors escape as generic HTTP 500 #344 boundary: no automated test (golden test of
errorBoundarybody/status/CORS, or an integration harness); plus a value-level (lazy body) test to pin the H3 gap after forcing is implemented. - HTTP-transport tests for the 400 paths (
enforceRunCap,badRequest) — pure logic is tested, the response rendering is not; also assert the content-type/envelope consistency from M1. - Docker arm64 smoke test — at minimum run the built image and hit
/versionper platform. - Fee double-count — whatever the resolution of M2 (fix or wontfix), it currently has zero coverage in the merged state.
- Non-mortgage pool
DefaultByAmtallocation behavior (M3) — either a test proving the replicate path is intended, or extend the fix. - Negative-balance prorata edge (L3).
Recommended before merge
- H1 fix + tests (blocking — silent corruption/crash in the new feature).
- H2 Docker fix + per-platform smoke (blocking for the arm64 deliverable).
- H3: implement handler-level forcing or correct the changelog/issue state explicitly.
- M1 decision (JSON-envelope 400s) and M2/M3/M4 follow-up issues or fixes.
- Hygiene: describe the empty-message commits; add
UT.LedgerTestto cabal; push67c7d64(sumTs/sumTsCF/firstDatecrash on empty input #332) into the PR.
…zero denominator)
… (fix arm64, PR #322 H2)
…ue-level escapes now enveloped)
Re-review — after the review round (33 commits, all pushed)Branch state: base 1. Status of the previous review's findings
2. Per-commit review — the five new commits
3. Fresh health metrics (clean
|
…alances; changelog for periodsBetween removal (PR #322)
…alances; changelog for periodsBetween removal (PR #322)
M4 / L1 / L2 / L3 fixed
Previous review comments referencing the old SHAs map through the table above. |
M2 resolved — not a bugThe fee "double-count" fix ( M2 is closed as not a bug; the only open items on this PR remain the tracked backlog (#354 record updates, #330 missing methods, x-partial family, #358 PrepaymentABS over-reporting residual), none of which block merge. |
… workflow_dispatch (PR #322 H2)
…eases (non-draft, gh CLI), add linux/arm64 + macos/arm64 legs, refresh archived actions, GHC 9.8.4
BugFix:
New Feature: