nx_asset_record.nx source
↩ module page · 238 lines · 12884 B
1// nx_asset_record.nx -- UNIVERSAL ORGANIZATION TOOLING arc, R0.
2//
3// THE unified, content-addressed ASSET RECORD: every artifact (a generated image, a downloaded
4// video, a doc, a dataset, an audio clip, a product) BECOMES one of these -- one model, all kinds
5// (org_research.tsv: unified-base-type, schema.org CreativeWork is the base, subtypes specialize).
6// Keyed by its CID (content-addressed-id): same bytes -> same id everywhere; any byte change ->
7// a different id (the inventory's self-proof of integrity).
8//
9// THE BACKBONE NEEDS ZERO NEW STORAGE/IDENTITY/CRYPTO -- this organ is pure COMPOSITION:
10// * identity + canonical serialize -> nx_canon_cid (canon_encode / cid_of)
11// * tolerant decode -> nx_uxf_decode (canon_decode) [house-of-cards safe]
12// * self-describing profiled CID -> nx_uxf_cid (uxf_cid_profiled, codec=UXF_DATA)
13// * durable segmented KV store -> nx_seg_store (ss_begin/ss_add/ss_build_keys/ss_commit/
14// ss_open/ss_hget), key = the record's CID
15// A record is a SET OF (key,value) string fields fed to canon_encode -> exactly one canonical
16// byte string -> exactly one CID (insertion order is irrelevant by construction; canon_encode
17// key-sorts). Field ACCESS is by key name over a decoded (keys[],vals[]) pair -- never struct-field
18// aliasing (heap-mmap + buffer/offset idiom only, per the build rules).
19//
20// RECORD MODEL (cited to org_research.tsv CONFIRMED claims):
21// type image|video|doc|dataset|audio|product (unified-base-type)
22// -- Dublin Core core spine (dublin-core-core): -----------------------------------------
23// title dc:title
24// creator dc:creator
25// date dc:date (created/issued, ISO-8601 string)
26// identifier dc:identifier (external/native id; CID is the PRIMARY id, this is a sidecar)
27// subject dc:subject (free-text subject line; tags[] is the keyword facet)
28// rights dc:rights
29// -- W3C PROV provenance class (provenance-class-three; entity<-activity<-agent shape): -----
30// prov_class human | machine | downloaded
31// prov_agent person name | software name | "external"
32// prov_tool (machine) the generating tool/app
33// prov_model (machine) the model id
34// prov_source (downloaded) the source URL
35// prov_date (downloaded) the fetch date
36// -- type-specific MEDIA block (schema-mediaobject; only meaningful for image/video/audio): --
37// width / height / duration / encoding_format
38// -- organization facets: ---------------------------------------------------------------
39// tags comma-joined keyword facet (faceted-findability)
40// location where the bytes live (path / NAS volume / URL)
41// lifecycle_stage create|ingest|manage|use|archive|retire (dam-lifecycle)
42// is_current "1" live / "0" soft-retired (ADDITIVE law #13: never delete)
43// classification private | professional (private-vs-pro-acl)
44// relations comma-joined related CIDs (PROV wasDerivedFrom graph edges)
45//
46// Empty/absent optional fields are simply not added (a downloaded asset carries prov_source but no
47// prov_tool, a doc carries no width). R0 = the record + its gate ONLY; signals/dashboard/dedup/acl
48// are later rungs that READ these records. No hardware/persistent-firmware writes (Rule 26).
49// license_tier: ORIGINAL
50import "nx_syscalls.nx"
51import "nx_canon_cid.nx"
52import "nx_uxf_cid.nx"
53import "nx_uxf_decode.nx"
54import "nx_seg_store.nx"
55
56// ---- small string helpers (no libc; null-terminated bytes) ----
57func ar_len(s: *u8) -> i64 {
58 var n: i64 = 0
59 while s[n] != (0 as u8) { n = n + 1 }
60 return n
61}
62
63func ar_streq(a: *u8, b: *u8) -> i64 {
64 var i: i64 = 0
65 while 1 == 1 {
66 if a[i] != b[i] { return 0 }
67 if a[i] == (0 as u8) { return 1 }
68 i = i + 1
69 }
70 return 1
71}
72
73// is this field value present? (non-null pointer AND non-empty string)
74func ar_present(v: *u8) -> i64 {
75 if (v as i64) == 0 { return 0 }
76 if v[0] == (0 as u8) { return 0 }
77 return 1
78}
79
80// append one (key,value) field into the parallel keys[]/vals[] arrays IFF the value is present.
81// kv[0] = current field count (mutated). Single responsibility: one field, conditionally.
82func ar_addf(keys: *i64, vals: *i64, kv: *i64, key: *u8, val: *u8) -> i64 {
83 if ar_present(val) == 0 { return kv[0] }
84 let n: i64 = kv[0]
85 keys[n] = key as i64
86 vals[n] = val as i64
87 kv[0] = n + 1
88 return kv[0]
89}
90
91// ---- ENCODE: record field set -> canonical NXR1 bytes, returns byte length ----
92// Callers populate a (keys[],vals[]) field set with ar_addf (omitting empty optionals), then call
93// ar_encode(keys, vals, n, out). The ARRAY shape (not 24 positional args) is deliberate: the
94// sovereign compiler caps a call at 16 args, AND a data-driven field set is extensible -- a new
95// field is a new ar_addf line, never a signature change (#19 contract stability / #25 add data).
96// canon_encode key-sorts, so INSERTION ORDER never affects the bytes or the CID. `out` must be
97// caller-mmap'd (>= sum of field bytes + framing; callers use 8192). Pure/deterministic.
98func ar_encode(keys: *i64, vals: *i64, n: i64, out: *u8) -> i64 {
99 return canon_encode(keys, vals, n, out)
100}
101
102// The CANONICAL FIELD-KEY VOCABULARY (one definition, reused by every caller so spelling can't
103// drift). Convenience: stamp the full record field set in ONE call from a small set of grouped
104// buffers, staying under the 16-arg cap by passing the prov_* block and the media block as the
105// helpers already produce them. Each *present* field is added; empties are skipped.
106// core[] = [type,title,creator,date,identifier,subject,rights] (7 i64 ptrs, any may be empty)
107// prov[] = [prov_class,prov_agent,prov_tool,prov_model,prov_source,prov_date] (6 ptrs)
108// media[]= [width,height,duration,encoding_format] (4 ptrs)
109// org[] = [tags,location,lifecycle_stage,is_current,classification,relations] (6 ptrs)
110// Returns the field count populated into keys/vals (then call ar_encode).
111func ar_fields(core: *i64, prov: *i64, media: *i64, org: *i64, keys: *i64, vals: *i64) -> i64 {
112 let kv: *i64 = sys_mmap(16) as *i64
113 kv[0] = 0
114 ar_addf(keys, vals, kv, "type\x00" as *u8, core[0] as *u8)
115 ar_addf(keys, vals, kv, "title\x00" as *u8, core[1] as *u8)
116 ar_addf(keys, vals, kv, "creator\x00" as *u8, core[2] as *u8)
117 ar_addf(keys, vals, kv, "date\x00" as *u8, core[3] as *u8)
118 ar_addf(keys, vals, kv, "identifier\x00" as *u8, core[4] as *u8)
119 ar_addf(keys, vals, kv, "subject\x00" as *u8, core[5] as *u8)
120 ar_addf(keys, vals, kv, "rights\x00" as *u8, core[6] as *u8)
121 ar_addf(keys, vals, kv, "prov_class\x00" as *u8, prov[0] as *u8)
122 ar_addf(keys, vals, kv, "prov_agent\x00" as *u8, prov[1] as *u8)
123 ar_addf(keys, vals, kv, "prov_tool\x00" as *u8, prov[2] as *u8)
124 ar_addf(keys, vals, kv, "prov_model\x00" as *u8, prov[3] as *u8)
125 ar_addf(keys, vals, kv, "prov_source\x00" as *u8, prov[4] as *u8)
126 ar_addf(keys, vals, kv, "prov_date\x00" as *u8, prov[5] as *u8)
127 ar_addf(keys, vals, kv, "width\x00" as *u8, media[0] as *u8)
128 ar_addf(keys, vals, kv, "height\x00" as *u8, media[1] as *u8)
129 ar_addf(keys, vals, kv, "duration\x00" as *u8, media[2] as *u8)
130 ar_addf(keys, vals, kv, "encoding_format\x00" as *u8, media[3] as *u8)
131 ar_addf(keys, vals, kv, "tags\x00" as *u8, org[0] as *u8)
132 ar_addf(keys, vals, kv, "location\x00" as *u8, org[1] as *u8)
133 ar_addf(keys, vals, kv, "lifecycle_stage\x00" as *u8, org[2] as *u8)
134 ar_addf(keys, vals, kv, "is_current\x00" as *u8, org[3] as *u8)
135 ar_addf(keys, vals, kv, "classification\x00" as *u8, org[4] as *u8)
136 ar_addf(keys, vals, kv, "relations\x00" as *u8, org[5] as *u8)
137 return kv[0]
138}
139
140// ---- CID: content-addressed identity of the record bytes ----
141// Plain CID over the canonical bytes (nxc1-<64hex>, len 69); writes NUL into cid.
142func ar_cid(bytes: *u8, n: i64, cid: *u8) -> i64 {
143 return cid_of(bytes, n, cid)
144}
145
146// Self-describing PROFILED CID (nxc1-<2hex codec>-<64hex>, len 72), codec = UXF_DATA so an endpoint
147// can pick the right view with no out-of-band schema (the unified-envelope property).
148func ar_cid_profiled(bytes: *u8, n: i64, cid: *u8) -> i64 {
149 return uxf_cid_profiled(UXF_DATA, bytes, n, cid)
150}
151
152// ---- DECODE: canonical bytes -> (keys[],vals[]) field set (tolerant; preserves unknown fields) ----
153// Returns field count (>=0) or negative on malformed framing (see canon_decode). Field access is
154// then ar_get(keys,vals,nf,"key") -- by NAME over the decoded arrays, no struct aliasing.
155func ar_decode(bytes: *u8, n: i64, out_keys: *i64, out_vals: *i64, maxf: i64) -> i64 {
156 return canon_decode(bytes, n, out_keys, out_vals, maxf)
157}
158
159// linear-find a decoded field's value by key name; returns null pointer if absent.
160func ar_get(keys: *i64, vals: *i64, nf: i64, key: *u8) -> *u8 {
161 var i: i64 = 0
162 while i < nf {
163 if ar_streq((keys[i]) as *u8, key) == 1 { return (vals[i]) as *u8 }
164 i = i + 1
165 }
166 return 0 as *u8
167}
168
169// ---- STORE: put/get a record by its CID into the durable segmented KV store ----
170// ar_put commits ONE record as its own segment keyed by CID (additive: each put = a new immutable
171// segment; the CID IS the key so re-putting identical bytes is idempotent by content). `prefix` is
172// a path PREFIX (e.g. "knowledge/assets/" or "/tmp/ar-") -- no mkdir needed. Returns 0 on success,
173// negative on a store error (propagated from ss_commit).
174func ar_put(prefix: *u8, bytes: *u8, n: i64) -> i64 {
175 let cid: *u8 = sys_mmap(128)
176 ar_cid(bytes, n, cid)
177 let w: *i64 = ss_begin()
178 if ss_add(w, 1, cid, bytes, n) != 0 { return 0 - 20 }
179 // segment id = a monotone-ish stamp; immutable segments + CID-key make collisions harmless
180 // (same CID re-stated is the same content; readers take the latest, ss_hget semantics).
181 let segid: i64 = sys_now_ms()
182 return ss_commit(prefix, w, segid)
183}
184
185// ar_get_by_cid: retrieve the stored record bytes for `cid`. ptrout[0]/lenout[0] receive the
186// bytes pointer + length. Returns 1 found, 0 tombstoned, -1 absent (ss_hget via a snapshot handle).
187func ar_get_by_cid(prefix: *u8, cid: *u8, ptrout: *i64, lenout: *i64) -> i64 {
188 // ss_open_cached (seq905/962 class fix, seq1347 migration, 2026-07-30). SAFE: open -> one ss_hget
189 // -> return, no re-entrancy while the handle is held.
190 // ⚠BORROWED-POINTER CONTRACT (seq1349): ptrout is an INTERIOR POINTER into the store's mapping, not
191 // a copy. Under plain ss_open that pointer stayed valid forever precisely BECAUSE nothing was ever
192 // freed (the leak was accidentally load-bearing). It stays valid under the cache too, because a
193 // stale-manifest re-open RETIRES the old handle without freeing it -- but it would DANGLE after an
194 // ss_cache_reap(). Callers must copy out before reaping; do not reap while holding this pointer.
195 let h: *i64 = ss_open_cached(prefix)
196 if (h as i64) == 0 { return 0 - 1 }
197 return ss_hget(h, cid, ptrout, lenout)
198}
199
200// ---- PROVENANCE HELPERS: stamp the three classes onto a record's prov_* fields ----
201// Each writes the prov fields into caller-provided buffers and sets the class string. These are the
202// 3 provenance classes the operator named (generated image / downloaded video / human-authored), on
203// the W3C PROV entity<-activity<-agent shape. Buffers are caller-mmap'd null-terminated copies.
204
205// copy src -> dst (null-terminated), returns dst
206func ar_cpy(dst: *u8, src: *u8) -> *u8 {
207 var i: i64 = 0
208 while src[i] != (0 as u8) { dst[i] = src[i]; i = i + 1 }
209 dst[i] = 0 as u8
210 return dst
211}
212
213// HUMAN-authored: agent = the person; no tool/model/source. Sets class="human", agent=<person>.
214func ar_prov_human(person: *u8, out_class: *u8, out_agent: *u8) -> i64 {
215 ar_cpy(out_class, "human\x00" as *u8)
216 ar_cpy(out_agent, person)
217 return 0
218}
219
220// MACHINE-generated: agent = the software, carrying the generating tool + model. Sets
221// class="machine", agent="software", tool=<tool>, model=<model>.
222func ar_prov_machine(tool: *u8, model: *u8, out_class: *u8, out_agent: *u8, out_tool: *u8, out_model: *u8) -> i64 {
223 ar_cpy(out_class, "machine\x00" as *u8)
224 ar_cpy(out_agent, "software\x00" as *u8)
225 ar_cpy(out_tool, tool)
226 ar_cpy(out_model, model)
227 return 0
228}
229
230// DOWNLOADED: agent = external, carrying the source URL + fetch date. Sets class="downloaded",
231// agent="external", source=<url>, date=<fetch-date>.
232func ar_prov_downloaded(source_url: *u8, fetch_date: *u8, out_class: *u8, out_agent: *u8, out_source: *u8, out_date: *u8) -> i64 {
233 ar_cpy(out_class, "downloaded\x00" as *u8)
234 ar_cpy(out_agent, "external\x00" as *u8)
235 ar_cpy(out_source, source_url)
236 ar_cpy(out_date, fetch_date)
237 return 0
238}