code wiki / _hdl_build / nx_share_convergence_gate.nx

nx_share_convergence_gate.nx source

↩ module page · 205 lines · 11364 B

1// nx_share_convergence_gate.nx -- THE DEDUP PROOF. I built nx_share_plane as a new who-may-see-what 2// primitive and only afterwards found runtime/nx_rebac.nx, which calls itself "the ONE shared authorization 3// plane for every Nishi surface" and exists precisely because the estate once had three incompatible authz 4// philosophies. Mine would be a fourth. The irony is exact: nx_share_plane's whole justification was that 5// shared gallery, shared contacts and gifts must not become three copies of one permission rule. 6// 7// The estate's law is to RETIRE a duplicate by PROVING CONVERGENCE, not by asserting redundancy. So this 8// gate runs the SAME scenarios through both planes and compares verdicts, and it is deliberately built so 9// that agreement is not the only outcome it can report: 10// * an UNDECLARED divergence turns the gate RED -- two planes that disagree about who may see a photo is 11// a privacy bug wearing a refactor's clothes; 12// * a DECLARED divergence is printed with its reason and does NOT fail, because the point of the exercise 13// is to find them. Two are declared below, and each one changes what "retire the duplicate" means. 14// A gate that could only ever say "they agree" would prove nothing about a decision this consequential. 15// license_tier: ORIGINAL expect_exit: 0 16import "nx_rebac.nx" 17import "nx_share_plane.nx" 18 19const CV_CAP: i64 = 64 20const CV_ALICE: i64 = 1001 21const CV_BOB: i64 = 1002 22const CV_CARA: i64 = 1003 23const CV_ALBUM: i64 = 7001 24 25func c_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 26func c_n(v: i64) -> i64 { 27 let bb: *u8=sys_mmap(28); var m: i64=v 28 if m<0 { m=0-m; sys_write(1,"-" as *u8,1) } 29 let t: *u8=sys_mmap(28); var k: i64=0 30 if m==0 { t[0]=48 as u8; k=1 } 31 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 32 var i: i64=0 33 while i<k { bb[i]=t[k-1-i]; i=i+1 } 34 sys_write(1,bb,k); return 0 35} 36// compare one scenario across both planes. declared=1 means "these are KNOWN to differ and here is why". 37func cv(label: *u8, mine: i64, theirs: i64, declared: i64, why: *u8, agree: *i64, div: *i64, fails: *i64) -> i64 { 38 c_w(" " as *u8); c_w(label) 39 c_w(" share_plane=" as *u8); c_n(mine) 40 c_w(" rebac=" as *u8); c_n(theirs) 41 if mine==theirs { 42 agree[0]=agree[0]+1 43 c_w(" AGREE\n" as *u8) 44 return 0 45 } 46 div[0]=div[0]+1 47 if declared==1 { 48 c_w(" DIVERGE (declared): " as *u8); c_w(why); c_w("\n" as *u8) 49 return 0 50 } 51 fails[0]=fails[0]+1 52 c_w(" DIVERGE (UNDECLARED -- RED): " as *u8); c_w(why); c_w("\n" as *u8) 53 return 0 54} 55func arena() -> *i64 { 56 let a: *i64 = sys_mmap(8*sp_slots_needed(CV_CAP)) as *i64 57 sp_init(a, CV_CAP) 58 return a 59} 60// a fresh isolated seg_store prefix per scenario, so one scenario's tuples cannot leak into the next 61func pfx(tag: i64) -> *u8 { 62 let p: *u8 = sys_mmap(128); var o: i64 = 0 63 o = rb_cat(p, o, "/tmp/shconv_" as *u8); o = rb_catn(p, o, sys_now_us()) 64 o = rb_cat(p, o, "_" as *u8); o = rb_catn(p, o, tag); o = rb_cat(p, o, "_" as *u8) 65 p[o] = 0 as u8 66 return p 67} 68 69func main() -> i64 { 70 let fails: *i64 = sys_mmap(16) as *i64 71 let agree: *i64 = sys_mmap(16) as *i64 72 let div: *i64 = sys_mmap(16) as *i64 73 fails[0]=0; agree[0]=0; div[0]=0 74 c_w("=== nx_share_convergence_gate -- does nx_share_plane duplicate nx_rebac, and exactly where? ===\n" as *u8) 75 c_w("mapping: album -> doc:album1 | actor N -> user:aliceN | SP_VIEW -> perm 'view' | SP_ADMIN -> relation 'owner'\n" as *u8) 76 77 // C1 -- an unclaimed resource shows nothing to anybody 78 let a1: *i64 = arena() 79 let p1: *u8 = pfx(1) 80 cv("C1 unowned, stranger asks view " as *u8, 81 sp_may(a1, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW), 82 rb_check(p1, "user:bob" as *u8, "view" as *u8, "doc:album1" as *u8, 0), 83 0, "deny-by-default must hold in both" as *u8, agree, div, fails) 84 85 // C2 -- the owner sees their own resource with no grant row in existence 86 let a2: *i64 = arena() 87 let p2: *u8 = pfx(2) 88 sp_claim(a2, CV_ALICE, SP_C_ALBUM, CV_ALBUM) 89 rb_put(p2, "doc:album1" as *u8, "owner" as *u8, "user:alice" as *u8, "system" as *u8, 1) 90 cv("C2 owner views own album " as *u8, 91 sp_may(a2, CV_ALICE, SP_C_ALBUM, CV_ALBUM, SP_VIEW), 92 rb_check(p2, "user:alice" as *u8, "view" as *u8, "doc:album1" as *u8, 0), 93 0, "ownership must imply access in both" as *u8, agree, div, fails) 94 95 // C3 -- a stranger still sees nothing after the claim 96 cv("C3 stranger views a claimed album " as *u8, 97 sp_may(a2, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW), 98 rb_check(p2, "user:bob" as *u8, "view" as *u8, "doc:album1" as *u8, 0), 99 0, "claiming must not open the resource to everyone" as *u8, agree, div, fails) 100 101 // C4 -- a grant is what makes a stranger a viewer 102 let a4: *i64 = arena() 103 let p4: *u8 = pfx(4) 104 sp_claim(a4, CV_ALICE, SP_C_ALBUM, CV_ALBUM) 105 sp_grant(a4, CV_ALICE, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW) 106 rb_put(p4, "doc:album1" as *u8, "owner" as *u8, "user:alice" as *u8, "system" as *u8, 1) 107 rb_put(p4, "doc:album1" as *u8, "viewer" as *u8, "user:bob" as *u8, "user:alice" as *u8, 1) 108 cv("C4 granted viewer views " as *u8, 109 sp_may(a4, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW), 110 rb_check(p4, "user:bob" as *u8, "view" as *u8, "doc:album1" as *u8, 0), 111 0, "a grant must take effect in both" as *u8, agree, div, fails) 112 113 // C5 -- revocation is total, and in both planes it APPENDS rather than erases 114 let a5: *i64 = arena() 115 let p5: *u8 = pfx(5) 116 sp_claim(a5, CV_ALICE, SP_C_ALBUM, CV_ALBUM) 117 sp_grant(a5, CV_ALICE, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW) 118 sp_revoke(a5, CV_ALICE, CV_BOB, SP_C_ALBUM, CV_ALBUM) 119 rb_put(p5, "doc:album1" as *u8, "owner" as *u8, "user:alice" as *u8, "system" as *u8, 1) 120 rb_put(p5, "doc:album1" as *u8, "viewer" as *u8, "user:bob" as *u8, "user:alice" as *u8, 1) 121 rb_put(p5, "doc:album1" as *u8, "viewer" as *u8, "user:bob" as *u8, "user:alice" as *u8, 0) 122 cv("C5 revoked viewer views " as *u8, 123 sp_may(a5, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW), 124 rb_check(p5, "user:bob" as *u8, "view" as *u8, "doc:album1" as *u8, 0), 125 0, "a tombstone must win over the grant it revokes, in both" as *u8, agree, div, fails) 126 127 // C6 -- grant, revoke, grant again: the LAST write is the one that counts 128 let a6: *i64 = arena() 129 let p6: *u8 = pfx(6) 130 sp_claim(a6, CV_ALICE, SP_C_ALBUM, CV_ALBUM) 131 sp_grant(a6, CV_ALICE, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW) 132 sp_revoke(a6, CV_ALICE, CV_BOB, SP_C_ALBUM, CV_ALBUM) 133 sp_grant(a6, CV_ALICE, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW) 134 rb_put(p6, "doc:album1" as *u8, "owner" as *u8, "user:alice" as *u8, "system" as *u8, 1) 135 rb_put(p6, "doc:album1" as *u8, "viewer" as *u8, "user:bob" as *u8, "user:alice" as *u8, 1) 136 rb_put(p6, "doc:album1" as *u8, "viewer" as *u8, "user:bob" as *u8, "user:alice" as *u8, 0) 137 rb_put(p6, "doc:album1" as *u8, "viewer" as *u8, "user:bob" as *u8, "user:alice" as *u8, 1) 138 cv("C6 grant-revoke-grant " as *u8, 139 sp_may(a6, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW), 140 rb_check(p6, "user:bob" as *u8, "view" as *u8, "doc:album1" as *u8, 0), 141 0, "latest-wins must be the rule in both" as *u8, agree, div, fails) 142 143 // C7 -- a plain viewer must not be able to re-share (the escalation both planes exist to stop) 144 let a7: *i64 = arena() 145 let p7: *u8 = pfx(7) 146 sp_claim(a7, CV_ALICE, SP_C_ALBUM, CV_ALBUM) 147 sp_grant(a7, CV_ALICE, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW) 148 rb_put(p7, "doc:album1" as *u8, "owner" as *u8, "user:alice" as *u8, "system" as *u8, 1) 149 rb_put(p7, "doc:album1" as *u8, "viewer" as *u8, "user:bob" as *u8, "user:alice" as *u8, 1) 150 var mine7: i64 = 0 151 if sp_grant(a7, CV_BOB, CV_CARA, SP_C_ALBUM, CV_ALBUM, SP_VIEW) > 0 { mine7 = 1 } 152 cv("C7 viewer tries to re-share " as *u8, 153 mine7, 154 rb_may_grant(p7, "user:bob" as *u8, "doc:album1" as *u8), 155 0, "no transitive escalation from VIEW, in either plane" as *u8, agree, div, fails) 156 157 // ---- DECLARED DIVERGENCE 1: an UNCLAIMED object ---- 158 // nx_rebac deliberately lets ANYONE make the first grant on an object with no owner tuple, as a 159 // bootstrap path. nx_share_plane refuses (SP_E_NOOWNER) and requires an explicit claim first. Neither 160 // is wrong in the abstract, but they are not the same rule, and for family photos the stricter one is 161 // the one I want: an album nobody has claimed should not be shareable by a passer-by. 162 let a8: *i64 = arena() 163 let p8: *u8 = pfx(8) 164 var mine8: i64 = 0 165 if sp_grant(a8, CV_BOB, CV_CARA, SP_C_ALBUM, CV_ALBUM, SP_VIEW) > 0 { mine8 = 1 } 166 cv("C8 grant on an UNCLAIMED object " as *u8, 167 mine8, 168 rb_may_grant(p8, "user:bob" as *u8, "doc:album1" as *u8), 169 1, "rebac allows a bootstrap first-grant on an ownerless object; share_plane requires an explicit claim. Keep the strict rule for media: claim at creation." as *u8, 170 agree, div, fails) 171 172 // ---- DECLARED DIVERGENCE 2: DELEGATED re-share ---- 173 // nx_share_plane has an explicit ADMIN bit that lets the owner delegate re-sharing. nx_rebac's doc 174 // rewrite table maps manage -> owner ONLY, so under the doc type nobody but the owner can ever grant. 175 // That is a capability nx_rebac's DATA does not currently express for docs -- which means retiring my 176 // plane is not a pure deletion: this rule has to survive, as a rewrite-table row rather than as code. 177 let a9: *i64 = arena() 178 let p9: *u8 = pfx(9) 179 sp_claim(a9, CV_ALICE, SP_C_ALBUM, CV_ALBUM) 180 sp_grant(a9, CV_ALICE, CV_BOB, SP_C_ALBUM, CV_ALBUM, SP_VIEW+SP_ADMIN) 181 rb_put(p9, "doc:album1" as *u8, "owner" as *u8, "user:alice" as *u8, "system" as *u8, 1) 182 rb_put(p9, "doc:album1" as *u8, "editor" as *u8, "user:bob" as *u8, "user:alice" as *u8, 1) 183 var mine9: i64 = 0 184 if sp_grant(a9, CV_BOB, CV_CARA, SP_C_ALBUM, CV_ALBUM, SP_VIEW) > 0 { mine9 = 1 } 185 cv("C9 delegated re-share (ADMIN bit) " as *u8, 186 mine9, 187 rb_may_grant(p9, "user:bob" as *u8, "doc:album1" as *u8), 188 1, "share_plane can delegate re-sharing via an explicit ADMIN bit; rebac's doc rewrite says manage=owner only. Migrating means adding the row, not dropping the rule." as *u8, 189 agree, div, fails) 190 191 c_w("\n scenarios=9 AGREE=" as *u8); c_n(agree[0]) 192 c_w(" DIVERGE=" as *u8); c_n(div[0]) 193 c_w(" undeclared(RED)=" as *u8); c_n(fails[0]); c_w("\n" as *u8) 194 c_w(" READING: the overlapping half is REDUNDANT and must be retired onto nx_rebac. The two declared\n" as *u8) 195 c_w(" divergences are the migration's actual work: keep claim-before-share, and carry delegated\n" as *u8) 196 c_w(" re-sharing across as a rewrite-table row. The GIFT half (ownership transfer + provenance) is in\n" as *u8) 197 c_w(" neither plane's overlap and is the only part that was genuinely new.\n" as *u8) 198 if fails[0]==0 { 199 c_w("VERDICT: verdict=GREEN (convergence proven where it matters; every divergence named, none silent)\n" as *u8) 200 sys_exit(0) 201 } 202 c_w("VERDICT: RED (an UNDECLARED divergence -- two planes disagreeing about who may see what)\n" as *u8) 203 sys_exit(1) 204 return 1 205}