code wiki / (root) / nx_staging_guard_candidate_t296.nx

nx_staging_guard_candidate_t296.nx source

↩ module page · 415 lines · 18458 B

1// nx_staging_guard.nx -- REFUSE A PROMOTE THAT WOULD WALK A TARGET BACKWARDS. 2// 3// WHY (measured 2026-08-06): the deploy queue is armed to destroy work. md_promote_staged already 4// carries md_prov_ok, which refuses a promote whose CONTENT this target ALREADY HELD (seq1484). 5// That guard is blind to the COMMON CASE: a staged .new that was never itself promoted is a FIRST 6// SIGHTING, so md_prov_ok records it as the baseline and returns ALLOW -- even when the staged file 7// PREDATES the live binary it would replace. A .new older than its live .elf is not a pending 8// upgrade, it is A ROLLBACK WEARING AN UPGRADE FILENAME, and nothing in the staging area marks it 9// as one. It sits there looking deployable forever. 10// 11// *SIZE IS A SCREEN, NOT PROOF (nx_treecanon_gate:15, and the banked law: a byte DECREASE after an 12// addition is a REVERT). A legitimate refactor can shrink a binary, and an operator-swap fix is 13// BYTE-IDENTICAL. So this module never decides on size alone: it also measures CAPABILITY-TOKEN 14// LOSS -- how many long printable tokens present in the LIVE binary are ABSENT from the staged one. 15// That is the honest reading of would-lose-capability; size is only the cheap first screen. 16// (2026-08-18: that sentence became TRUE IN CODE, not only in prose -- see the SHRINK note in 17// sg_classify. Whole-program DCE in nx_cc made "smaller" the normal direction of every rebuild.) 18// 19// *IT REFUSES, IT NEVER DELETES. Every verdict is fail-closed and reversible: a refusal leaves live, 20// .prev and the staged file untouched (Rule 13 additive-only, Rule 26 never-brick). Nothing in this 21// module writes persistent state of any kind. 22// 23// *THRESHOLDS ARE DATA (Rule 11), read from knowledge/staging_hygiene.conf; the consts below are 24// bootstrap defaults only, and every one of them is overridable without a rebuild (Rule 17). 25// 26// DIALECT: this module is IMPORTED by nx_mgmt_data -> nx_mgmt_api, so it must compile under today's 27// hub compiler: plain-if (NO else -- seq533 desyncs the imported parser), no empty string literals 28// (seq907 aliases the next literal), <=6 params (seq239). ONE implementation, every consumer 29// (Rule 15) -- a guard that only the CLI imports is not a guard the deploy plane has. 30// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 31import "nx_syscalls.nx" 32import "_hdl_build/nx_contentdiff_lib.nx" // THE content ruler -- shared with the nx_contentdiff oracle, never a second copy 33 34const SG_STAT_SIZE_OFF: i64 = 48 35const SG_STAT_MTIME_OFF: i64 = 88 36const SG_STATBUF: i64 = 256 37const SG_DAY: i64 = 86400 38const SG_FILECAP: i64 = 8388608 39const SG_CONFCAP: i64 = 8192 40 41// ---- sealed verdict classes ---- 42const SG_NONE: i64 = 0 43const SG_FORWARD: i64 = 1 44const SG_ORPHAN: i64 = 2 45const SG_BACKDATE: i64 = 3 46const SG_EXPIRED: i64 = 4 47const SG_SHRINK: i64 = 5 48const SG_CAPLOSS: i64 = 6 49const SG_UNQUALIFIED:i64=7 50 51// ---- out[] slots ---- 52const SG_O_LIVESZ: i64 = 0 53const SG_O_NEWSZ: i64 = 1 54const SG_O_LIVEMT: i64 = 2 55const SG_O_NEWMT: i64 = 3 56const SG_O_LOSTPERMIL: i64 = 4 57const SG_O_AGEDAYS: i64 = 5 58const SG_O_FLAGS: i64 = 6 59const SG_O_CHECKED: i64 = 7 60const SG_O_MISSING: i64 = 8 61const SG_O_SHRINKPERMIL: i64 = 9 62// 1 when staged and live are BYTE-IDENTICAL. Distinguishes a harmless re-stage from the mutation-class 63// blind spot: same size + differing content + zero capability delta is the signature nothing else here sees. 64const SG_O_IDENTICAL: i64 = 10 65const SG_O_GAINED: i64 = 11 // tokens staged has that live lacks -- the half a loss detector cannot see 66const SG_O_LIVERUNS: i64 = 12 // qualifying runs in live 67const SG_O_CANDRUNS: i64 = 13 // qualifying runs in staged; >= liveruns means nothing was net-removed 68const SG_O_CONTENT_STATE:i64=14 69const SG_O_SOURCE_REMOVED:i64=15 70const SG_OUT_SLOTS: i64 = 16 71 72// ---- cfg[] slots ---- 73const SG_C_MAXAGE: i64 = 0 74const SG_C_SHRINKTOL: i64 = 1 75const SG_C_CAPLOSS: i64 = 2 76const SG_C_MINTOK: i64 = 3 77const SG_C_MAXTOK: i64 = 4 78const SG_C_CAPLOSSABS: i64 = 5 79const SG_C_CAPLOSSMIN: i64 = 6 80const SG_C_SLOTS: i64 = 8 81 82// ---- bootstrap defaults (lowest priority; conf overrides each) ---- 83const SG_D_MAXAGE: i64 = 14 84const SG_D_SHRINKTOL: i64 = 20 85// DELIBERATELY DIFFERENT from the shipped knowledge/staging_hygiene.conf value. If these two were equal, 86// a gate asserting the conf was read would be VACUOUS -- it would pass identically when the file is 87// missing. The conf is what operates; this is only the no-file fallback. 88const SG_D_CAPLOSS: i64 = 25 89// ABSOLUTE floor, OR'd with the permil one. TUNED FROM THE MEASURED DISTRIBUTION, not from taste: 90// permil alone is SIZE-DEPENDENT (one lost run is 24 permil in a 41-run binary but 4 permil in a 250-run 91// one), so a single threshold cannot separate an ordinary string edit from a real capability loss. It 92// caught its own author within the hour: an edit that renamed ONE report line scored 24 permil and this 93// guard refused the build that contained it. THAT MATTERS MORE THAN THE FALSE POSITIVE -- a guard which 94// fires on routine work teaches operators to pass the override by reflex, and a bypass that becomes 95// routine is a guard that has already been removed. 96const SG_D_CAPLOSSABS: i64 = 5 97// Minimum ABSOLUTE absent-run count before the PERMIL bar is allowed to fire. Added 2026-08-06 after a 98// SECOND false positive, on nx_offc_install: ONE deliberately reworded message string scored 125 permil 99// because that binary has only EIGHT sampled runs. The OR added in the previous round did NOT fix 100// size-dependence -- it only added a second way to FIRE, and the permil bar alone still trips on any 101// small binary where a single edit is a large fraction. The T11 anti-bypass tooth missed it because its 102// fixture carries 40 runs, not 8. A SINGLE ABSENT RUN IS AN ORDINARY EDIT AT EVERY SAMPLE SIZE. 103const SG_D_CAPLOSSMIN: i64 = 2 104const SG_D_MINTOK: i64 = 12 105const SG_D_MAXTOK: i64 = 256 106// longest prefix of a run this path will search for. Bounds the promote-time cost; the CLI oracle 107// passes 0 (unbounded) because it runs to completion and can afford the whole run. 108const SG_MAXTOKLEN: i64 = 255 109 110func sg_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 111 112// size in bytes, -1 when absent/unstattable. Fail-closed: never invents a size. 113func sg_size(path: *u8) -> i64 { 114 let sb: *u8 = sys_mmap(SG_STATBUF) 115 if sys_fstatat(path, sb) != 0 { sys_munmap(sb, SG_STATBUF); return 0 - 1 } 116 let p: *i64 = ((sb as i64) + SG_STAT_SIZE_OFF) as *i64 117 let v: i64 = p[0] 118 sys_munmap(sb, SG_STATBUF) 119 return v 120} 121 122// st_mtime seconds, -1 when absent. Offset 88 is the channel proven by _freshness_gate T5. 123func sg_mtime(path: *u8) -> i64 { 124 let sb: *u8 = sys_mmap(SG_STATBUF) 125 if sys_fstatat(path, sb) != 0 { sys_munmap(sb, SG_STATBUF); return 0 - 1 } 126 let p: *i64 = ((sb as i64) + SG_STAT_MTIME_OFF) as *i64 127 let v: i64 = p[0] 128 sys_munmap(sb, SG_STATBUF) 129 return v 130} 131 132func sg_slurp(path: *u8, buf: *u8, cap: i64) -> i64 { 133 let fd: i64 = sys_openat_rd(path) 134 if fd < 0 { return 0 - 1 } 135 var tot: i64 = 0 136 var go: i64 = 1 137 while go == 1 { 138 if tot >= cap { go = 0 } 139 if go == 1 { 140 let r: i64 = sys_read(fd, ((buf as i64) + tot) as *u8, cap - tot) 141 if r <= 0 { go = 0 } 142 if r > 0 { tot = tot + r } 143 } 144 } 145 sys_close(fd) 146 return tot 147} 148 149// NOTE: the printable-run scanner that used to live here was DELETED, not kept alongside. It was a 150// second implementation of the same ruler nx_contentdiff already specified, and two rulers that can 151// disagree about the same question are worse than one. Both consumers now call cdl_lost. 152 153// 1 when the nl bytes at nd appear anywhere in the bn bytes at b 154func sg_contains(b: *u8, bn: i64, nd: *u8, nl: i64) -> i64 { 155 if nl <= 0 { return 1 } 156 if nl > bn { return 0 } 157 let first: u8 = nd[0] 158 let last: i64 = bn - nl 159 var i: i64 = 0 160 while i <= last { 161 if b[i] == first { 162 var k: i64 = 1 163 var ok: i64 = 1 164 while k < nl { 165 if b[i + k] != nd[k] { ok = 0; k = nl } 166 if ok == 1 { k = k + 1 } 167 } 168 if ok == 1 { return 1 } 169 } 170 i = i + 1 171 } 172 return 0 173} 174 175func sg_eq_at(b: *u8, pos: i64, key: *u8, kl: i64) -> i64 { 176 var k: i64 = 0 177 while k < kl { 178 if b[pos + k] != key[k] { return 0 } 179 k = k + 1 180 } 181 return 1 182} 183 184func sg_parse_int_at(b: *u8, n: i64, pos: i64) -> i64 { 185 var v: i64 = 0 186 var seen: i64 = 0 187 var i: i64 = pos 188 var go: i64 = 1 189 while go == 1 { 190 if i >= n { go = 0 } 191 if go == 1 { 192 let c: i64 = b[i] as i64 193 if c < 48 { go = 0 } 194 if c > 57 { go = 0 } 195 if go == 1 { v = (v * 10) + (c - 48); seen = 1; i = i + 1 } 196 } 197 } 198 if seen == 0 { return 0 - 1 } 199 return v 200} 201 202// value of a line-anchored `key=<int>` row, or -1 when the key is absent 203func sg_conf_get(b: *u8, n: i64, key: *u8) -> i64 { 204 let kl: i64 = sg_strlen(key) 205 if kl <= 0 { return 0 - 1 } 206 let last: i64 = n - kl 207 var i: i64 = 0 208 while i <= last { 209 if sg_eq_at(b, i, key, kl) == 1 { 210 var atstart: i64 = 0 211 if i == 0 { atstart = 1 } 212 if i > 0 { if b[i - 1] == (10 as u8) { atstart = 1 } } 213 if atstart == 1 { 214 if (i + kl) < n { 215 if b[i + kl] == (61 as u8) { return sg_parse_int_at(b, n, i + kl + 1) } 216 } 217 } 218 } 219 i = i + 1 220 } 221 return 0 - 1 222} 223 224// Rule 17 chain: bootstrap defaults, then knowledge/staging_hygiene.conf overrides each key it names. 225// Returns 1 when a conf file was read, 0 when running on defaults alone. 226func sg_conf_load(cfg: *i64) -> i64 { 227 cfg[SG_C_MAXAGE] = SG_D_MAXAGE 228 cfg[SG_C_SHRINKTOL] = SG_D_SHRINKTOL 229 cfg[SG_C_CAPLOSS] = SG_D_CAPLOSS 230 cfg[SG_C_MINTOK] = SG_D_MINTOK 231 cfg[SG_C_MAXTOK] = SG_D_MAXTOK 232 cfg[SG_C_CAPLOSSABS] = SG_D_CAPLOSSABS 233 cfg[SG_C_CAPLOSSMIN] = SG_D_CAPLOSSMIN 234 let buf: *u8 = sys_mmap(SG_CONFCAP) 235 let n: i64 = sg_slurp("knowledge/staging_hygiene.conf" as *u8, buf, SG_CONFCAP - 2) 236 if n <= 0 { sys_munmap(buf, SG_CONFCAP); return 0 } 237 let a: i64 = sg_conf_get(buf, n, "max_stage_age_days" as *u8) 238 if a >= 0 { cfg[SG_C_MAXAGE] = a } 239 let s: i64 = sg_conf_get(buf, n, "shrink_tolerance_permil" as *u8) 240 if s >= 0 { cfg[SG_C_SHRINKTOL] = s } 241 let c: i64 = sg_conf_get(buf, n, "caploss_refuse_permil" as *u8) 242 if c >= 0 { cfg[SG_C_CAPLOSS] = c } 243 let t: i64 = sg_conf_get(buf, n, "min_token_len" as *u8) 244 if t >= 0 { cfg[SG_C_MINTOK] = t } 245 let m: i64 = sg_conf_get(buf, n, "max_tokens_sampled" as *u8) 246 if m >= 0 { cfg[SG_C_MAXTOK] = m } 247 let ca: i64 = sg_conf_get(buf, n, "caploss_refuse_absolute" as *u8) 248 if ca >= 0 { cfg[SG_C_CAPLOSSABS] = ca } 249 let cm: i64 = sg_conf_get(buf, n, "caploss_min_absent" as *u8) 250 if cm >= 0 { cfg[SG_C_CAPLOSSMIN] = cm } 251 sys_munmap(buf, SG_CONFCAP) 252 return 1 253} 254 255// Structured comparison shares the CLI iterator; errors never mean zero loss. 256struct SgContentEvidence { state:i64, checked:i64, missing:i64, gained:i64, live_runs:i64, candidate_runs:i64, source_removed:i64, permil:i64 } 257func sg_measure_bytes(lb:*u8,ln:i64,nb:*u8,nn:i64,cfg:*i64,e:*SgContentEvidence)->i64{ 258 e.state=CDS_INVALID;e.checked=-1;e.missing=-1;e.gained=-1;e.live_runs=-1;e.candidate_runs=-1;e.source_removed=-1;e.permil=-1 259 var x:NxCdStructured;var y:NxCdStructured 260 let xi:i64=cds_init(&x,lb,ln);let yi:i64=cds_init(&y,nb,nn) 261 let p:*i64=sys_mmap_try(CDL_P_SLOTS*__size_of(i64)) as *i64 262 var rc:i64=xi;if rc==0{rc=yi};if (p as i64)<=0{rc=CDS_RESOURCE} 263 var f:NxCdResult;var r:NxCdResult 264 if rc==0{ 265 p[CDL_P_MINLEN]=cfg[SG_C_MINTOK];p[CDL_P_MAXSAMPLES]=cfg[SG_C_MAXTOK];p[CDL_P_MAXTOKLEN]=SG_MAXTOKLEN 266 rc=cds_measure(&x,&y,p,&f);if rc==0{rc=f.state} 267 if rc==0{rc=cds_measure(&y,&x,p,&r);if rc==0{rc=r.state}} 268 if rc==0{ 269 e.checked=f.checked;e.missing=f.lost;e.gained=r.lost;e.live_runs=f.runs;e.candidate_runs=r.runs;e.source_removed=f.source_removed 270 e.permil=cds_permil(f.lost,f.checked);if e.permil<0{rc=e.permil} 271 } 272 } 273 cds_close(&x);cds_close(&y) 274 if (p as i64)>0{sys_munmap_direct(p as *u8,CDL_P_SLOTS*__size_of(i64))} 275 e.state=rc;return rc 276} 277// Legacy raw-buffer API retained; structured classification uses sg_measure_bytes. 278func sg_caploss(lb: *u8, ln: i64, nb: *u8, nn: i64, cfg: *i64, out2: *i64) -> i64 { 279 out2[0] = 0 280 out2[1] = 0 281 let prm: *i64 = sys_mmap(8 * CDL_P_SLOTS) as *i64 282 prm[CDL_P_MINLEN] = cfg[SG_C_MINTOK] 283 prm[CDL_P_MAXSAMPLES] = cfg[SG_C_MAXTOK] 284 prm[CDL_P_MAXTOKLEN] = SG_MAXTOKLEN 285 let o3: *i64 = sys_mmap(8 * CDL_O_SLOTS) as *i64 286 let permil: i64 = cdl_lost(lb, ln, nb, nn, prm, o3) 287 out2[0] = o3[CDL_O_CHECKED] 288 out2[1] = o3[CDL_O_LOST] 289 // SYMMETRIC PASS 2026-08-06. THREE TIMES this session this guard refused a legitimate edit of its 290 // own author's source, and every time the "lost" token had merely been RENAMED (fork_literals -> 291 // elf_literals): the old string vanished from live, an equally distinctive one appeared in staged, 292 // and a ONE-SIDED measure can only ever see the vanishing half. 293 // A LOSS DETECTOR THAT COUNTS ONLY WHAT VANISHED CANNOT TELL A RENAME FROM A REMOVAL. 294 // No second ruler is needed and none is written: cdl_lost is SYMMETRIC BY CONSTRUCTION, so the very 295 // same call with its arguments SWAPPED yields the tokens the STAGED binary has that live lacks. 296 // out2[2]=gained out2[3]=live runs out2[4]=candidate runs. 297 let o4: *i64 = sys_mmap(8 * CDL_O_SLOTS) as *i64 298 cdl_lost(nb, nn, lb, ln, prm, o4) 299 out2[2] = o4[CDL_O_LOST] 300 sys_munmap(o4 as *u8, 8 * CDL_O_SLOTS) 301 out2[3] = cdl_count_runs(lb, ln, cfg[SG_C_MINTOK]) 302 out2[4] = cdl_count_runs(nb, nn, cfg[SG_C_MINTOK]) 303 sys_munmap(prm as *u8, 8 * CDL_P_SLOTS) 304 sys_munmap(o3 as *u8, 8 * CDL_O_SLOTS) 305 return permil 306} 307 308const SG_ERR_NOENT:i64=-2 309const SG_IO:i64=-204 310struct SgFileEvidence { state:i64,size:i64,mtime:i64 } 311func sg_file_evidence(path:*u8,e:*SgFileEvidence)->i64{ 312 e.state=CDS_RESOURCE;e.size=-1;e.mtime=-1 313 let sb:*u8=sys_mmap_try(SG_STATBUF) 314 if (sb as i64)<=0{return e.state} 315 let rc:i64=sys_fstatat(path,sb) 316 if rc==0{e.size=*(((sb as i64)+SG_STAT_SIZE_OFF) as *i64);e.mtime=*(((sb as i64)+SG_STAT_MTIME_OFF) as *i64)} 317 sys_munmap_direct(sb,SG_STATBUF);e.state=rc;return rc 318} 319func sg_content_paths(livep:*u8,newp:*u8,lsz:i64,nsz:i64,cfg:*i64,out:*i64)->i64{ 320 var an:i64=0;var bn:i64=0 321 let b:*u8=sys_map_file(newp,&bn) 322 if (b as i64)<=0{return SG_IO} 323 var a:*u8=0 as *u8 324 if lsz>=0{a=sys_map_file(livep,&an)} 325 var rc:i64=0 326 if bn!=nsz{rc=SG_IO} 327 if lsz>=0{if (a as i64)<=0{rc=SG_IO};if an!=lsz{rc=SG_IO}} 328 var e:SgContentEvidence 329 if rc==0{ 330 if lsz<0{rc=sg_measure_bytes(b,bn,b,bn,cfg,&e)} 331 if lsz>=0{rc=sg_measure_bytes(a,an,b,bn,cfg,&e)} 332 if rc==0{ 333 out[SG_O_CHECKED]=e.checked;out[SG_O_MISSING]=e.missing;out[SG_O_GAINED]=e.gained 334 out[SG_O_LIVERUNS]=e.live_runs;out[SG_O_CANDRUNS]=e.candidate_runs 335 out[SG_O_SOURCE_REMOVED]=e.source_removed;out[SG_O_LOSTPERMIL]=e.permil 336 if lsz>=0{out[SG_O_IDENTICAL]=cdl_bytes_eq(a,an,b,bn)} 337 } 338 } 339 if (a as i64)>0{sys_munmap_direct(a,an)} 340 sys_munmap_direct(b,bn);return rc 341} 342// No writes. Complete mappings are process-local and reclaimed on every return. 343func sg_classify(livep:*u8,newp:*u8,now:i64,cfg:*i64,out:*i64)->i64{ 344 var i:i64=0;while i<SG_OUT_SLOTS{out[i]=0;i=i+1} 345 out[SG_O_CONTENT_STATE]=CDS_INVALID;out[SG_O_MISSING]=-1;out[SG_O_CHECKED]=-1;out[SG_O_SOURCE_REMOVED]=-1 346 var n:SgFileEvidence;var l:SgFileEvidence 347 let ns:i64=sg_file_evidence(newp,&n) 348 out[SG_O_NEWSZ]=n.size;out[SG_O_NEWMT]=n.mtime;out[SG_O_LIVESZ]=-1 349 if ns==SG_ERR_NOENT{out[SG_O_CONTENT_STATE]=ns;return SG_NONE} 350 if ns!=0{out[SG_O_CONTENT_STATE]=ns;return SG_UNQUALIFIED} 351 let ls:i64=sg_file_evidence(livep,&l) 352 out[SG_O_LIVESZ]=l.size;out[SG_O_LIVEMT]=l.mtime 353 if ls!=0{if ls!=SG_ERR_NOENT{out[SG_O_CONTENT_STATE]=ls;return SG_UNQUALIFIED}} 354 let rc:i64=sg_content_paths(livep,newp,l.size,n.size,cfg,out) 355 out[SG_O_CONTENT_STATE]=rc 356 if rc!=0{return SG_UNQUALIFIED} 357 var age:i64=0;if now>n.mtime{age=(now-n.mtime)/SG_DAY};out[SG_O_AGEDAYS]=age 358 var shrink:i64=0;if l.size>0{if n.size<l.size{shrink=cds_permil(l.size-n.size,l.size)}} 359 out[SG_O_SHRINKPERMIL]=shrink 360 var flags:i64=0 361 if ls==0{if n.mtime<l.mtime{flags=flags+1}} 362 if age>cfg[SG_C_MAXAGE]{flags=flags+2} 363 var caphit:i64=0 364 if out[SG_O_MISSING]>=cfg[SG_C_CAPLOSSABS]{caphit=1} 365 if out[SG_O_LOSTPERMIL]>cfg[SG_C_CAPLOSS]{if out[SG_O_MISSING]>=cfg[SG_C_CAPLOSSMIN]{caphit=1}} 366 // Preserve existing small-exchange policy; it is not proof of behavioral equivalence. 367 if out[SG_O_GAINED]>=out[SG_O_MISSING]{if out[SG_O_CANDRUNS]>=out[SG_O_LIVERUNS]{if out[SG_O_MISSING]<cfg[SG_C_CAPLOSSABS]{caphit=0}}} 368 // File-table removals are exhaustive and cannot disappear through sampling or exchange. 369 if out[SG_O_SOURCE_REMOVED]>0{caphit=1} 370 if caphit==1{flags=flags+8};out[SG_O_FLAGS]=flags 371 if ls==SG_ERR_NOENT{return SG_ORPHAN} 372 if flags%2==1{return SG_BACKDATE} 373 if (flags/2)%2==1{return SG_EXPIRED} 374 if (flags/8)%2==1{return SG_CAPLOSS} 375 return SG_FORWARD 376} 377 378// 1 = this staged artifact may be promoted. Anything unrecognised is REFUSED (fail-closed). 379func sg_allows(c: i64) -> i64 { 380 if c == SG_FORWARD { return 1 } 381 if c == SG_ORPHAN { return 1 } 382 return 0 383} 384 385func sg_class_name(c: i64) -> *u8 { 386 if c == SG_NONE { return "NONE" as *u8 } 387 if c == SG_FORWARD { return "FORWARD" as *u8 } 388 if c == SG_ORPHAN { return "ORPHAN" as *u8 } 389 if c == SG_BACKDATE { return "BACKDATE" as *u8 } 390 if c == SG_EXPIRED { return "EXPIRED" as *u8 } 391 if c == SG_SHRINK { return "SHRINK" as *u8 } 392 if c == SG_CAPLOSS { return "CAPLOSS" as *u8 } 393 if c == SG_UNQUALIFIED { return "UNQUALIFIED" as *u8 } 394 return "UNKNOWN" as *u8 395} 396func sg_allows_override(c:i64,explicitApproval:i64)->i64{ 397 if sg_allows(c)==1{return 1} 398 if explicitApproval!=1{return 0} 399 if c==SG_BACKDATE{return 1} 400 if c==SG_EXPIRED{return 1} 401 if c==SG_SHRINK{return 1} 402 if c==SG_CAPLOSS{return 1} 403 return 0 404} 405 406// Legacy scratch entrypoint retained; structured classification does not use it. 407static sg_lb: *u8 408static sg_nb: *u8 409 410func sg_scratch() -> i64 { 411 if (sg_lb as i64) == 0 { sg_lb = sys_mmap(SG_FILECAP) } 412 if (sg_nb as i64) == 0 { sg_nb = sys_mmap(SG_FILECAP) } 413 return 1 414} 415