code wiki / _hdl_build / nx_suite_share.nx
nx_suite_share.nx source
↩ module page · 131 lines · 6785 B
1// nx_suite_share.nx -- the family suite's sharing vocabulary (albums, contact cards, gifts) expressed
2// ENTIRELY as tuples on nx_rebac, the estate's one authorization plane. This REPLACES nx_share_plane,
3// which was a second implementation of a rule that already existed.
4//
5// WHAT THE CONVERGENCE PROOF DECIDED (nx_share_convergence_gate: 7 agree, 2 declared divergences, 0 silent):
6// * the grant / revoke / check half was REDUNDANT -- both planes gave identical verdicts on every
7// scenario that mattered, so it is retired here rather than maintained twice;
8// * DIVERGENCE 1 (kept, deliberately): rebac lets anyone bootstrap the first grant on an OWNERLESS
9// object. For family media that is too loose -- an album nobody has claimed should not be shareable by
10// a passer-by -- so sg_grant REFUSES until someone has claimed it. This is a facade-level tightening
11// of a plane rule, which is allowed; loosening one would not be.
12// * DIVERGENCE 2 (migrated, not dropped): delegated re-sharing now rides the `curator` relation on the
13// new `album` object type in rb_expand -- a rewrite-table ROW, i.e. data on the shared plane, instead
14// of a capability bit in a private one.
15// * the GIFT half was the only genuinely new capability, and it is here: a gift MOVES ownership (the
16// giver stops being the owner) and the chain of holders stays recoverable from the append-only log via
17// rb_list_subjects_history. No second history is kept, because keeping one is how the duplicate began.
18// license_tier: ORIGINAL
19import "nx_rebac.nx"
20
21const SG_VIEW: *u8 = "view\x00"
22const SG_ADD: *u8 = "add\x00"
23const SG_MANAGE: *u8 = "manage\x00"
24const SG_R_VIEWER: *u8 = "viewer\x00"
25const SG_R_CURATOR: *u8 = "curator\x00"
26const SG_R_OWNER: *u8 = "owner\x00"
27const SG_SYSTEM: *u8 = "system\x00"
28
29const SG_E_NOTOWNER: i64 = -1
30const SG_E_UNCLAIMED: i64 = -2
31const SG_E_SELF: i64 = -3
32const SG_E_BADARG: i64 = -6
33const SG_MAXCHAIN: i64 = 64
34
35// "album:7001" / "contact:42" / "gift:900" -- one namespace per resource class, so the same numeric id in
36// two classes can never be the same object (the isolation nx_share_plane's T14 pinned, now free from the
37// object naming itself rather than from a class field nobody outside the plane could see).
38func sg_obj(kind: *u8, id: i64, out: *u8) -> i64 {
39 var o: i64 = 0
40 o = rb_cat(out, o, kind)
41 o = rb_cat(out, o, ":" as *u8)
42 o = rb_catn(out, o, id)
43 out[o] = 0 as u8
44 return o
45}
46func sg_sub(uid: i64, out: *u8) -> i64 {
47 var o: i64 = 0
48 o = rb_cat(out, o, "user:" as *u8)
49 o = rb_catn(out, o, uid)
50 out[o] = 0 as u8
51 return o
52}
53func sg_owner_of(prefix: *u8, kind: *u8, id: i64) -> i64 {
54 let obj: *u8 = sys_mmap(256)
55 sg_obj(kind, id, obj)
56 let out: *i64 = sys_mmap(8*SG_MAXCHAIN) as *i64
57 let n: i64 = rb_list_subjects(prefix, obj, SG_R_OWNER, out, SG_MAXCHAIN)
58 if n <= 0 { return 0 }
59 return out[n-1] as i64 as i64 // pointer to the current owner subject string
60}
61func sg_is_claimed(prefix: *u8, kind: *u8, id: i64) -> i64 {
62 let obj: *u8 = sys_mmap(256)
63 sg_obj(kind, id, obj)
64 return rb_obj_has_owner(prefix, obj)
65}
66// claim: idempotent for the current owner, refused for anyone else (no silent ownership theft)
67func sg_claim(prefix: *u8, owner: i64, kind: *u8, id: i64) -> i64 {
68 if owner <= 0 { return SG_E_BADARG }
69 let obj: *u8 = sys_mmap(256); sg_obj(kind, id, obj)
70 let sub: *u8 = sys_mmap(64); sg_sub(owner, sub)
71 if rb_obj_has_owner(prefix, obj) == 1 {
72 if rb_tuple_current(prefix, obj, SG_R_OWNER, sub) == 1 { return 0 } // already mine
73 return SG_E_NOTOWNER
74 }
75 rb_put(prefix, obj, SG_R_OWNER, sub, SG_SYSTEM, 1)
76 return 1
77}
78// grant. STRICTER THAN THE PLANE BENEATH IT, on purpose: rebac would bootstrap a first grant on an
79// ownerless object; a family album must be claimed before anyone can be added to it.
80func sg_grant(prefix: *u8, actor: i64, subj: i64, kind: *u8, id: i64, rel: *u8) -> i64 {
81 if subj <= 0 { return SG_E_BADARG }
82 let obj: *u8 = sys_mmap(256); sg_obj(kind, id, obj)
83 if rb_obj_has_owner(prefix, obj) == 0 { return SG_E_UNCLAIMED }
84 let asub: *u8 = sys_mmap(64); sg_sub(actor, asub)
85 let ssub: *u8 = sys_mmap(64); sg_sub(subj, ssub)
86 if rb_seq(asub, ssub) == 1 { return SG_E_SELF }
87 if rb_may_grant(prefix, asub, obj) == 0 { return SG_E_NOTOWNER }
88 rb_put(prefix, obj, rel, ssub, asub, 1)
89 return 1
90}
91// revoke: a tombstone, never an erasure -- the grant that existed stays in the record
92func sg_revoke(prefix: *u8, actor: i64, subj: i64, kind: *u8, id: i64, rel: *u8) -> i64 {
93 let obj: *u8 = sys_mmap(256); sg_obj(kind, id, obj)
94 if rb_obj_has_owner(prefix, obj) == 0 { return SG_E_UNCLAIMED }
95 let asub: *u8 = sys_mmap(64); sg_sub(actor, asub)
96 let ssub: *u8 = sys_mmap(64); sg_sub(subj, ssub)
97 if rb_may_grant(prefix, asub, obj) == 0 { return SG_E_NOTOWNER }
98 rb_put(prefix, obj, rel, ssub, asub, 0)
99 return 1
100}
101// the read path: one question, answered by the one plane
102func sg_may(prefix: *u8, actor: i64, kind: *u8, id: i64, perm: *u8) -> i64 {
103 let obj: *u8 = sys_mmap(256); sg_obj(kind, id, obj)
104 let asub: *u8 = sys_mmap(64); sg_sub(actor, asub)
105 return rb_check(prefix, asub, perm, obj, 0)
106}
107// A GIFT MOVES OWNERSHIP. Tombstone the giver's owner tuple, then establish the receiver's -- so the giver
108// genuinely stops being the owner rather than merely sharing. Both writes are appends; the chain survives.
109func sg_gift(prefix: *u8, giver: i64, receiver: i64, kind: *u8, id: i64) -> i64 {
110 if receiver <= 0 { return SG_E_BADARG }
111 if giver == receiver { return SG_E_SELF }
112 let obj: *u8 = sys_mmap(256); sg_obj(kind, id, obj)
113 if rb_obj_has_owner(prefix, obj) == 0 { return SG_E_UNCLAIMED }
114 let gsub: *u8 = sys_mmap(64); sg_sub(giver, gsub)
115 let rsub: *u8 = sys_mmap(64); sg_sub(receiver, rsub)
116 if rb_tuple_current(prefix, obj, SG_R_OWNER, gsub) == 0 { return SG_E_NOTOWNER }
117 rb_put(prefix, obj, SG_R_OWNER, gsub, gsub, 0)
118 rb_put(prefix, obj, SG_R_OWNER, rsub, gsub, 1)
119 return 1
120}
121// whose hands has this passed through? read straight out of the append-only log -- no second ledger.
122func sg_provenance(prefix: *u8, kind: *u8, id: i64, out: *i64, cap: i64) -> i64 {
123 let obj: *u8 = sys_mmap(256); sg_obj(kind, id, obj)
124 return rb_list_subjects_history(prefix, obj, SG_R_OWNER, out, cap)
125}
126// everyone who can currently see this, for the "who can see this" panel: a sharing rule nobody can inspect
127// is one nobody can trust.
128func sg_viewers(prefix: *u8, kind: *u8, id: i64, out: *i64, cap: i64) -> i64 {
129 let obj: *u8 = sys_mmap(256); sg_obj(kind, id, obj)
130 return rb_list_subjects(prefix, obj, SG_R_VIEWER, out, cap)
131}