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}