1use std::path::Path;
4use std::sync::Arc;
5
6use kcode_k1_access::{
7 AccessId, Authorizations, K1Access, ModelId, OwnerSubject, SubsystemId, Target, TxId, UserId,
8 ViewerSubject,
9};
10use kcode_k1_access_kmap::{
11 AccessProfile, ConnectionSpec, ConnectionTier, K1AccessKmap, Measurement,
12 MeasurementImportance, Node, OpenMode, Weight,
13};
14use kcode_k1_access_profiles::{
15 AuthorizationProfile, K1AccessProfiles, ProfileOwner, ProfileSelection, ProfileViewer,
16};
17use kcode_k1_groups::{ALL_MODELS, K1Groups};
18use kcode_k1_kmap::K1Kmap;
19use kcode_k1_peering::K1Peering;
20use kcode_k1_txn_ordering::K1TxnOrdering;
21
22const ORIGINAL_TITLE: &str = "shared root";
23const UPDATED_TITLE: &str = "shared root after authorized update";
24
25pub fn verify() -> Result<(), String> {
27 let temporary =
28 tempfile::tempdir().map_err(|_| "create temporary root: local I/O error".to_string())?;
29 let root = temporary.path().to_path_buf();
30 let stack = Stack::open(&root)?;
31
32 let visible = create_leaf(
33 &stack,
34 user(1),
35 ProfileSelection::Inline(shared_profile_definition()?),
36 "visible child",
37 )?;
38 let hidden = create_leaf(&stack, user(1), ProfileSelection::BuiltIn, "hidden child")?;
39 let third_party = create_leaf(
40 &stack,
41 user(3),
42 ProfileSelection::BuiltIn,
43 "third-party child",
44 )?;
45 let root_id = create(
46 &stack,
47 user(1),
48 ProfileSelection::Inline(shared_profile_definition()?),
49 ORIGINAL_TITLE,
50 vec![edge(&visible), edge(&hidden)],
51 "create shared root",
52 )?;
53
54 verify_projection(&stack, &root_id, &visible, &hidden, ORIGINAL_TITLE)?;
55 verify_open(&stack, &root_id, &visible, &hidden)?;
56 verify_update_authority(&stack, &root_id, &visible, &hidden, &third_party)?;
57
58 let before_measurement = visible_weight(&stack, &root_id)?;
59 expect_unavailable(
60 stack
61 .facade
62 .apply_measurements(user(2), model(2), vec![measurement(&root_id, &visible)]),
63 "viewer measurement",
64 )?;
65 equal_weight(
66 visible_weight(&stack, &root_id)?,
67 before_measurement,
68 "denied measurement changed weight",
69 )?;
70 operation(
71 stack
72 .facade
73 .apply_measurements(user(1), model(1), vec![measurement(&root_id, &visible)]),
74 "apply owner measurement",
75 )?;
76 let measured_weight = visible_weight(&stack, &root_id)?;
77 require(
78 measured_weight != before_measurement,
79 "owner measurement did not change weight",
80 )?;
81
82 expect_unavailable(
83 stack.facade.create_node(
84 user(1),
85 model(1),
86 ProfileSelection::BuiltIn,
87 "rejected creation".to_string(),
88 "rejected navigation".to_string(),
89 "rejected narrative".to_string(),
90 vec![edge(&third_party)],
91 ),
92 "creation with third-party target",
93 )?;
94 verify_bad_wrappers(&stack)?;
95
96 drop(stack);
97 let reopened = Stack::open(&root)?;
98 let after_restart = get(&reopened, user(2), model(2), &root_id, "restart root")?;
99 require(
100 after_restart.title == UPDATED_TITLE,
101 "authorized title did not survive restart",
102 )?;
103 equal_weight(
104 visible_edge(
105 &after_restart,
106 &visible,
107 "restart projection did not retain its single visible edge",
108 )?,
109 measured_weight,
110 "measured weight did not survive restart",
111 )?;
112 expect_unavailable(
113 reopened.facade.get_node(user(2), model(2), hidden),
114 "restart hidden direct read",
115 )
116}
117
118struct Stack {
119 _ordering: Arc<K1TxnOrdering>,
120 _peering: Arc<K1Peering>,
121 _groups: Arc<K1Groups>,
122 _profiles: Arc<K1AccessProfiles>,
123 access: Arc<K1Access>,
124 _kmap: Arc<K1Kmap>,
125 facade: K1AccessKmap,
126}
127
128impl Stack {
129 fn open(root: &Path) -> Result<Self, String> {
130 let path = |name| root.join(name);
131 let ordering = Arc::new(operation(
132 K1TxnOrdering::open(&path("ordering")),
133 "open transaction ordering",
134 )?);
135 let peering = Arc::new(operation(
136 K1Peering::open(&path("peering"), ordering.clone()),
137 "open peering",
138 )?);
139 let groups = Arc::new(operation(
140 K1Groups::open(&path("groups"), ordering.clone(), peering.clone()),
141 "open groups",
142 )?);
143 let profiles = Arc::new(operation(
144 K1AccessProfiles::open(&path("profiles"), ordering.clone(), peering.clone()),
145 "open access profiles",
146 )?);
147 let access = Arc::new(operation(
148 K1Access::open(
149 &path("access"),
150 ordering.clone(),
151 peering.clone(),
152 groups.clone(),
153 ),
154 "open access",
155 )?);
156 let kmap = Arc::new(operation(
157 K1Kmap::open(&path("kmap"), ordering.clone(), peering.clone()),
158 "open kmap",
159 )?);
160 let facade = operation(
161 K1AccessKmap::open(access.clone(), profiles.clone(), kmap.clone()),
162 "open access-kmap facade",
163 )?;
164 Ok(Self {
165 _ordering: ordering,
166 _peering: peering,
167 _groups: groups,
168 _profiles: profiles,
169 access,
170 _kmap: kmap,
171 facade,
172 })
173 }
174}
175
176fn user(byte: u8) -> UserId {
177 UserId::from_tx_id(TxId::from_bytes([byte; 12]))
178}
179
180fn model(byte: u8) -> ModelId {
181 ModelId::from_bytes([byte; 32])
182}
183
184fn shared_profile_definition() -> Result<AuthorizationProfile, String> {
185 operation(
186 AuthorizationProfile::new(
187 vec![ProfileOwner::RequestUser],
188 vec![
189 ProfileViewer::User(user(2)),
190 ProfileViewer::Group(ALL_MODELS),
191 ],
192 ),
193 "build shared authorization profile",
194 )
195}
196
197fn edge(target: &AccessId) -> ConnectionSpec {
198 ConnectionSpec {
199 target: *target,
200 tier: ConnectionTier::Navigation,
201 }
202}
203
204fn measurement(source: &AccessId, target: &AccessId) -> Measurement {
205 Measurement {
206 source: *source,
207 target: *target,
208 useful: false,
209 importance: MeasurementImportance::NonCritical,
210 }
211}
212
213fn create_leaf(
214 stack: &Stack,
215 owner: UserId,
216 profile: AccessProfile,
217 title: &str,
218) -> Result<AccessId, String> {
219 create(
220 stack,
221 owner,
222 profile,
223 title,
224 Vec::new(),
225 "create fixture leaf",
226 )
227}
228
229fn create(
230 stack: &Stack,
231 owner: UserId,
232 profile: AccessProfile,
233 title: &str,
234 connections: Vec<ConnectionSpec>,
235 label: &str,
236) -> Result<AccessId, String> {
237 let revision = operation(
238 stack.facade.create_node(
239 owner,
240 model(1),
241 profile,
242 title.to_string(),
243 format!("navigation for {title}"),
244 format!("narrative for {title}"),
245 connections,
246 ),
247 label,
248 )?;
249 Ok(revision.access_id())
250}
251
252fn get(
253 stack: &Stack,
254 request_user: UserId,
255 request_model: ModelId,
256 id: &AccessId,
257 label: &str,
258) -> Result<Node, String> {
259 operation(
260 stack.facade.get_node(request_user, request_model, *id),
261 label,
262 )
263}
264
265fn visible_edge(node: &Node, visible: &AccessId, message: &str) -> Result<Weight, String> {
266 require(
267 node.connections.len() == 1 && node.connections[0].target == *visible,
268 message,
269 )?;
270 Ok(node.connections[0].weight)
271}
272
273fn verify_projection(
274 stack: &Stack,
275 root: &AccessId,
276 visible: &AccessId,
277 hidden: &AccessId,
278 expected_title: &str,
279) -> Result<(), String> {
280 let projected = get(stack, user(2), model(2), root, "get projected root")?;
281 require(
282 projected.access_id == *root,
283 "projection returned wrong root",
284 )?;
285 require(
286 projected.title == expected_title,
287 "projection changed title",
288 )?;
289 visible_edge(
290 &projected,
291 visible,
292 "projection did not preserve exactly the visible edge",
293 )?;
294 expect_unavailable(
295 stack.facade.get_node(user(2), model(2), *hidden),
296 "hidden direct read",
297 )
298}
299
300fn verify_open(
301 stack: &Stack,
302 root: &AccessId,
303 visible: &AccessId,
304 hidden: &AccessId,
305) -> Result<(), String> {
306 let opened = operation(
307 stack
308 .facade
309 .open_node(user(2), model(2), *root, 10.0, 0.0, OpenMode::Full),
310 "open projected root",
311 )?;
312 let contains = |id| opened.nodes.iter().any(|node| &node.access_id == id);
313 require(
314 contains(root)
315 && contains(visible)
316 && !contains(hidden)
317 && opened
318 .nodes
319 .iter()
320 .all(|node| &node.access_id == root || &node.access_id == visible),
321 "full traversal projection was incorrect",
322 )
323}
324
325fn verify_update_authority(
326 stack: &Stack,
327 root: &AccessId,
328 visible: &AccessId,
329 hidden: &AccessId,
330 third_party: &AccessId,
331) -> Result<(), String> {
332 expect_unavailable(
333 stack.facade.update_node(
334 user(2),
335 model(2),
336 *root,
337 Some("viewer edit".to_string()),
338 None,
339 None,
340 Vec::new(),
341 ),
342 "viewer update",
343 )?;
344 let unchanged = get(stack, user(1), model(1), root, "read after viewer update")?;
345 require(unchanged.title == ORIGINAL_TITLE, "viewer changed title")?;
346 let connections_before = unchanged.connections.clone();
347
348 expect_unavailable(
349 stack.facade.update_node(
350 user(1),
351 model(1),
352 *root,
353 Some("must not commit".to_string()),
354 None,
355 None,
356 vec![edge(visible), edge(hidden), edge(third_party)],
357 ),
358 "update with third-party target",
359 )?;
360 let denied = get(stack, user(1), model(1), root, "read after target denial")?;
361 require(
362 denied.title == ORIGINAL_TITLE && denied.connections == connections_before,
363 "target denial committed changes",
364 )?;
365
366 operation(
367 stack.facade.update_node(
368 user(1),
369 model(1),
370 *root,
371 Some(UPDATED_TITLE.to_string()),
372 None,
373 None,
374 vec![edge(visible), edge(hidden)],
375 ),
376 "authorized title update",
377 )?;
378 let projected = get(stack, user(2), model(2), root, "read authorized title")?;
379 require(
380 projected.title == UPDATED_TITLE,
381 "authorized title is not visible to viewer",
382 )?;
383 visible_edge(
384 &projected,
385 visible,
386 "authorized title update changed projection",
387 )?;
388 Ok(())
389}
390
391fn visible_weight(stack: &Stack, root: &AccessId) -> Result<Weight, String> {
392 let projected = get(stack, user(2), model(2), root, "read visible weight")?;
393 visible_edge(
394 &projected,
395 &projected.connections[0].target,
396 "visible edge missing while reading weight",
397 )
398}
399
400fn verify_bad_wrappers(stack: &Stack) -> Result<(), String> {
401 for (subsystem, bytes, subsystem_label, create_label, read_label) in [
402 (
403 "k1-kmap",
404 vec![91; 11],
405 "build kmap subsystem",
406 "create malformed wrapper",
407 "malformed wrapper read",
408 ),
409 (
410 "not-kmap",
411 vec![92; 12],
412 "build non-kmap subsystem",
413 "create wrong-subsystem wrapper",
414 "wrong-subsystem wrapper read",
415 ),
416 ] {
417 let target = Target::new(
418 operation(SubsystemId::from_str(subsystem), subsystem_label)?,
419 bytes,
420 );
421 let authorizations = operation(
422 Authorizations::new(
423 vec![OwnerSubject::User(user(1))],
424 vec![ViewerSubject::Group(ALL_MODELS)],
425 ),
426 "build wrapper authorizations",
427 )?;
428 let wrapper = operation(stack.access.create(target, authorizations), create_label)?;
429 expect_unavailable(
430 stack
431 .facade
432 .get_node(user(1), model(1), wrapper.access_id()),
433 read_label,
434 )?;
435 }
436 Ok(())
437}
438
439fn operation<T>(result: Result<T, String>, label: &str) -> Result<T, String> {
440 result.map_err(|error| format!("{label}: {error}"))
441}
442
443fn expect_unavailable<T>(result: Result<T, String>, label: &str) -> Result<(), String> {
444 match result {
445 Err(error) if error == "node unavailable" => Ok(()),
446 _ => Err(format!("{label}: expected exact generic unavailability")),
447 }
448}
449
450fn require(condition: bool, message: &str) -> Result<(), String> {
451 if condition {
452 Ok(())
453 } else {
454 Err(message.to_string())
455 }
456}
457
458fn equal_weight(actual: Weight, expected: Weight, message: &str) -> Result<(), String> {
459 require(actual == expected, message)
460}
461
462#[cfg(test)]
463mod tests {
464 #[test]
465 fn real_stack_conformance() {
466 super::verify().expect("K1 Access–Kmap conformance failed");
467 }
468}