nx_dms_catalog.nx source
↩ module page · 147 lines · 5364 B
1// nx_dms_catalog.nx -- the consent-gated media catalog (DMS spine, rung 3).
2//
3// A media asset is content-addressed (content_hash) and tagged with the
4// talent + use_scope it depicts/serves. THE property:
5//
6// nx_dms_is_visible(cat, rights, asset, now) -> 1 only if the asset is
7// ACTIVE *and* its (talent, use) is CURRENTLY licensed in the rights
8// ledger.
9//
10// So the catalog surfaces ONLY licensed media BY CONSTRUCTION -- revoke or
11// expire a performer's license and their assets stop surfacing the same
12// instant, with no separate sweep. Removal is soft (additive law #13: the
13// row is kept as history, never hard-deleted). Adds are idempotent on
14// content_hash (rule #10: same bytes catalogued twice = one row).
15//
16// Composes nx_rights_ledger (the gate) + the shared NxUseScope enum.
17//
18// RUNG 1 (this file): semantics, in-memory. Durability via nx_seg_store
19// snapshot (mirroring nx_rights_ledger_store) + per-asset canon_cid CID =
20// later rung (flagged, NOT a silent cap).
21// license_tier: ORIGINAL
22
23import "nx_syscalls.nx"
24import "nx_rights_ledger.nx"
25
26// ===== asset flags (sealed) ====================================
27const NX_DMS_ACTIVE: nx_int = 0
28const NX_DMS_REMOVED: nx_int = 1 // soft-removed; row kept as history
29
30// ===== status codes ============================================
31const NX_DMS_OK: nx_int = 0
32const NX_DMS_ERR_BAD_USE: nx_int = 1
33const NX_DMS_ERR_FULL: nx_int = 2
34const NX_DMS_ERR_NOT_FOUND: nx_int = 3
35const NX_DMS_DUP: nx_int = 4 // content already catalogued (idempotent)
36
37// ===== struct: NxDmsAsset ======================================
38struct NxDmsAsset {
39 asset_id: nx_int,
40 talent_bk: nx_int,
41 use_scope: nx_int,
42 content_hash: nx_size, // content fingerprint (rung 2: full canon_cid CID)
43 added_us: nx_size,
44 flags: nx_int,
45}
46
47struct NxDmsCatalog {
48 assets: *NxDmsAsset,
49 capacity: nx_size,
50 count: nx_size,
51}
52
53const NX_DMS_ASSET_BYTES: nx_size = 48 // 6 fields * 8 bytes
54
55func nx_dms_catalog_new(capacity: nx_size) -> *NxDmsCatalog {
56 let c: *NxDmsCatalog = (sys_mmap(24)) as *NxDmsCatalog
57 let bytes: nx_size = capacity * NX_DMS_ASSET_BYTES
58 c.assets = (sys_mmap(bytes)) as *NxDmsAsset
59 c.capacity = capacity
60 c.count = 0
61 return c
62}
63
64func _dms_at(c: *NxDmsCatalog, idx: nx_size) -> *NxDmsAsset {
65 return (c.assets as i64 + (idx as i64) * NX_DMS_ASSET_BYTES) as *NxDmsAsset
66}
67
68func _dms_find_hash(c: *NxDmsCatalog, content_hash: nx_size) -> nx_int {
69 var i: nx_size = 0
70 while i < c.count {
71 let a: *NxDmsAsset = _dms_at(c, i)
72 if a.content_hash == content_hash { return i as i64 }
73 i = i + 1
74 }
75 return -1
76}
77
78func _dms_find_id(c: *NxDmsCatalog, asset_id: nx_int) -> nx_int {
79 var i: nx_size = 0
80 while i < c.count {
81 let a: *NxDmsAsset = _dms_at(c, i)
82 if a.asset_id == asset_id { return i as i64 }
83 i = i + 1
84 }
85 return -1
86}
87
88// ===== nx_dms_catalog_add ======================================
89// register an asset; content-addressed + idempotent on content_hash.
90func nx_dms_catalog_add(c: *NxDmsCatalog,
91 asset_id: nx_int,
92 talent_bk: nx_int,
93 use_scope: nx_int,
94 content_hash: nx_size,
95 now_us: nx_size) -> nx_int {
96 if nx_use_is_valid(use_scope) == 0 { return NX_DMS_ERR_BAD_USE }
97 if _dms_find_hash(c, content_hash) >= 0 { return NX_DMS_DUP }
98 if c.count >= c.capacity { return NX_DMS_ERR_FULL }
99 let a: *NxDmsAsset = _dms_at(c, c.count)
100 a.asset_id = asset_id
101 a.talent_bk = talent_bk
102 a.use_scope = use_scope
103 a.content_hash = content_hash
104 a.added_us = now_us
105 a.flags = NX_DMS_ACTIVE
106 c.count = c.count + 1
107 return NX_DMS_OK
108}
109
110// ===== nx_dms_catalog_remove ===================================
111// soft-remove (additive; the row stays as history, never hard-deleted).
112func nx_dms_catalog_remove(c: *NxDmsCatalog, asset_id: nx_int, now_us: nx_size) -> nx_int {
113 let idx: nx_int = _dms_find_id(c, asset_id)
114 if idx < 0 { return NX_DMS_ERR_NOT_FOUND }
115 let a: *NxDmsAsset = _dms_at(c, idx as nx_size)
116 a.flags = NX_DMS_REMOVED
117 return NX_DMS_OK
118}
119
120// ===== nx_dms_is_visible =======================================
121// THE consent gate: visible iff the asset exists, is ACTIVE, and its
122// (talent, use) is CURRENTLY licensed. Revoke/expire propagates instantly.
123func nx_dms_is_visible(c: *NxDmsCatalog, l: *NxRightsLedger, asset_id: nx_int, now_us: nx_size) -> nx_int {
124 let idx: nx_int = _dms_find_id(c, asset_id)
125 if idx < 0 { return 0 }
126 let a: *NxDmsAsset = _dms_at(c, idx as nx_size)
127 if a.flags != NX_DMS_ACTIVE { return 0 }
128 return nx_rights_is_licensed(l, a.talent_bk, a.use_scope, now_us)
129}
130
131// count of currently-visible (active + licensed) assets
132func nx_dms_count_visible(c: *NxDmsCatalog, l: *NxRightsLedger, now_us: nx_size) -> nx_int {
133 var n: nx_int = 0
134 var i: nx_size = 0
135 while i < c.count {
136 let a: *NxDmsAsset = _dms_at(c, i)
137 if a.flags == NX_DMS_ACTIVE {
138 if nx_rights_is_licensed(l, a.talent_bk, a.use_scope, now_us) == 1 { n = n + 1 }
139 }
140 i = i + 1
141 }
142 return n
143}
144
145func nx_dms_catalog_count(c: *NxDmsCatalog) -> nx_size {
146 return c.count
147}