code wiki / (root) / nx_damages_gate.nx

nx_damages_gate.nx source

↩ module page · 124 lines · 8191 B

1// nx_damages_gate.nx -- PREJUDGMENT INTEREST GATE. 2// Proves the exact day count (via composed nx_sol), simple and annually-compounded interest, that compound 3// exceeds simple for the same period, the total-award composition, a full real scenario ($50k loss over an 4// exact 3-year interval at 8pct simple = $12k interest), and the fail-closed refusals (judgment before 5// accrual, negative principal/rate/days, sentinel propagation). license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 6// 7// D001-MIGRATED 2026-08-26: the verdict now comes from the shared nx_gate_verdict base class, so the outcome 8// is readable FROM OUTSIDE via the exit code (/api/gate_run) and a harness frame is recorded. The hand-rolled 9// cnt[] survives ONLY to feed the legacy `pass=/fail=` line, which is this gate's public signature; gv_ctr is 10// the canonical count, so a tooth that silently stops running can no longer read GREEN. 11// The fail-closed teeth are named `neg-control-` so the gate-law census can SEE them: a control nobody can 12// find is a control nobody maintains. 13 14import "nx_damages_lib.nx" 15import "nx_matter_lib.nx" 16import "nx_gate_verdict.nx" 17 18// ---- TEST VECTORS, named for the SCENARIO each one encodes (rule 11: name for PURPOSE, never for value). 19// A gate's expected values are FIXTURES, not configuration. They are pinned here rather than read from a 20// conf on purpose: a test whose expected value comes from the same config the subject reads is comparing 21// the system against itself and can never fail. Hoisting them makes the rule-11 ratchet measure production 22// logic instead of fixtures, and makes each assertion read as the scenario it proves. 23const ACCRUAL_YEAR: i64 = 2020 // date of loss, both scenarios 24const JUDGMENT_YEAR_2YR: i64 = 2022 // exactly two years after accrual (the span contains a leap day) 25const JUDGMENT_YEAR_3YR: i64 = 2023 // judgment year for the $50k scenario 26const DAYS_2YR_LEAP_SPAN: i64 = 731 // 2020-01-01 -> 2022-01-01, leap-aware 27const DAYS_3YR_SPAN: i64 = 1095 // 2020-03-15 -> 2023-03-15 28const PRINCIPAL_1K_CENTS: i64 = 100000 // $1,000.00 29const PRINCIPAL_50K_CENTS: i64 = 5000000 // $50,000.00 30const RATE_10PCT_BP: i64 = 1000 // 10.00% in basis points 31const RATE_8PCT_BP: i64 = 800 // 8.00% in basis points 32const DAYS_1YR: i64 = 365 33const DAYS_2YR: i64 = 730 34const YEARS_2: i64 = 2 35const INTEREST_1YR_SIMPLE_CENTS: i64 = 10000 // $100.00 36const INTEREST_2YR_SIMPLE_CENTS: i64 = 20000 // $200.00 37const INTEREST_2YR_COMPOUND_CENTS: i64 = 21000 // $210.00 -- compounding beats simple by $10 over the same 2 years 38const AWARD_1K_PLUS_2YR_CENTS: i64 = 120000 // $1,200.00 = principal + 2yr simple interest 39const INTEREST_50K_3YR_CENTS: i64 = 1200000 // $12,000.00 = 8pct x 3yr x $50k 40const AWARD_50K_TOTAL_CENTS: i64 = 6200000 // $62,000.00 = principal + prejudgment interest 41 42func dm_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 43func dm_putn(v: i64) -> i64 { 44 let t: *u8 = sys_mmap(32) 45 var o: i64 = 0 46 var m: i64 = v 47 if m < 0 { t[o] = 45 as u8; o = o + 1; m = 0 - m } 48 let d: *u8 = sys_mmap(32) 49 var k: i64 = 0 50 if m == 0 { d[0] = 48 as u8; k = 1 } 51 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 52 var i: i64 = 0 53 while i < k { t[o] = d[k - 1 - i]; o = o + 1; i = i + 1 } 54 sys_write(1, t, o) 55 return 0 56} 57// Every tooth reports through gv_check, so the DECLARED count and the EXECUTED count are the same object. 58func dm_ck(cnt: *i64, ctr: *i64, name: *u8, got: i64, want: i64) -> i64 { 59 var ok: i64 = 0 60 if got == want { ok = 1 } 61 if ok == 1 { 62 cnt[0] = cnt[0] + 1 63 dm_puts(" PASS " as *u8); dm_puts(name); dm_puts(" = " as *u8); dm_putn(got); dm_puts("\n" as *u8) 64 } else { 65 cnt[1] = cnt[1] + 1 66 dm_puts(" FAIL " as *u8); dm_puts(name); dm_puts(" got " as *u8); dm_putn(got) 67 dm_puts(" want " as *u8); dm_putn(want); dm_puts("\n" as *u8) 68 } 69 gv_check(name, ok, ctr) 70 return ok 71} 72 73func main(argc: i64, argv: *i64) -> i64 { 74 let cnt: *i64 = sys_mmap(16) as *i64 75 cnt[0] = 0 76 cnt[1] = 0 77 let ctr: *i64 = gv_ctr() 78 79 dm_puts("NISHI-DAMAGES-GATE (prejudgment interest: exact day count via nx_sol, simple/compound, fail-closed)\n" as *u8) 80 81 // ---- exact day count (composed from nx_sol) ---- 82 dm_ck(cnt, ctr, "D1 2020-01-01 to 2022-01-01 = 731 days (leap-aware)" as *u8, dmg_days_between(ACCRUAL_YEAR, 1, 1, JUDGMENT_YEAR_2YR, 1, 1), DAYS_2YR_LEAP_SPAN) 83 84 // ---- simple interest: $1000.00 principal at 10pct ---- 85 dm_ck(cnt, ctr, "D2 simple 100000c @10pct for 365d = 10000 ($100)" as *u8, dmg_simple_interest(PRINCIPAL_1K_CENTS, RATE_10PCT_BP, DAYS_1YR), INTEREST_1YR_SIMPLE_CENTS) 86 dm_ck(cnt, ctr, "D3 simple 100000c @10pct for 730d = 20000 ($200)" as *u8, dmg_simple_interest(PRINCIPAL_1K_CENTS, RATE_10PCT_BP, DAYS_2YR), INTEREST_2YR_SIMPLE_CENTS) 87 dm_ck(cnt, ctr, "D4 simple for 0 days = 0" as *u8, dmg_simple_interest(PRINCIPAL_1K_CENTS, RATE_10PCT_BP, 0), 0) 88 89 // ---- annually compounded ---- 90 dm_ck(cnt, ctr, "D5 compound 100000c @10pct for 2yr = 21000 interest" as *u8, dmg_compound_interest(PRINCIPAL_1K_CENTS, RATE_10PCT_BP, YEARS_2), INTEREST_2YR_COMPOUND_CENTS) 91 let comp: i64 = dmg_compound_interest(PRINCIPAL_1K_CENTS, RATE_10PCT_BP, YEARS_2) 92 let simp: i64 = dmg_simple_interest(PRINCIPAL_1K_CENTS, RATE_10PCT_BP, DAYS_2YR) 93 var cgt: i64 = 0 94 if comp > simp { cgt = 1 } 95 dm_ck(cnt, ctr, "D6 compound > simple over the same 2 years" as *u8, cgt, 1) 96 dm_ck(cnt, ctr, "D7 compound for 0 years = 0" as *u8, dmg_compound_interest(PRINCIPAL_1K_CENTS, RATE_10PCT_BP, 0), 0) 97 98 // ---- total award ---- 99 dm_ck(cnt, ctr, "D8 total award = principal + interest = 120000" as *u8, dmg_total_award(PRINCIPAL_1K_CENTS, INTEREST_2YR_SIMPLE_CENTS), AWARD_1K_PLUS_2YR_CENTS) 100 101 // ---- *REAL SCENARIO: $50,000 loss on 2020-03-15, judgment 2023-03-15, 8pct simple ---- 102 let days: i64 = dmg_days_between(ACCRUAL_YEAR, 3, 15, JUDGMENT_YEAR_3YR, 3, 15) 103 dm_ck(cnt, ctr, "D9 exact interval 2020-03-15 to 2023-03-15 = 1095 days" as *u8, days, DAYS_3YR_SPAN) 104 let intr: i64 = dmg_simple_interest(PRINCIPAL_50K_CENTS, RATE_8PCT_BP, days) 105 dm_ck(cnt, ctr, "D10 prejudgment interest = 1200000c ($12,000 = 8pct x 3yr x $50k)" as *u8, intr, INTEREST_50K_3YR_CENTS) 106 dm_ck(cnt, ctr, "D11 total award = 6200000c ($62,000)" as *u8, dmg_total_award(PRINCIPAL_50K_CENTS, intr), AWARD_50K_TOTAL_CENTS) 107 108 // ---- *FAIL-CLOSED: each of these MUST refuse. A deny-guard with no positive control passes every 109 // negative test by refusing everything -- D1..D11 above are those positive controls. 110 dm_ck(cnt, ctr, "D12 neg-control-judgment-before-accrual -> DMG_BAD" as *u8, dmg_days_between(JUDGMENT_YEAR_2YR, 1, 1, ACCRUAL_YEAR, 1, 1), DMG_BAD) 111 dm_ck(cnt, ctr, "D13 neg-control-negative-principal -> DMG_BAD" as *u8, dmg_simple_interest(0 - 100, RATE_10PCT_BP, DAYS_1YR), DMG_BAD) 112 dm_ck(cnt, ctr, "D14 neg-control-negative-rate -> DMG_BAD" as *u8, dmg_simple_interest(PRINCIPAL_1K_CENTS, 0 - 5, DAYS_1YR), DMG_BAD) 113 dm_ck(cnt, ctr, "D15 neg-control-negative-days -> DMG_BAD" as *u8, dmg_simple_interest(PRINCIPAL_1K_CENTS, RATE_10PCT_BP, 0 - 5), DMG_BAD) 114 dm_ck(cnt, ctr, "D16 neg-control-total-award-propagates-the-sentinel" as *u8, dmg_total_award(PRINCIPAL_1K_CENTS, DMG_BAD), DMG_BAD) 115 116 dm_puts("nx_damages_gate: pass=" as *u8); dm_putn(cnt[0]) 117 dm_puts(" fail=" as *u8); dm_putn(cnt[1]); dm_puts("\n" as *u8) 118 if cnt[1] == 0 { 119 dm_puts("DAMAGES nx_damages: VERDICT=GREEN (exact prejudgment interest on a composed nx_sol day count; fail-closed)\n" as *u8) 120 } else { 121 dm_puts("DAMAGES nx_damages: VERDICT=RED\n" as *u8) 122 } 123 return gv_verdict("damages" as *u8, ctr, "exact prejudgment interest on a composed nx_sol day count; simple and annually-compounded; every fail-closed refusal carries a named neg-control" as *u8) 124}