nx_acquire_lib.nx source
↩ module page · 240 lines · 13223 B
1// nx_acquire_lib.nx -- THE LIBRARIAN'S DOOR: what may we do with an EXTERNAL artifact?
2//
3// WHY THIS EXISTS. The estate already holds every primitive for taking in a third-party research
4// artifact: nx_research_fetch mirrors the bytes and emits a provenance line, nx_canon_cid content-
5// addresses them (cid_of_file), nx_asset_provenance signs the binding and already ships a
6// prov_class_downloaded, nx_mirror_orchestrator verifies a digest FAIL-CLOSED and derives an
7// idempotent CAS key, and nx_licgate_lib decides commercial rights. NOTHING JOINS THEM, so an
8// external work has never had a record. This lib is that record's DECISION half.
9//
10// THE MEASUREMENT THAT SHAPED IT, taken 2026-08-25 against the mirrored listing at
11// https://europe.naverlabs.com/research/code/ (knowledge/fetched/cmp_librarian_naverlabs_code.html,
12// 522716 bytes, sha256 deeb6ab8..5592): of the 26 releases on page 1, exactly 2 carry ANY licence
13// string -- 77 permil -- and BOTH are prose parentheticals inside a description, not a field. The
14// markup has no licence element at all. 24 of 26 would require opening the linked repository.
15// => A LIBRARIAN THAT READS THE LICENCE OFF A LISTING PAGE IS WRONG 923 TIMES IN 1000.
16// => EVIDENCE IS ITS OWN AXIS. "the page asserted Apache-2.0" and "we hold the LICENSE file and read
17// it" are DIFFERENT FACTS and must never collapse into one column. An unread licence ABSTAINS.
18// This is the same law nx_licgate_lib's `verified` column encodes -- and which, until 2026-08-25, it
19// parsed and never consulted, so an admittedly-UNCONFIRMED row returned full SHIP_OK.
20//
21// THE FOUR VERDICTS ARE DELIBERATELY INDEPENDENT, because they fail apart in practice. Collapsing
22// them is exactly how a project gets mirrored for study and then accidentally republished:
23// may_mirror -- may we keep a copy at all. THE ANTI-ROT RIGHT.
24// may_redistribute -- may we serve that copy onward to third parties.
25// may_learn_from -- may we study it, and use it as an ORACLE or a BENCHMARK, taking no code.
26// must_attribute -- an OBLIGATION, not a right: it takes the MAX and DEFAULTS TO YES.
27//
28// REVIEW BLOCKS. THE ONLY VERDICT THAT PERMITS IS YES. A consumer that reads REVIEW as permission
29// has misread this lib -- REVIEW means "a human must settle a term a machine may not", which is the
30// third state, not a soft pass. Encoded in the SAME order as nx_licgate_lib (0 NO < 1 REVIEW < 2 YES)
31// so "most restrictive wins" stays a plain MIN and the two rulers can never disagree on direction.
32//
33// PURE BY CONSTRUCTION: aq_decide touches no file and no clock. Its whole input is its arguments, so
34// its gate needs no fixture and cannot go vacuous through a missing one.
35// license_tier: ORIGINAL
36import "nx_syscalls.nx"
37
38// EVIDENCE CLASS for a licence claim -- the axis the estate did not have.
39const AQ_EV_NONE: i64 = 0 // no licence seen anywhere. The 24-of-26 NAVER case.
40const AQ_EV_DECLARED: i64 = 1 // an upstream page ASSERTED one; we have NOT read its text.
41const AQ_EV_READ: i64 = 2 // we hold the licence text in our own mirror and read it.
42
43// RIGHT / OBLIGATION ENCODING -- ordered, identical to nx_licgate_lib's.
44const AQ_NO: i64 = 0
45const AQ_REVIEW: i64 = 1
46const AQ_YES: i64 = 2
47
48// res[] slots.
49const AQ_R_MIRROR: i64 = 0
50const AQ_R_REDIST: i64 = 1
51const AQ_R_LEARN: i64 = 2
52const AQ_R_ATTRIB: i64 = 3
53const AQ_R_SLOTS: i64 = 4
54const AQ_WORD: i64 = 8
55
56// OVERALL CODES -- deliberately the SAME integers nx_licgate_lib's lg_rc returns, so a caller that
57// already branches on a licgate exit code branches identically here instead of learning a second
58// vocabulary. A second verdict vocabulary for one domain is a duplicate ruler made of integers.
59const AQ_RC_OK: i64 = 0
60const AQ_RC_REVIEW: i64 = 3
61const AQ_RC_REFUSE: i64 = 4
62
63func aq_res() -> *i64 { return sys_mmap(AQ_R_SLOTS * AQ_WORD) as *i64 }
64
65// THE DECISION. Inputs are facts, never intentions:
66// ev -- evidence class above, how we know the licence at all
67// redist -- the licence's redistribution right (nx_licgate_lib weights_redist), 0/1/2
68// verified -- the licence row's own verified column: 1 = its text was read, 0 = transcribed only
69// Returns AQ_RC_OK / AQ_RC_REVIEW / AQ_RC_REFUSE and fills res[AQ_R_*].
70func aq_decide(ev: i64, redist: i64, verified: i64, res: *i64) -> i64 {
71 // ATTRIBUTION IS THE DEFAULT, AND IT IS AN OBLIGATION SO IT NEVER ABSTAINS. Every licence in the
72 // estate's rights table -- MIT, Apache-2.0, BSD, CC-BY, CC-BY-SA, CC-BY-NC, OpenRAIL, fair-ai,
73 // Coqui CPML -- requires notice retention or credit. Assuming the obligation costs one line in a
74 // NOTICE file; missing it is a breach. So the safe direction is YES, and under NO evidence at all
75 // it STAYS YES rather than degrading to REVIEW: "I could not look" must never lighten a duty.
76 res[AQ_R_ATTRIB] = AQ_YES
77
78 // LEARNING FROM PUBLISHED WORK IS NOT THE ACT A COPYRIGHT LICENCE GOVERNS -- copying and
79 // distributing are. Reading a public paper, running a published benchmark and measuring ourselves
80 // against it take nothing. This is what makes the artifact usable as an ORACLE, which is the whole
81 // point of taking it in. The separate question -- may we SELL what we built after learning -- is
82 // nx_licgate_lib's out_commercial and is deliberately NOT re-answered here. One ruler per question.
83 res[AQ_R_LEARN] = AQ_YES
84
85 // NO EVIDENCE => ABSTAIN ON THE COPYING RIGHTS. Not REFUSE, not ALLOW.
86 // REFUSE would make the librarian useless, because 923 permil of upstream rows arrive exactly like
87 // this, and a permanently-refusing door teaches every caller to route around it. ALLOW would be a
88 // fabricated permission. REVIEW is the honest third state AND it names the work that clears it:
89 // go and read the licence. Note this is not merely theoretical -- the StarDrinks row on that very
90 // page reads "To download this dataset you need to submit a request online", so a librarian that
91 // mirrored on silence would have breached a gated dataset on its first run.
92 // METADATA ABOUT A WORK IS NOT THE WORK: the record (name, url, description) is always safe to
93 // keep, so an artifact can be preserved against rot even while its BYTES stay unmirrored.
94 if ev == AQ_EV_NONE {
95 res[AQ_R_MIRROR] = AQ_REVIEW
96 res[AQ_R_REDIST] = AQ_REVIEW
97 return AQ_RC_REVIEW
98 }
99
100 // DECLARED BUT UNREAD CANNOT EXCEED REVIEW, however permissive the declared id looks. A listing
101 // page saying "Apache 2.0" is an upstream ASSERTION about a repository we have not opened; the
102 // repo may carry a different LICENSE file, a dual licence, or none. Same rule for a rights-table
103 // row whose own verified column is 0 -- DECLARED MUST NEVER BE SERVED AS MEASURED.
104 if ev == AQ_EV_DECLARED {
105 res[AQ_R_MIRROR] = AQ_REVIEW
106 res[AQ_R_REDIST] = AQ_REVIEW
107 return AQ_RC_REVIEW
108 }
109 if verified == 0 {
110 res[AQ_R_MIRROR] = AQ_REVIEW
111 res[AQ_R_REDIST] = AQ_REVIEW
112 return AQ_RC_REVIEW
113 }
114
115 // READ AND VERIFIED. We hold the licence text and its row was confirmed, so we may state the
116 // rights. Keeping our own copy of a work we lawfully obtained is the weakest act available and is
117 // granted here; passing it ON is the act the licence actually governs, so it takes the table's
118 // redistribution value verbatim -- including 0, which is a real REFUSE and must not be softened
119 // into REVIEW. "I looked and it is forbidden" is a finding; "I could not look" is an abstention.
120 res[AQ_R_MIRROR] = AQ_YES
121 res[AQ_R_REDIST] = redist
122 if redist == AQ_NO { return AQ_RC_REFUSE }
123 if redist == AQ_REVIEW { return AQ_RC_REVIEW }
124 return AQ_RC_OK
125}
126
127// name of a right value, for output that a human reads and a gate can assert on.
128func aq_right_name(v: i64) -> *u8 {
129 if v == AQ_YES { return "YES" as *u8 }
130 if v == AQ_REVIEW { return "REVIEW" as *u8 }
131 if v == AQ_NO { return "NO" as *u8 }
132 return "UNKNOWN" as *u8
133}
134
135func aq_ev_name(ev: i64) -> *u8 {
136 if ev == AQ_EV_READ { return "READ" as *u8 }
137 if ev == AQ_EV_DECLARED { return "DECLARED" as *u8 }
138 if ev == AQ_EV_NONE { return "NONE" as *u8 }
139 return "UNKNOWN" as *u8
140}
141
142func aq_rc_name(rc: i64) -> *u8 {
143 if rc == AQ_RC_OK { return "CLEAR" as *u8 }
144 if rc == AQ_RC_REVIEW { return "REVIEW" as *u8 }
145 if rc == AQ_RC_REFUSE { return "REFUSE" as *u8 }
146 return "UNKNOWN" as *u8
147}
148
149// parse an evidence word from a record row. Unknown text is NOT quietly NONE: it returns -1 so the
150// caller REFUSES a malformed row instead of silently downgrading it to the abstaining case, which
151// would read as an honest abstention and hide a broken producer.
152func aq_ev_parse(s: *u8) -> i64 {
153 var i: i64 = 0
154 let r: *u8 = "READ" as *u8
155 let d: *u8 = "DECLARED" as *u8
156 let n: *u8 = "NONE" as *u8
157 var mr: i64 = 1
158 while r[i] != (0 as u8) { if s[i] != r[i] { mr = 0 } i = i + 1 }
159 if mr == 1 { if s[i] == (0 as u8) { return AQ_EV_READ } }
160 i = 0
161 var md: i64 = 1
162 while d[i] != (0 as u8) { if s[i] != d[i] { md = 0 } i = i + 1 }
163 if md == 1 { if s[i] == (0 as u8) { return AQ_EV_DECLARED } }
164 i = 0
165 var mn: i64 = 1
166 while n[i] != (0 as u8) { if s[i] != n[i] { mn = 0 } i = i + 1 }
167 if mn == 1 { if s[i] == (0 as u8) { return AQ_EV_NONE } }
168 return 0 - 1
169}
170
171// ============================ FIXITY: IS THIS ALL OF THE BYTES? ============================
172//
173// A DIGEST ANSWERS "ARE THESE THE BYTES I HASHED". ONLY A LENGTH ANSWERS "ARE THESE ALL THE BYTES".
174// When a body is truncated in transit and the digest is computed on what ARRIVED, the integrity check
175// PASSES BECAUSE IT WAS COMPUTED ON THE DAMAGE. Researched Aug-2026: neither in-toto's DigestSet nor
176// SPDX's verifiedUsing carries a byte length anywhere, so this failure is invisible to both. Our own
177// provenance row already carries `bytes` beside `sha256` -- and a census proved that field was written
178// and read by NOTHING. This is the reader.
179//
180// THE HEX LENGTH IS DERIVED FROM THE ALGORITHM, NEVER A HAND-PICKED CONSTANT. Measured in the field:
181// the Croissant specification's own canonical FileObject examples put SIX values in a property named
182// `sha256`, of which FOUR are 32 hex characters (an MD5 length) and one is 40 (SHA-1). A validator
183// that recomputes SHA-256 fails the specification's own examples. A field named for one hash routinely
184// carries another, so the NAME is a claim and the LENGTH is the check.
185const AQ_ALG_UNKNOWN: i64 = 0
186const AQ_ALG_MD5: i64 = 1
187const AQ_ALG_SHA1: i64 = 2
188const AQ_ALG_SHA256: i64 = 3
189const AQ_HEXLEN_MD5: i64 = 32
190const AQ_HEXLEN_SHA1: i64 = 40
191const AQ_HEXLEN_SHA256: i64 = 64
192
193// THE THREE STATES A FIXITY CHECK MUST HAVE. "I compared and they differ" and "I had nothing to
194// compare against" take OPPOSITE remedies -- one is a corrupted artifact, the other is an unrecorded
195// one -- so they must never arrive in the same word. UNAVAILABLE is not a soft pass.
196const AQ_FIX_MATCH: i64 = 0
197const AQ_FIX_UNAVAILABLE: i64 = 3
198const AQ_FIX_MISMATCH: i64 = 4
199
200func aq_alg_hexlen(alg: i64) -> i64 {
201 if alg == AQ_ALG_MD5 { return AQ_HEXLEN_MD5 }
202 if alg == AQ_ALG_SHA1 { return AQ_HEXLEN_SHA1 }
203 if alg == AQ_ALG_SHA256 { return AQ_HEXLEN_SHA256 }
204 return 0 - 1
205}
206
207// Does a digest STRING have the shape the algorithm it is filed under would produce?
208// An unknown algorithm returns 0: we cannot certify a shape we have no expected length for, and
209// guessing one would be the fabricated-fact defect wearing a checksum.
210func aq_digest_shape_ok(alg: i64, hexlen: i64) -> i64 {
211 let want: i64 = aq_alg_hexlen(alg)
212 if want < 0 { return 0 }
213 if hexlen != want { return 0 }
214 return 1
215}
216
217// THE FIXITY VERDICT. Both legs must agree, and BOTH must be present to say MATCH.
218// declared_bytes / declared_hexlen -- what the record says we should be holding
219// actual_bytes -- what we measured on the bytes in hand
220// digest_equal -- 1 iff the recomputed digest equals the recorded one
221// A negative declared_bytes or a negative declared_hexlen means the record never carried it, which is
222// UNAVAILABLE -- the in-toto / SPDX case. It is NOT a pass, because it is precisely the state in which
223// a truncated body verifies clean.
224func aq_fixity(alg: i64, declared_bytes: i64, actual_bytes: i64, declared_hexlen: i64, digest_equal: i64) -> i64 {
225 if declared_bytes < 0 { return AQ_FIX_UNAVAILABLE }
226 if declared_hexlen < 0 { return AQ_FIX_UNAVAILABLE }
227 // A MALFORMED DIGEST IS A MISMATCH, NOT AN ABSTENTION. The record does carry a value; it is the
228 // wrong shape for the algorithm it claims, so the record is WRONG rather than missing.
229 if aq_digest_shape_ok(alg, declared_hexlen) == 0 { return AQ_FIX_MISMATCH }
230 if declared_bytes != actual_bytes { return AQ_FIX_MISMATCH }
231 if digest_equal != 1 { return AQ_FIX_MISMATCH }
232 return AQ_FIX_MATCH
233}
234
235func aq_fix_name(v: i64) -> *u8 {
236 if v == AQ_FIX_MATCH { return "MATCH" as *u8 }
237 if v == AQ_FIX_MISMATCH { return "MISMATCH" as *u8 }
238 if v == AQ_FIX_UNAVAILABLE { return "UNAVAILABLE" as *u8 }
239 return "UNKNOWN" as *u8
240}