13 — Full Mathematical Audit & Fixes
13 — Full Mathematical Audit & Fixes
Section titled “13 — Full Mathematical Audit & Fixes”Scope: Complete audit of
packages/core/againstfaraid/source texts andfindings/formalism. All arithmetic flaws corrected. All 438 core tests + 289 API tests pass.Methodology: Read all
faraid/source docs andfindings/files. Read every core source file directly. Trace arithmetic against classical examples. Fix each flaw at its mathematical root.
Summary Table
Section titled “Summary Table”| ID | Severity | File | Description | Fixed |
|---|---|---|---|---|
| C2 | Critical | phase2.ts, phase3.ts, phase4.ts | α₂ deferred to phase4; PaternalSister loses farḍ when FB+PB coexist | ✓ |
| C3 | Critical | phase4.ts | GF muqāsama weight counts α₂-excluded siblings | ✓ |
| C4 | Critical | phase4.ts | Akdariyya pool includes maternal siblings (q=3) | ✓ |
| C5 | Critical | phase3.ts | GD topup cascades to great-granddaughter; breaks 2/3 ceiling | ✓ |
| S1 | Significant | phase1.ts | siblingCount includes nephews (j=3, d≥2) | ✓ |
| S2 | Significant | vector.ts | α₁ cascade missing for collaterals at d≥2 | ✓ |
| S3 | Significant | extensions/chain.ts | Non-DAG resolveChain breaks on same-vector sequential deaths | ✓ |
| M1 | Significant | vector.ts | Uterine siblings not blocked by GF (consensus rule) | ✓ |
| M2 | Significant | vector.ts | Cousins not blocked by surviving uncle | ✓ |
| M3 | Significant | vector.ts | Nephews not blocked by GF | ✓ |
Errata (original audit): C1 (GF farḍ-1/6 election) was retracted — the Akdariyya did trigger correctly. The counter-example used Husband=¼ (with-descendants) instead of the correct Husband=½ (no-descendants).
Root Cause: Stale .js Files in src/
Section titled “Root Cause: Stale .js Files in src/”Before any mathematical issue could be verified, a critical infrastructure problem was discovered: stale compiled .js files shadowed the TypeScript sources in src/. When Vitest resolved relative imports (e.g., '../phase2'), it loaded src/phase2.js (old version) instead of src/phase2.ts. All changes to source files were invisible to the test runner.
Fix: Delete all .js files from src/ (they belong only in dist/). After rebuilding with pnpm build, tests resolved correctly.
C2 — PaternalSister Loses Farḍ (α₂ Deferred to Phase4)
Section titled “C2 — PaternalSister Loses Farḍ (α₂ Deferred to Phase4)”Classical case: {FullBrother, PaternalBrother, PaternalSister}
Classical fiqh:
- FullBrother (q=1) outranks PaternalBrother (q=2) by α₂ (القاعدة الثانية)
- PaternalBrother is excluded → PaternalSister has no same-q rescuer → she takes farḍ ½
- Expected: FB=½, PS=½, PB=0
Old behavior (buggy): Phase2 kept all heirs with comment // For now, we keep all. Phase3 saw PB in survivors and assigned PS asaba_bil_ghayr (rescued by PB). Phase4 filtered PB to 0 via minQ, leaving PS also at 0. Both got 0.
Fix: Phase2 now implements α₂ properly:
// In phase2.ts — within each (j, d) stratum where j ∈ {3, 4}:// among males with q ∈ {1, 2}, keep only minQ.// Females exempt: their role (farḍ/asaba_bil_ghayr/asaba_maa_ghayr)// is decided in phase3 against the post-α₂ survivor set.Also: Phase4 retains a minQ filter within the ʿaṣaba assignments pool (for females who became ʿaṣaba in phase3 — e.g., asaba_maa_ghayr FullSister at q=1 outranking PaternalBrother at q=2 when a Daughter is present). Heirs with q=9 (GF, sons, fathers) are never filtered by this rule.
C3 — GF Muqāsama Weight Inflated (Downstream of C2)
Section titled “C3 — GF Muqāsama Weight Inflated (Downstream of C2)”Classical case: {GF, FullBrother, PaternalBrother} — no farḍ heirs
Old result: GF=⅓, FB=⅓, PB=⅓ (PB incorrectly included in muqāsama weight).
Correct: PB excluded by α₂ in phase2. Phase4 GF-Sibling muqāsama runs with sibPoolAsaba = [FB only]. GF=½, FB=½.
Fix: Flows from C2. Once phase2 removes PB from survivors, the muqāsama weight is automatically correct.
C4 — Akdariyya Pool Includes Maternal Siblings (phase4.ts)
Section titled “C4 — Akdariyya Pool Includes Maternal Siblings (phase4.ts)”Source: faraid/jaddma'aikhwah.md:454–514
The Akdariyya post-ʿawl pooling is a repair between GF and his ʿaṣaba competitors (full/paternal sisters). Maternal siblings (q=3) are farḍ heirs with no ʿaṣaba claim. Including them in the pool:
- Inflates the pool beyond the GF+sister amount
- Incorrectly weights them 2:1 (uterine siblings are 1:1 per Qurʾān 4:12)
Fix: One-line: add && a.vector.q <= 2 to the siblingAssignments filter in phase4’s Akdariyya block.
const siblingAssignments = awlResult.filter( (a) => a.role === 'fard' && a.vector.j === 3 && a.vector.q <= 2 // ← added);C5 — GD Topup Cascades Beyond d*+1 (phase3.ts)
Section titled “C5 — GD Topup Cascades Beyond d*+1 (phase3.ts)”Classical rule: When exactly one daughter (d=1, solo) takes ½, granddaughters (d=2) take a collective ⅙ topup to reach the 2/3 group ceiling. Great-granddaughters (d=3) get 0 — the ceiling was already reached at d=2.
Old bug: The code checked only femaleCountAtD[heir.d - 1] === 1. For a great-granddaughter (d=3), this checked whether exactly one granddaughter exists at d=2 — if so, it fired the topup again. With Daughter(½) + Granddaughter(⅙ topup) + GreatGranddaughter(⅙ topup) = 5/6 ≠ 2/3.
Fix: Compute dStar = shallowest depth with eligible female descendants. Topup only at dStar + 1; depths beyond dStar + 1 return 0.
let dStar = Infinity;for (const [depthKey, femaleCount] of cache.femaleCountAtDepth) { if (femaleCount > 0 && !(cache.maleAtDepth.get(depthKey) ?? false)) { if (depthKey < dStar) dStar = depthKey; }}// d === dStar → solo=½, plural=2/3 proportional// d === dStar + 1 → topup ⅙ group (only if dStar had exactly 1 female)// d > dStar + 1 → 0S1 — siblingCount Includes Nephews (phase1.ts)
Section titled “S1 — siblingCount Includes Nephews (phase1.ts)”Source: Qurʾān 4:11 — «فإن كان له إخوة فلأمه السدس». “Siblings” (إخوة) does not include nephews.
Nephews have the vector j=3, d=2 (sibling jiha, depth 2). The old code counted all j=3 heirs:
if (j === 3) { flags.siblingCount += heir.count; } // wrong — included d≥2If only nephews were present (no father to block them), siblingCount ≥ 2 triggered mother’s share halving from ⅓ to ⅙ — incorrect.
Fix:
if (j === 3 && heir.vector.d === 1) { flags.siblingCount += heir.count; }S2, M2 — α₁ Cascade Missing for Collaterals at d≥2 (vector.ts)
Section titled “S2, M2 — α₁ Cascade Missing for Collaterals at d≥2 (vector.ts)”Source: faraid/hajb.md — «القاعدة الأولى: من أدلى بواسطة حجبته تلك الواسطة». The intermediary is the specific person through whom the heir connects to the deceased.
For a nephew (j=3, d=2), the intermediary is his own father (the deceased’s brother, at j=3, d=1), not the deceased’s father. The old code handled all j=3 heirs with the same blocking rule (blocked only by deceased’s father), missing the cascade.
Same defect for cousins (j=4, d=2): blocked by their father (the uncle, j=4, d=1), not by the deceased’s sibling.
Fix in isIntermediaryOf:
if (heir.j === 3) { if (heir.d === 1) { // Direct siblings: father blocks (plus GF in Path A) if (blocker.j === 2 && blocker.d === 1 && blocker.g === 1) return true; ... } // Nephews (d≥2): father, OR sibling-ancestor at d-1, OR GF if (blocker.j === 3 && blocker.d === heir.d - 1 && blocker.g === 1 && blocker.q <= heir.q) return true; ...}// Similar cascade for j=4 cousinsM1 — Uterine Siblings Not Blocked by Grandfather (vector.ts)
Section titled “M1 — Uterine Siblings Not Blocked by Grandfather (vector.ts)”Source: faraid/jaddma'aikhwah.md:35 — «أما الإخوة لأم، فإنهم محجوبون بالجد، بلا خلاف» (“Maternal siblings are blocked by the grandfather, without disagreement”).
This is unanimous across all four schools. The old code blocked uterine siblings (q=3, j=3, d=1) only by the father — not by a surviving grandfather.
Fix: Added to the heir.d === 1 block in isIntermediaryOf:
// M1: uterine siblings also blocked by GF — unanimous across all schoolsif (heir.q === 3 && blocker.j === 2 && blocker.d >= 2 && blocker.g === 1) return true;M3 — Nephews Not Blocked by Grandfather (vector.ts)
Section titled “M3 — Nephews Not Blocked by Grandfather (vector.ts)”Source: faraid/jaddma'aikhwah.md:36 — «أبناء الإخوة الأشقاء وأبناء الإخوة لأب محجوبون بالجد ولا يرثون معه شيئًا» (“Sons of full/paternal brothers are blocked by the grandfather and inherit nothing alongside him”).
Fix: In the heir.j === 3 && heir.d >= 2 block:
if (blocker.j === 2 && blocker.d >= 2 && blocker.g === 1) return true; // GF blocks nephewsS3 — Per-Capita Split in Non-DAG resolveChain (extensions/chain.ts)
Section titled “S3 — Per-Capita Split in Non-DAG resolveChain (extensions/chain.ts)”Problem: When two identical-vector heirs (e.g., two daughters, count=2 in a single entry) were passed to resolveChain, and one died, the vector-match lookup found the first entry (combined count=2, sahm=combined). The old code then deleted the entire vector-matching entry, losing the surviving daughter’s share.
Fix: Per-capita split — track whether the dying heir was the last of their vector-type:
const entryCount = BigInt(deceasedResultEntry.count);const perCapitaSahm = deceasedResultEntry.sahm / entryCount;const deceasedShare = perCapitaSahm;
// hasSurvivors: true iff ≥1 peer with same 5-tuple survivesconst hasSurvivors = deceasedResultEntry.count > 1;if (hasSurvivors) { deceasedResultEntry.count -= 1; deceasedResultEntry.sahm -= perCapitaSahm;}// In the scaledShares loop: if hasSurvivors, carry the decremented entry.// If !hasSurvivors (sole heir died), omit entirely.The correct long-term path for complex munasakhat chains is resolveChainFromDAG, which uses node IDs instead of vector matching.
Correction to tests/test-cases.json — Case 102
Section titled “Correction to tests/test-cases.json — Case 102”Case 102 (FullBrother + PaternalBrother + PaternalSister) had the wrong expected output from before the C2 fix:
| Heir | Old (buggy) | Correct |
|---|---|---|
| FullBrother | 1/1 (100%) | 1/2 (50%) |
| PaternalBrother | 0 | 0 |
| PaternalSister | 0 | 1/2 (50%) |
Updated in tests/test-cases.json.
New Rule Discovered During Audit: M1
Section titled “New Rule Discovered During Audit: M1”The consensus rule “maternal siblings blocked by grandfather” was documented clearly in faraid/jaddma'aikhwah.md but absent from the implementation. This was a straightforward miss, not a disputed rule — all four schools agree.
What Was NOT Flawed
Section titled “What Was NOT Flawed”- The Akdariyya trigger itself (GF takes farḍ ⅙ when residual ≤ 0): correctly handled. The counter-example in the original audit was wrong.
- The ascendant eligibility predicates (
eligibility.ts): correctly implement MF rule. - The GF-Sibling muqāsama formula itself: correct when given clean input (post-C2 fix).
- The Mafqūd enumeration (
min.ts): correct 2ⁿ enumeration and min-share categorization. - The Taṣḥīḥ algorithm (
phase5.ts): correct LCM-based integer normalization. - The Walā resolution (
resolver/wala.ts): correct priority cascade in muʿtiq’s frame. - The 4-axiom system in
findings/02-axioms.md: mathematically sound. - The 6-phase pipeline ordering: correct (Phase 1 before Phase 2 is essential for ghost pressure).
Verification
Section titled “Verification”Canonical test cases after all fixes (PATH_B / SHAFII config unless noted):
| Case | Heirs | Expected |
|---|---|---|
| Akdariyya | Husband + Mother + GF + FullSister | H=9/27, M=6/27, GF=8/27, FS=4/27 |
| C2 golden | FB + PB + PS | FB=½, PS=½, PB=0 |
| C3 golden | GF + FB + PB | GF=½, FB=½, PB=0 |
| C5 golden | Daughter + Granddaughter + GreatGranddaughter | ½, ⅙, 0 |
| S1 golden | Wife + Mother + NephewFull×2 | Wife=¼, Mother=⅓ (not ⅙) |
| M1 golden | GF + BrMaternal×2 | GF=1, BrMaternal=0 |
| M2 golden | Uncle + Cousin | Uncle=1, Cousin=0 |
All cases are pinned in packages/core/src/__tests__/integration/audit-fixes.test.ts.
References
Section titled “References”- Intermediary blocking:
faraid/hajb.md§القاعدة الأولى - ʿAṣaba ranking:
faraid/hajb.md§القاعدة الثانية - Grandfather-sibling:
faraid/jaddma'aikhwah.md— lines 35–36 (maternal/nephews consensus) - Akdariyya:
faraid/jaddma'aikhwah.md:454–514 - Mother’s halving (plural siblings): Qurʾān 4:11
- Group ceiling (2/3):
findings/02-axioms.md§β₂,findings/05-proofs.mdTheorem 4