nx_manga_rating.nx source
↩ module page · 185 lines · 8020 B
1// nx_manga_rating.nx -- THE CONTENT RATING PLANE for the manga lane (mangagen MG24 mr_rate_row,
2// MG32 mr_adult_invariant). A rating is DATA CARRIED ON THE ASSET -- never a property of the
3// account, and never which folder somebody remembered to drop the file in.
4//
5// COMPOSES THE INCUMBENTS AND RE-IMPLEMENTS NEITHER:
6// nx_torrent_access nx_taccess_allow(viewer_level, area) -- the fail-closed VIEWER-LEVEL
7// decision from the operator directive of 2026-06-20. Levels OWNER 3 /
8// FAMILY 1 / ANON 0; areas SFW 0 requires FAMILY, NSFW 1 requires OWNER.
9// This organ NEVER re-decides access; it only says which AREA an asset
10// belongs in, and hands that to the incumbent ruler.
11// nx_wardrobe_state WS_ADULT_AGE -- the adult threshold that already fails closed at
12// construction in the character lane. IMPORTED rather than redeclared:
13// the same constant written twice is two constants to every scanner, and
14// an age threshold that can drift between two organs is a defect generator.
15//
16// THE LOAD-BEARING PROPERTY, and the whole point of the organ:
17// AN UNRATED ASSET IS TREATED AS GATED, NEVER AS SAFE. An absent rating is not evidence of
18// innocence. Every mapping below fails toward the MORE restrictive area, so the failure mode of
19// a missing row is the family viewer cannot see it, never the child can.
20
21import "nx_syscalls.nx"
22import "nx_torrent_access.nx"
23import "nx_wardrobe_state.nx"
24
25// -- THE RATING LADDER. Rows, not opinions: a caller stores the integer, the meaning lives here.
26const MR_RATE_UNRATED: i64 = 0-1
27const MR_RATE_ALLAGES: i64 = 0
28const MR_RATE_TEEN: i64 = 1
29const MR_RATE_MATURE: i64 = 2
30const MR_RATE_ADULT: i64 = 3
31const MR_RATE_MAX: i64 = 3
32
33// -- RETURN CODES. Named so a refusal can say WHICH rule fired rather than just no.
34const MR_OK: i64 = 0
35const MR_E_MINOR: i64 = 0-1
36const MR_E_RATING: i64 = 0-2
37const MR_E_ROW: i64 = 0-3
38
39const MR_ROWCAP: i64 = 512
40const MR_PIPE: i64 = 124
41
42func mr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
43
44func mr_apps(dst: *u8, o: i64, s: *u8) -> i64 {
45 var i: i64 = 0
46 while s[i] != (0 as u8) { dst[o + i] = s[i]; i = i + 1 }
47 return o + i
48}
49
50func mr_num(dst: *u8, o: i64, v: i64) -> i64 {
51 var w: i64 = o
52 var m: i64 = v
53 if m < 0 { dst[w] = 45 as u8; w = w + 1; m = 0 - m }
54 if m == 0 { dst[w] = 48 as u8; return w + 1 }
55 let t: *u8 = sys_mmap(32)
56 var k: i64 = 0
57 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
58 while k > 0 { k = k - 1; dst[w] = t[k]; w = w + 1 }
59 return w
60}
61
62// mr_rating_valid -- a rating is either UNRATED or inside the ladder. Anything else is a MALFORMED
63// ROW and must be reported as such, never silently coerced into a verdict.
64func mr_rating_valid(r: i64) -> i64 {
65 if r == MR_RATE_UNRATED { return 1 }
66 if r < MR_RATE_ALLAGES { return 0 }
67 if r > MR_RATE_MAX { return 0 }
68 return 1
69}
70
71// mr_area_for_rating -- THE MAPPING, and the direction it fails in IS the design.
72// UNRATED maps to NSFW on purpose: an asset nobody has rated is gated, not safe.
73func mr_area_for_rating(r: i64) -> i64 {
74 if r == MR_RATE_UNRATED { return NX_TAREA_NSFW }
75 if r >= MR_RATE_MATURE { return NX_TAREA_NSFW }
76 return NX_TAREA_SFW
77}
78
79// mr_may_serve -- COMPOSE, never re-decide. The AREA comes from the asset own rating; the
80// allow/deny comes from the incumbent access ruler. An invalid rating denies rather than defaulting.
81func mr_may_serve(viewer_level: i64, r: i64) -> i64 {
82 if mr_rating_valid(r) == 0 { return NX_TACCESS_DENY }
83 let area: i64 = mr_area_for_rating(r)
84 return nx_taccess_allow(viewer_level, area)
85}
86
87// mr_adult_invariant -- MG32, FAIL-CLOSED AT CONSTRUCTION. A subject below the adult age may not
88// carry a MATURE-or-above rating. Returns a NAMED code rather than a boolean so the caller can
89// report which rule fired. WS_ADULT_AGE is the character lane threshold, imported not copied.
90func mr_adult_invariant(subject_age: i64, r: i64) -> i64 {
91 if mr_rating_valid(r) == 0 { return MR_E_RATING }
92 if r < MR_RATE_MATURE { return MR_OK }
93 if subject_age < WS_ADULT_AGE { return MR_E_MINOR }
94 return MR_OK
95}
96
97// mr_rate_row -- MG24. Write the rating row for one asset. The ROW is the artifact: a rating that
98// exists only in a caller variable is a rating nobody can audit and nothing can re-check.
99// rate|<asset-cid>|<rating>|<source>|<epoch>
100func mr_rate_row(cid: *u8, r: i64, src: *u8, epoch: i64, out: *u8, cap: i64) -> i64 {
101 if mr_rating_valid(r) == 0 { return MR_E_RATING }
102 let need: i64 = mr_slen(cid) + mr_slen(src) + 48
103 if need > cap { return MR_E_ROW }
104 var o: i64 = 0
105 o = mr_apps(out, o, "rate" as *u8)
106 out[o] = MR_PIPE as u8; o = o + 1
107 o = mr_apps(out, o, cid)
108 out[o] = MR_PIPE as u8; o = o + 1
109 o = mr_num(out, o, r)
110 out[o] = MR_PIPE as u8; o = o + 1
111 o = mr_apps(out, o, src)
112 out[o] = MR_PIPE as u8; o = o + 1
113 o = mr_num(out, o, epoch)
114 out[o] = 0 as u8
115 return o
116}
117
118func mr_puts(s: *u8) -> i64 { sys_write(1, s, mr_slen(s)); return 0 }
119func mr_putn(v: i64) -> i64 { let b: *u8 = sys_mmap(64); let n: i64 = mr_num(b, 0, v); sys_write(1, b, n); return 0 }
120
121func mr_atoi(s: *u8) -> i64 {
122 var i: i64 = 0
123 var neg: i64 = 0
124 if s[0] == (45 as u8) { neg = 1; i = 1 }
125 var v: i64 = 0
126 while s[i] != (0 as u8) {
127 let c: i64 = s[i] as i64
128 if c < 48 { return v }
129 if c > 57 { return v }
130 v = v * 10 + (c - 48)
131 i = i + 1
132 }
133 if neg == 1 { return 0 - v }
134 return v
135}
136
137func main(argc: i64, argv: *i64) -> i64 {
138 if argc < 2 {
139 mr_puts("usage: nx_manga_rating row <cid> <rating> <source> <epoch>\n" as *u8)
140 mr_puts(" nx_manga_rating serve <viewer_level> <rating>\n" as *u8)
141 mr_puts(" nx_manga_rating adult <subject_age> <rating>\n" as *u8)
142 mr_puts("ratings: -1 UNRATED gated, 0 ALLAGES, 1 TEEN, 2 MATURE, 3 ADULT\n" as *u8)
143 return 3
144 }
145 let verb: *u8 = argv[1] as *u8
146
147 if verb[0] == (114 as u8) {
148 if argc < 6 { mr_puts("usage: row <cid> <rating> <source> <epoch>\n" as *u8); return 3 }
149 let out: *u8 = sys_mmap(MR_ROWCAP)
150 let rc: i64 = mr_rate_row(argv[2] as *u8, mr_atoi(argv[3] as *u8), argv[4] as *u8, mr_atoi(argv[5] as *u8), out, MR_ROWCAP)
151 if rc < 0 { mr_puts("REFUSED rule=" as *u8); mr_putn(rc); mr_puts(" rating outside the ladder, or the row would not fit\n" as *u8); return 1 }
152 mr_puts(out); mr_puts("\n" as *u8)
153 return 0
154 }
155
156 if verb[0] == (115 as u8) {
157 if argc < 4 { mr_puts("usage: serve <viewer_level> <rating>\n" as *u8); return 3 }
158 let vl: i64 = mr_atoi(argv[2] as *u8)
159 let r: i64 = mr_atoi(argv[3] as *u8)
160 let a: i64 = mr_may_serve(vl, r)
161 mr_puts("viewer_level=" as *u8); mr_putn(vl)
162 mr_puts(" rating=" as *u8); mr_putn(r)
163 mr_puts(" area=" as *u8); mr_putn(mr_area_for_rating(r))
164 if a == NX_TACCESS_ALLOW { mr_puts(" verdict=ALLOW\n" as *u8); return 0 }
165 mr_puts(" verdict=DENY\n" as *u8)
166 return 1
167 }
168
169 if verb[0] == (97 as u8) {
170 if argc < 4 { mr_puts("usage: adult <subject_age> <rating>\n" as *u8); return 3 }
171 let age: i64 = mr_atoi(argv[2] as *u8)
172 let r2: i64 = mr_atoi(argv[3] as *u8)
173 let rc2: i64 = mr_adult_invariant(age, r2)
174 mr_puts("subject_age=" as *u8); mr_putn(age)
175 mr_puts(" rating=" as *u8); mr_putn(r2)
176 mr_puts(" adult_age=" as *u8); mr_putn(WS_ADULT_AGE)
177 if rc2 == MR_OK { mr_puts(" verdict=OK\n" as *u8); return 0 }
178 if rc2 == MR_E_MINOR { mr_puts(" verdict=REFUSED rule=MR_E_MINOR\n" as *u8); return 1 }
179 mr_puts(" verdict=REFUSED rule=MR_E_RATING\n" as *u8)
180 return 1
181 }
182
183 mr_puts("unknown verb\n" as *u8)
184 return 3
185}