code wiki / (root) / nx_asset_record.nx

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}