code wiki / (root) / nx_manga_rating_lib.nx

nx_manga_rating_lib.nx source

↩ module page · 145 lines · 6530 B

1// nx_manga_rating_lib.nx -- THE CONTENT RATING DECISION as a shared lib (mangagen MG24 mr_rate_row, 2// MG32 mr_adult_invariant). SINGLE RESPONSIBILITY: this file DECIDES, nx_manga_rating is a thin CLI 3// over it, and nx_manga_rating_gate tests it IN-PROCESS. One decision, one home -- so the CLI and the 4// gate cannot drift into two different answers, which is the whole reason for the split. 5// 6// COMPOSES THE INCUMBENTS AND RE-IMPLEMENTS NEITHER: 7// nx_torrent_access nx_taccess_allow(viewer_level, area) -- the fail-closed VIEWER-LEVEL decision 8// from the operator directive of 2026-06-20. OWNER 3 / FAMILY 1 / ANON 0; 9// areas SFW 0 requires FAMILY, NSFW 1 requires OWNER. This lib NEVER re-decides 10// access: it says which AREA an asset belongs in and hands that to the incumbent. 11// nx_wardrobe_state WS_ADULT_AGE -- the adult threshold already failing closed at construction in 12// the character lane. IMPORTED, never redeclared: the same constant written twice 13// is two constants to every scanner, and an age threshold that can drift between 14// two organs is a defect generator rather than a policy. 15// 16// THE LOAD-BEARING PROPERTY: 17// AN UNRATED ASSET IS TREATED AS GATED, NEVER AS SAFE. An absent rating is not evidence of innocence. 18// Every mapping fails toward the MORE restrictive area, so a missing row costs a family viewer a page 19// it could have seen -- never the reverse. 20 21import "nx_syscalls.nx" 22import "nx_torrent_access.nx" 23import "nx_wardrobe_state.nx" 24 25const MR_RATE_UNRATED: i64 = 0-1 26const MR_RATE_ALLAGES: i64 = 0 27const MR_RATE_TEEN: i64 = 1 28const MR_RATE_MATURE: i64 = 2 29const MR_RATE_ADULT: i64 = 3 30const MR_RATE_MAX: i64 = 3 31 32// EXPOSURE, as measured by the wardrobe lane (ws_exposure over the wardrobe vector: a gated zone 33// below WS_MODEST). This lib CONSUMES that measurement and never re-derives it. 34const MR_CLOTHED: i64 = 0 35const MR_EXPOSED: i64 = 1 36 37const MR_OK: i64 = 0 38const MR_E_MINOR: i64 = 0-1 39const MR_E_RATING: i64 = 0-2 40const MR_E_ROW: i64 = 0-3 41 42const MR_ROWCAP: i64 = 512 43const MR_PIPE: i64 = 124 44 45func mr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 46 47func mr_apps(dst: *u8, o: i64, s: *u8) -> i64 { 48 var i: i64 = 0 49 while s[i] != (0 as u8) { dst[o + i] = s[i]; i = i + 1 } 50 return o + i 51} 52 53func mr_num(dst: *u8, o: i64, v: i64) -> i64 { 54 var w: i64 = o 55 var m: i64 = v 56 if m < 0 { dst[w] = 45 as u8; w = w + 1; m = 0 - m } 57 if m == 0 { dst[w] = 48 as u8; return w + 1 } 58 let t: *u8 = sys_mmap(32) 59 var k: i64 = 0 60 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 61 while k > 0 { k = k - 1; dst[w] = t[k]; w = w + 1 } 62 return w 63} 64 65func mr_atoi(s: *u8) -> i64 { 66 var i: i64 = 0 67 var neg: i64 = 0 68 if s[0] == (45 as u8) { neg = 1; i = 1 } 69 var v: i64 = 0 70 while s[i] != (0 as u8) { 71 let c: i64 = s[i] as i64 72 if c < 48 { return v } 73 if c > 57 { return v } 74 v = v * 10 + (c - 48) 75 i = i + 1 76 } 77 if neg == 1 { return 0 - v } 78 return v 79} 80 81// mr_rating_valid -- a rating is either UNRATED or inside the ladder. Anything else is a MALFORMED ROW 82// and is reported as such, never silently coerced into a verdict. 83func mr_rating_valid(r: i64) -> i64 { 84 if r == MR_RATE_UNRATED { return 1 } 85 if r < MR_RATE_ALLAGES { return 0 } 86 if r > MR_RATE_MAX { return 0 } 87 return 1 88} 89 90// mr_area_for_rating -- THE MAPPING, and the direction it fails in IS the design. 91func mr_area_for_rating(r: i64) -> i64 { 92 if r == MR_RATE_UNRATED { return NX_TAREA_NSFW } 93 if r >= MR_RATE_MATURE { return NX_TAREA_NSFW } 94 return NX_TAREA_SFW 95} 96 97// mr_may_serve -- COMPOSE, never re-decide. Area from the asset own rating, allow/deny from the 98// incumbent ruler. An invalid rating DENIES rather than defaulting to anything. 99func mr_may_serve(viewer_level: i64, r: i64) -> i64 { 100 if mr_rating_valid(r) == 0 { return NX_TACCESS_DENY } 101 let area: i64 = mr_area_for_rating(r) 102 return nx_taccess_allow(viewer_level, area) 103} 104 105// mr_depiction_legal -- MG32, AXIS 2: WHAT MAY BE DEPICTED. Age x EXPOSURE, never age x rating. 106// at or above WS_ADULT_AGE -> MR_OK, UNCONDITIONALLY. A declared adult is an adult. There is 107// deliberately NO appearance test and NO hold: a petite, slight or 108// youthful-looking adult character is lawful, and a classifier that 109// held such work for review would be a false-positive engine policing 110// legal content. The estate's own law is that a detector with false 111// positives is worse than none. 112// below, CLOTHED -> MR_OK. Minor characters exist in ordinary comics and school stories. 113// below, EXPOSED -> MR_E_MINOR, refused at construction. THE ONLY HARD LINE, and it is 114// the same one ws_set already enforces per gated zone. 115// 116// THE FIRST CUT OF THIS FUNCTION GATED ON THE WORK'S RATING and was WRONG: it would have refused a 117// fully clothed minor character in a MATURE-rated story, which is ordinary lawful content. The rating 118// of a WORK says nothing about whether a given FIGURE is lawfully depicted -- those are two axes and 119// conflating them both over-blocks lawful work and misses the case that actually matters. 120func mr_depiction_legal(subject_age: i64, exposed: i64) -> i64 { 121 if subject_age >= WS_ADULT_AGE { return MR_OK } 122 if exposed == MR_EXPOSED { return MR_E_MINOR } 123 return MR_OK 124} 125 126// mr_rate_row -- MG24. The ROW is the artifact: a rating that exists only in a caller variable is a 127// rating nobody can audit and nothing can re-check. 128// rate|<asset-cid>|<rating>|<source>|<epoch> 129func mr_rate_row(cid: *u8, r: i64, src: *u8, epoch: i64, out: *u8, cap: i64) -> i64 { 130 if mr_rating_valid(r) == 0 { return MR_E_RATING } 131 let need: i64 = mr_slen(cid) + mr_slen(src) + 48 132 if need > cap { return MR_E_ROW } 133 var o: i64 = 0 134 o = mr_apps(out, o, "rate" as *u8) 135 out[o] = MR_PIPE as u8; o = o + 1 136 o = mr_apps(out, o, cid) 137 out[o] = MR_PIPE as u8; o = o + 1 138 o = mr_num(out, o, r) 139 out[o] = MR_PIPE as u8; o = o + 1 140 o = mr_apps(out, o, src) 141 out[o] = MR_PIPE as u8; o = o + 1 142 o = mr_num(out, o, epoch) 143 out[o] = 0 as u8 144 return o 145}