nx_organkind.nx source
↩ module page · 207 lines · 10429 B
1// nx_organkind.nx -- WHAT KIND OF THING IS THIS ORGAN? The classifier seq1492 says both ship verbs need.
2//
3// THE DEFECT IT FIXES: `/api/promote target=nx_torrent_get` -> 400 'daemon/oracle names are never
4// promotable (use /api/deploy)'; `/api/deploy target=nx_torrent_get` -> 400 'unknown target'. Promote
5// calls it a DAEMON, deploy has never heard of it, so a gate-proven binary (nx_poolgov_gate 21/21,
6// deleting a hardcoded MAXP=32) cannot ship by ANY sanctioned route. Both verdicts come from matching
7// NAMES -- a `torrent`/daemon-ish substring -- rather than from what the organ IS.
8// LAW: TWO VERBS THAT DISAGREE ABOUT AN ARTEFACT'S KIND LEAVE IT UNSHIPPABLE -- the classifier, not the
9// allowlist, is the defect.
10//
11// WHY A DECLARED KIND AND NOT A BETTER HEURISTIC: a smarter name rule is still a guess, and the cost of
12// guessing is asymmetric -- misfiling a DAEMON as a one-shot lets promote swap a binary underneath a
13// running process with no health check and no rollback (a rule-26 hazard), while misfiling a one-shot as
14// a daemon merely makes it unshippable. So the kind must be DECLARED DATA (rule 11/17), and anything
15// undeclared must be REFUSED BY BOTH VERBS rather than fall through to a name guess.
16//
17// THE ASYMMETRY IS THE WHOLE POLICY:
18// deploy = for things with a HEALTH SURFACE (a port to probe, so never-brick rollback is meaningful).
19// promote = for one-shots/oracles: atomic rename + .prev backup, no probe needed because nothing is
20// holding the old binary open -- its 'health' is that the NEXT invocation succeeds.
21// A one-shot handed to deploy would have a port-based health check run against a target with no port,
22// making the never-brick verdict meaningless-or-wrong BY CONSTRUCTION.
23//
24// Lives in runtime/ (PRIMITIVE layer, seq1450) so BOTH mgmt handlers can reach it; imports nothing above.
25// license_tier: ORIGINAL Pure + read-only. No hw writes (Rule 26).
26import "nx_syscalls.nx"
27
28const OK_UNKNOWN: i64 = 0
29const OK_DAEMON: i64 = 1
30const OK_ONESHOT: i64 = 2
31const OK_ORACLE: i64 = 3
32const OK_LIB: i64 = 4
33const OK_NL: i64 = 10
34const OK_SP: i64 = 32
35const OK_TAB: i64 = 9
36
37func ok_is_sep(c: i64) -> i64 {
38 if c == OK_SP { return 1 }
39 if c == OK_TAB { return 1 }
40 return 0
41}
42
43func ok_kind_code(buf: *u8, s: i64, e: i64) -> i64 {
44 let n: i64 = e - s
45 if n == 6 { if buf[s] == (100 as u8) { return OK_DAEMON } }
46 if n == 7 { if buf[s] == (111 as u8) { if buf[s+1] == (110 as u8) { return OK_ONESHOT } } }
47 if n == 6 { if buf[s] == (111 as u8) { return OK_ORACLE } }
48 if n == 3 { if buf[s] == (108 as u8) { return OK_LIB } }
49 return OK_UNKNOWN
50}
51
52// Look up <name> in a declared-kind table: one `<name> <kind>` row per line.
53// FAIL-CLOSED: an absent name is OK_UNKNOWN, never a guessed default -- that refusal is the point.
54// Match is WHOLE-FIELD (name must be followed by a separator), so `nx_torrent` can never match
55// `nx_torrent_get` -- the substring lie that produced this defect in the first place.
56func ok_kind_of(buf: *u8, n: i64, name: *u8) -> i64 {
57 var nl: i64 = 0
58 while name[nl] != (0 as u8) { nl = nl + 1 }
59 if nl == 0 { return OK_UNKNOWN }
60 var i: i64 = 0
61 while i < n {
62 var bol: i64 = 0
63 if i == 0 { bol = 1 }
64 if i > 0 { if buf[i-1] == (OK_NL as u8) { bol = 1 } }
65 if bol == 1 {
66 var j: i64 = 0
67 var m: i64 = 1
68 while j < nl {
69 if i + j >= n { m = 0; j = nl } else {
70 if buf[i + j] != name[j] { m = 0; j = nl } else { j = j + 1 }
71 }
72 }
73 if m == 1 {
74 var after: i64 = i + nl
75 var whole: i64 = 0
76 if after < n { if ok_is_sep(buf[after] as i64) == 1 { whole = 1 } }
77 if whole == 1 {
78 // skip the separator run after the name (clean sentinel-free idiom: a flag, not an
79 // out-of-range index -- the k=n+1 / k-1 trick I first wrote collapsed to k=n and made
80 // EVERY lookup return UNKNOWN; the gate caught it at 2/11 before it reached mgmt)
81 var k: i64 = after
82 var r1: i64 = 1
83 while r1 == 1 {
84 if k >= n { r1 = 0 } else {
85 if ok_is_sep(buf[k] as i64) == 1 { k = k + 1 } else { r1 = 0 }
86 }
87 }
88 // the kind token ends at the first separator or newline
89 var kend: i64 = k
90 var r2: i64 = 1
91 while r2 == 1 {
92 if kend >= n { r2 = 0 } else {
93 if ok_is_sep(buf[kend] as i64) == 1 { r2 = 0 } else {
94 if buf[kend] == (OK_NL as u8) { r2 = 0 } else { kend = kend + 1 }
95 }
96 }
97 }
98 return ok_kind_code(buf, k, kend)
99 }
100 }
101 }
102 i = i + 1
103 }
104 return OK_UNKNOWN
105}
106
107const OK_CONFBUF: i64 = 65536
108const OK_CONF_PATH_LEN: i64 = 256
109
110// Self-contained lookup: open the declared-kind table and classify <name>. Returns OK_UNKNOWN when the
111// file is absent or the name is undeclared -- callers then fall back to their legacy policy, so adopting
112// this is ADDITIVE and inert until the conf exists.
113// The file read lives HERE, not in the caller, so a consumer's adoption is ONE LINE. That is deliberate:
114// seq1410 measured that primitives fail to get adopted, and the cost of adoption is the main reason --
115// a primitive that makes its consumer do the plumbing is a primitive that stays dark.
116func ok_kind_of_path(path: *u8, name: *u8) -> i64 {
117 let fd: i64 = sys_openat_rd(path)
118 if fd < 0 { return OK_UNKNOWN }
119 let b: *u8 = sys_mmap(OK_CONFBUF)
120 var tot: i64 = 0
121 var r: i64 = sys_read(fd, b, OK_CONFBUF - 1)
122 while r > 0 {
123 tot = tot + r
124 if tot >= OK_CONFBUF - 1 { r = 0 } else { r = sys_read(fd, ((b as i64) + tot) as *u8, OK_CONFBUF - 1 - tot) }
125 }
126 sys_close(fd)
127 if tot <= 0 { sys_munmap(b, OK_CONFBUF); return OK_UNKNOWN }
128 let k: i64 = ok_kind_of(b, tot, name)
129 sys_munmap(b, OK_CONFBUF)
130 return k
131}
132
133// deploy is ONLY for things with a health surface to probe.
134func ok_may_deploy(kind: i64) -> i64 {
135 if kind == OK_DAEMON { return 1 }
136 return 0
137}
138
139// promote is for one-shots and oracles: atomic rename + .prev, nothing holds the old binary open.
140// A LIB is neither -- it is compiled INTO consumers, so shipping one means rebuilding them.
141func ok_may_promote(kind: i64) -> i64 {
142 if kind == OK_ONESHOT { return 1 }
143 if kind == OK_ORACLE { return 1 }
144 return 0
145}
146
147// The honest end state: an UNDECLARED organ is refused by BOTH verbs, loudly, instead of being
148// name-guessed into the wrong one. Returns 1 when the kind is declared and at least one verb accepts it.
149func ok_is_shippable(kind: i64) -> i64 {
150 if ok_may_deploy(kind) == 1 { return 1 }
151 if ok_may_promote(kind) == 1 { return 1 }
152 return 0
153}
154
155// ---- seq1789: A TERMINAL SUFFIX IS A DECLARATION, A SUBSTRING IS A GUESS --------------------------
156// THE DEFECT THIS FIXES (measured 2026-07-30): /api/promote refused nx_survey_serve_gate because the
157// legacy shape heuristic md_promote_deny asks `does the name CONTAIN "serve"`. It does -- inside the
158// word `survey_serve` -- so an ORACLE that runs to completion was classified as a long-lived DAEMON and
159// became unshippable by every sanctioned route. Its gate then read exit=127 (binary absent) and that one
160// RED was the only thing holding an otherwise 35/35-grounded, 3/4-green domain off MEASURED-HONEST.
161// LAW: A SUBSTRING CANNOT TELL A ROLE FROM A WORD THAT MERELY APPEARS IN ONE. Position is meaning.
162//
163// WHY THIS IS NOT THE NAME-GUESSING THIS FILE EXISTS TO FORBID. The policy above refuses inference
164// because the cost is ASYMMETRIC: misfiling a DAEMON as a one-shot lets promote swap a binary under a
165// live process with no probe and no rollback (rule 26), while the reverse merely blocks a build. That
166// same asymmetry is what makes a TERMINAL suffix safe where a substring is not:
167// - it is a CONVENTION ENFORCED AT CREATION, not an inference about runtime behaviour;
168// - it is ONE-DIRECTIONAL -- it can only ever yield OK_ORACLE, the SAFE side of the asymmetry, so this
169// rule is INCAPABLE BY CONSTRUCTION of producing the dangerous misfile;
170// - the claim is MECHANICALLY CHECKED, not asserted: nx_organkind_gate proves that no name carrying
171// one of these suffixes can classify as a daemon, and that a DECLARED daemon still wins if one ever
172// did. A guarantee that is only promised is the failure mode this ecosystem keeps paying for.
173// A DECLARED kind always wins -- see ok_kind_or_suffix. This is strictly a floor under UNDECLARED names,
174// which previously fell through to the substring lie.
175func ok_ends_with(name: *u8, sfx: *u8) -> i64 {
176 var nl: i64 = 0
177 while name[nl] != (0 as u8) { nl = nl + 1 }
178 var sl: i64 = 0
179 while sfx[sl] != (0 as u8) { sl = sl + 1 }
180 if sl == 0 { return 0 }
181 if nl < sl { return 0 }
182 var i: i64 = 0
183 var m: i64 = 1
184 while i < sl {
185 if name[nl - sl + i] != sfx[i] { m = 0; i = sl } else { i = i + 1 }
186 }
187 return m
188}
189
190// The oracle-shaped suffixes. Kept in ONE function so the rule and its proof cannot drift apart: the
191// gate exercises this same entry point, so a suffix added here without a daemon-free proof fails there.
192func ok_suffix_kind(name: *u8) -> i64 {
193 if ok_ends_with(name, "_gate" as *u8) == 1 { return OK_ORACLE }
194 if ok_ends_with(name, "_test" as *u8) == 1 { return OK_ORACLE }
195 if ok_ends_with(name, "_kat" as *u8) == 1 { return OK_ORACLE }
196 return OK_UNKNOWN
197}
198
199// THE PRECEDENCE, IN ONE PLACE so no consumer re-implements it (and re-implements it differently, which
200// is how this concern grew two confs and two readers already): a DECLARED kind always wins; only an
201// UNDECLARED name falls back to the terminal-suffix rule; anything else stays UNKNOWN and is refused by
202// both verbs exactly as before. Passing the declared kind IN rather than re-reading the conf keeps this
203// pure and lets the caller pay for the file read once.
204func ok_kind_or_suffix(declared: i64, name: *u8) -> i64 {
205 if declared != OK_UNKNOWN { return declared }
206 return ok_suffix_kind(name)
207}