qualia_core_db/governance/
provenance.rs1use crate::temporal_graph::{
11 P_DC_CREATOR, P_WAS_ATTRIBUTED_TO, P_WAS_GENERATED_BY, P_WAS_INVALIDATED_BY,
12};
13use crate::{q_hash, NQuin};
14
15pub const PROVENANCE_CONTEXT: u64 = q_hash("urn:qualia:context:provenance");
17pub const CONTEST_CONTEXT: u64 = q_hash("urn:qualia:context:contest");
18
19const P_LABELLED_BY: u64 = q_hash("urn:qualia:prov:labelledBy");
21const P_LABELLED_AT: u64 = q_hash("urn:qualia:prov:labelledAt");
22const P_MODERATED_BY: u64 = q_hash("urn:qualia:prov:moderatedBy");
23const P_MODERATED_AT: u64 = q_hash("urn:qualia:prov:moderatedAt");
24const P_CLEANED_BY: u64 = q_hash("urn:qualia:prov:cleanedBy");
25
26const P_CONTESTED_BY: u64 = q_hash("urn:qualia:prov:contestedBy");
28const P_CONTEST_REASON: u64 = q_hash("urn:qualia:prov:contestReason");
29const P_CONTEST_AT: u64 = q_hash("urn:qualia:prov:contestedAt");
30const P_RESOLVED_BY: u64 = q_hash("urn:qualia:prov:resolvedBy");
31const P_RESOLVED_AT: u64 = q_hash("urn:qualia:prov:resolvedAt");
32
33const P_ACTIVITY_LABEL: u64 = q_hash("urn:qualia:prov:activityLabel");
35const P_ACTIVITY_START: u64 = q_hash("urn:qualia:prov:activityStart");
36const P_ACTIVITY_END: u64 = q_hash("urn:qualia:prov:activityEnd");
37
38pub fn label_with_worker_did(data_hash: u64, worker_did: u64, ts: u64) -> [NQuin; 2] {
46 [
47 make_prov(data_hash, P_LABELLED_BY, worker_did, ts),
48 make_prov(data_hash, P_LABELLED_AT, ts, ts),
49 ]
50}
51
52pub fn record_moderation(data_hash: u64, moderator_did: u64, ts: u64) -> [NQuin; 2] {
54 [
55 make_prov(data_hash, P_MODERATED_BY, moderator_did, ts),
56 make_prov(data_hash, P_MODERATED_AT, ts, ts),
57 ]
58}
59
60pub fn record_cleaning(data_hash: u64, agent_did: u64, ts: u64) -> NQuin {
62 make_prov(data_hash, P_CLEANED_BY, agent_did, ts)
63}
64
65pub fn full_attribution(
72 data_hash: u64,
73 worker_did: u64,
74 activity_hash: u64,
75 ts: u64,
76) -> [NQuin; 3] {
77 [
78 make_prov(data_hash, P_DC_CREATOR, worker_did, ts),
79 make_prov(data_hash, P_WAS_ATTRIBUTED_TO, worker_did, ts),
80 make_prov(data_hash, P_WAS_GENERATED_BY, activity_hash, ts),
81 ]
82}
83
84pub fn contest_assertion(
97 disputed_hash: u64,
98 agent_did: u64,
99 reason_hash: u64,
100 ts: u64,
101) -> [NQuin; 4] {
102 [
103 make_contest(disputed_hash, P_CONTESTED_BY, agent_did, ts),
105 make_contest(disputed_hash, P_CONTEST_REASON, reason_hash, ts),
106 make_contest(disputed_hash, P_CONTEST_AT, ts, ts),
107 make_prov(disputed_hash, P_WAS_INVALIDATED_BY, agent_did, ts),
109 ]
110}
111
112pub fn resolve_contest(disputed_hash: u64, resolver_did: u64, ts: u64) -> [NQuin; 2] {
118 [
119 make_contest(disputed_hash, P_RESOLVED_BY, resolver_did, ts),
120 make_contest(disputed_hash, P_RESOLVED_AT, ts, ts),
121 ]
122}
123
124pub fn write_activity(
133 activity_hash: u64,
134 label_hash: u64,
135 start_ms: u64,
136 end_ms: u64,
137) -> [NQuin; 3] {
138 [
139 make_prov(activity_hash, P_ACTIVITY_LABEL, label_hash, start_ms),
140 make_prov(activity_hash, P_ACTIVITY_START, start_ms, start_ms),
141 make_prov(activity_hash, P_ACTIVITY_END, end_ms, end_ms),
142 ]
143}
144
145pub fn is_contested(quins: &[NQuin], data_hash: u64) -> bool {
152 quins.iter().any(|q| {
153 q.context == CONTEST_CONTEXT && q.subject == data_hash && q.predicate == P_CONTESTED_BY
154 })
155}
156
157pub fn labellers(quins: &[NQuin], data_hash: u64) -> Vec<u64> {
159 quins
160 .iter()
161 .filter(|q| {
162 q.context == PROVENANCE_CONTEXT
163 && q.subject == data_hash
164 && q.predicate == P_LABELLED_BY
165 })
166 .map(|q| q.object)
167 .collect()
168}
169
170#[inline]
173fn make_prov(subject: u64, predicate: u64, object: u64, lamport: u64) -> NQuin {
174 NQuin {
175 subject,
176 predicate,
177 object,
178 context: PROVENANCE_CONTEXT,
179 metadata: lamport & 0xFFFF_FFFF,
180 parity: 0,
181 }
182}
183
184#[inline]
185fn make_contest(subject: u64, predicate: u64, object: u64, lamport: u64) -> NQuin {
186 NQuin {
187 subject,
188 predicate,
189 object,
190 context: CONTEST_CONTEXT,
191 metadata: lamport & 0xFFFF_FFFF,
192 parity: 0,
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 const DATA_HASH: u64 = 0xDA7A_1234_u64;
201 const WORKER_DID: u64 = 0x9999_B0B0_u64;
202 const DISPUTED: u64 = 0xD157_FEED_u64;
203 const AGENT_DID: u64 = 0xA6E4_7777_u64;
204 const REASON: u64 = 0x4EA5_0000_u64;
205 const WORKER1: u64 = 0xB0B1_u64;
206 const WORKER2: u64 = 0xB0B2_u64;
207
208 #[test]
209 fn label_produces_correct_context() {
210 let qs = label_with_worker_did(DATA_HASH, WORKER_DID, 12345);
211 for q in &qs {
212 assert_eq!(q.context, PROVENANCE_CONTEXT);
213 assert_eq!(q.subject, DATA_HASH);
214 }
215 assert_eq!(qs[0].predicate, P_LABELLED_BY);
216 assert_eq!(qs[0].object, WORKER_DID);
217 }
218
219 #[test]
220 fn contest_produces_four_quins() {
221 let qs = contest_assertion(DISPUTED, AGENT_DID, REASON, 99999);
222 assert_eq!(qs.len(), 4);
223 assert_eq!(qs[0].context, CONTEST_CONTEXT);
224 assert_eq!(qs[1].context, CONTEST_CONTEXT);
225 assert_eq!(qs[2].context, CONTEST_CONTEXT);
226 assert_eq!(qs[3].context, PROVENANCE_CONTEXT);
227 }
228
229 #[test]
230 fn is_contested_detects_dispute() {
231 let qs = contest_assertion(DISPUTED, AGENT_DID, REASON, 1);
232 assert!(is_contested(&qs, DISPUTED));
233 assert!(!is_contested(&qs, DATA_HASH));
234 }
235
236 #[test]
237 fn labellers_returns_worker_dids() {
238 let mut all = Vec::new();
239 all.extend_from_slice(&label_with_worker_did(DATA_HASH, WORKER1, 1));
240 all.extend_from_slice(&label_with_worker_did(DATA_HASH, WORKER2, 2));
241 let workers = labellers(&all, DATA_HASH);
242 assert_eq!(workers.len(), 2);
243 assert!(workers.contains(&WORKER1));
244 assert!(workers.contains(&WORKER2));
245 }
246}