code wiki / (root) / nx_id_epoch_prepare_gate_t293.nx

nx_id_epoch_prepare_gate_t293.nx source

↩ module page · 164 lines · 11475 B

1// nx_id_epoch_prepare_gate_t293.nx -- Tests id epoch preparation and resolution under various crash and failure scenarios. 2import "nx_apistack_idempotency_epoch_candidate_t293.nx" 3import "nx_gate_verdict.nx" 4import "nx_fsops_lib.nx" 5func eg_write(p:*u8,b:*u8,n:i64)->i64{ 6 let fd:i64=sys_openat_wr(p,MODE_0600);if fd<0{return fd} 7 let r:*NxFileWriteResult=sys_mmap_try(__size_of(NxFileWriteResult)) as *NxFileWriteResult 8 let rc:i64=fio_write_sync_fd(fd,b,n,r);sys_munmap_direct(r as *u8,__size_of(NxFileWriteResult));return rc 9} 10func eg_run(root:*u8,s:*NxIdEpochSpec)->i64{ 11 let r:*NxIdEpochPrepared=sys_mmap_try(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared 12 let state:i64=id_epoch_prepare(root,s,r);ie_close(r);sys_munmap_direct(r as *u8,__size_of(NxIdEpochPrepared)) 13 if state==ID_EP_PREPARED_SYNCED{return 0} 14 if state==ID_EP_BUSY{return 2};if state==ID_EP_INCOMPLETE{return 3};return 4 15} 16 17// These child crash fixtures reproduce persistence states, not injected production fsync errors. 18func eg_death_case(root:*u8,s:*NxIdEpochSpec,phase:i64,c:*i64)->i64{ 19 let r:*NxIdEpochPrepared=sys_mmap(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared 20 if ie_build(root,s,r)!=0{return 1} 21 let child:i64=sys_fork() 22 if child==0{ 23 if sys_mkdir(r.directory,ID_EP_PRIVATE_MODE)!=0{sys_exit(2)} 24 if phase>0{if ie_create_file(r.ledger,"",0)!=0{sys_exit(3)}} 25 if phase>1{ 26 let fd:i64=sys_openat_exclusive(r.manifest,MODE_0600);if fd<0{sys_exit(4)} 27 var extent:i64=r.definition_bytes 28 if phase==2{extent=extent-1} 29 if sys_write(fd,r.definition,extent)!=extent{sys_exit(5)} 30 // Intentionally die with open unsynced definition: no helper completion was acknowledged. 31 } 32 nx_kill(osp_selfpid(),9);sys_exit(6) 33 } 34 let status:*i64=sys_mmap(8) as *i64 35 let waited:i64=sys_wait4(child,status,0) 36 gv_kv("crash_phase",phase);gv_kv("crash_signal",wait_term_signal(status[0])) 37 gv_check("controlled child died after selected persistence phase",child>0&&waited==child&&wait_term_signal(status[0])==9,c) 38 ie_close(r) 39 let resolved:i64=id_epoch_resolve(root,s,r) 40 if phase<3{gv_check("partial crash state cannot resolve prepared",resolved==ID_EP_INCOMPLETE,c)} 41 else{gv_check("complete unsynced bytes are observed only not active",resolved==ID_EP_PREPARED_OBSERVED,c)} 42 ie_close(r) 43 let replay:i64=id_epoch_prepare(root,s,r) 44 if phase<3{gv_check("partial crash replay stays incomplete no overwrite",replay==ID_EP_INCOMPLETE,c)} 45 else{gv_check("explicit replay revalidates and resyncs complete bytes",replay==ID_EP_PREPARED_SYNCED&&r.created==0,c)} 46 ie_close(r);sys_munmap_direct(r as *u8,__size_of(NxIdEpochPrepared));sys_munmap_direct(status as *u8,8);return 0 47} 48func eg_failures(root:*u8,s:*NxIdEpochSpec,c:*i64)->i64{ 49 s.epoch="crash-mkdir";eg_death_case(root,s,0,c) 50 s.epoch="crash-ledger";eg_death_case(root,s,1,c) 51 s.epoch="crash-partial-definition";eg_death_case(root,s,2,c) 52 s.epoch="crash-complete-unsynced";eg_death_case(root,s,3,c) 53 let r:*NxIdEpochPrepared=sys_mmap(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared 54 let status:*i64=sys_mmap(8) as *i64 55 let limit:*i64=sys_mmap(16) as *i64;limit[0]=0;limit[1]=0 56 s.epoch="allocation-refusal" 57 let child:i64=sys_fork() 58 if child==0{ 59 if nx_prlimit(0,RLIMIT_AS,limit as *u8,0 as *u8)!=0{sys_exit(2)} 60 let rc:i64=id_epoch_prepare(root,s,r) 61 var good:i64=0;if rc==ID_RESOURCE&&r.system_code==-12{good=1} 62 ie_close(r);if good==1{sys_exit(0)};sys_exit(3) 63 } 64 let waited:i64=sys_wait4(child,status,0) 65 gv_check("actual child address-space exhaustion returns resource refusal",child>0&&waited==child&&wait_status_rc(status[0])==0,c) 66 gv_check("allocation failure did not create epoch",id_epoch_resolve(root,s,r)==ID_EP_UNKNOWN,c);ie_close(r) 67 // Adversarial namespace replacement demonstrates the unresolved full-path lock gap. 68 s.epoch="ancestor-replacement" 69 let prepared:i64=id_epoch_prepare(root,s,r) 70 let held:i64=ie_open_directory(r.directory);let locked:i64=sys_flock(held,SYS_LOCK_EX|SYS_LOCK_NB) 71 var movedlen:i64=0;let moved:*u8=ie_path(root,"replaced-directory-retained",&movedlen) 72 let renamed:i64=sys_renameat(r.directory,moved);let made:i64=sys_mkdir(r.directory,ID_EP_PRIVATE_MODE) 73 let ledger:i64=ie_create_file(r.ledger,"",0);let manifest:i64=ie_create_file(r.manifest,r.definition,r.definition_bytes) 74 let observed:i64=ie_verify_files(r) 75 let other:i64=ie_open_directory(r.directory);let secondlock:i64=sys_flock(other,SYS_LOCK_EX|SYS_LOCK_NB) 76 gv_check("existing full-path read can cross held-directory identity: defect reproduced",prepared==ID_EP_PREPARED_SYNCED&&locked==0&&renamed==0&&made==0&&ledger==0&&manifest==0&&observed==ID_EP_PREPARED_OBSERVED&&secondlock==0,c) 77 sys_flock(other,SYS_LOCK_UN);sys_close(other);sys_flock(held,SYS_LOCK_UN);sys_close(held) 78 ie_close(r);sys_munmap_direct(moved,movedlen);sys_munmap_direct(limit as *u8,16);sys_munmap_direct(status as *u8,8);sys_munmap_direct(r as *u8,__size_of(NxIdEpochPrepared)) 79 return 0 80} 81 82func main()->i64{ 83 let c:*i64=gv_ctr();gv_head("EXPLICIT EPOCH PREPARATION ONLY") 84 var ln:i64=0;let legacy:*u8=sys_read_file("../knowledge/status/fswrite_idem.jrnl",&ln) 85 if (legacy as i64)<=0||ln<=0{return 2} 86 let root:*u8=sys_mmap(160);let prefix:*u8="knowledge/gates/epoch-prepare-t293-" 87 let pn:i64=ie_len(prefix);ie_copy(root,0,prefix,pn);let end:i64=nxi_buf(root,pn,osp_selfpid());root[end]=0 as u8 88 gv_check("fresh isolated epoch registry root",sys_mkdir(root,ID_EP_PRIVATE_MODE)==0,c) 89 let snaplen:*i64=sys_mmap(8) as *i64;let snapshot:*u8=ie_path(root,"legacy-snapshot",snaplen) 90 gv_check("actual legacy snapshot retained without modifying original",eg_write(snapshot,legacy,ln)==0,c) 91 let sha:*u8=sys_mmap(ID_EP_HASH_HEX+1);gv_check("native predecessor digest computed",ie_hash_hex(legacy,ln,sha)==0,c) 92 gv_kv("predecessor_snapshot_bytes",ln);gv_puts("predecessor_snapshot_sha256=");gv_puts(sha);gv_puts("\nfixture_root=");gv_puts(root);gv_puts("\n") 93 let s:*NxIdEpochSpec=sys_mmap(__size_of(NxIdEpochSpec)) as *NxIdEpochSpec 94 s.domain="fswrite";s.epoch="new-work-test";s.creation="create-request-1";s.creation_sha=sha 95 s.predecessor="legacy";s.snapshot_path=snapshot;s.snapshot_sha=sha;s.snapshot_bytes=ln 96 let r:*NxIdEpochPrepared=sys_mmap(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared 97 gv_check("null output boundary refuses",id_epoch_prepare(root,s,0 as *NxIdEpochPrepared)==ID_EP_INVALID,c) 98 gv_check("null specification boundary refuses",id_epoch_resolve(root,0 as *NxIdEpochSpec,r)==ID_EP_INVALID,c);ie_close(r) 99 gv_check("resolve absent epoch does not create it",id_epoch_resolve(root,s,r)==ID_EP_UNKNOWN,c);ie_close(r) 100 gv_check("explicit preparation completes synced files",id_epoch_prepare(root,s,r)==ID_EP_PREPARED_SYNCED&&r.created==1,c);ie_close(r) 101 gv_check("same creation identity replays existing prepared definition",id_epoch_prepare(root,s,r)==ID_EP_PREPARED_SYNCED&&r.created==0,c);ie_close(r) 102 gv_check("resolve observes prepared without claiming active execution",id_epoch_resolve(root,s,r)==ID_EP_PREPARED_OBSERVED,c);ie_close(r) 103 s.creation="changed-request" 104 gv_check("changed creation identity cannot reuse epoch",id_epoch_prepare(root,s,r)<0,c);ie_close(r) 105 s.creation="create-request-1";s.predecessor="changed-predecessor" 106 gv_check("changed predecessor cannot resolve same epoch",id_epoch_resolve(root,s,r)<0,c);ie_close(r) 107 s.predecessor="legacy";s.domain="other-domain" 108 gv_check("different domain cannot alias epoch",id_epoch_resolve(root,s,r)==ID_EP_UNKNOWN,c);ie_close(r) 109 s.domain="fswrite";s.snapshot_bytes=ln-1 110 gv_check("wrong predecessor extent refuses creation",id_epoch_prepare(root,s,r)==ID_EP_CONFLICT,c);ie_close(r);s.snapshot_bytes=ln 111 s.creation_sha="xyz" 112 gv_check("malformed creation digest refused",id_epoch_resolve(root,s,r)==ID_EP_INVALID,c);ie_close(r);s.creation_sha=sha 113 s.epoch="orphan" 114 ie_build(root,s,r);sys_mkdir(r.directory,ID_EP_PRIVATE_MODE);ie_close(r) 115 gv_check("orphan namespace is not silently initialized",id_epoch_prepare(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r) 116 s.epoch="new-work-test" 117 id_epoch_resolve(root,s,r) 118 let held:i64=sys_openat_directory(r.directory);sys_flock(held,SYS_LOCK_EX|SYS_LOCK_NB) 119 let child:i64=sys_fork() 120 if child==0{ 121 // Drop inherited lock reference before independently trying to resolve. 122 sys_close(held) 123 let z:*NxIdEpochPrepared=sys_mmap(__size_of(NxIdEpochPrepared)) as *NxIdEpochPrepared 124 let got:i64=id_epoch_resolve(root,s,z);ie_close(z);if got==ID_EP_BUSY{sys_exit(0)};sys_exit(1) 125 } 126 let status:*i64=sys_mmap(8) as *i64;let waited:i64=sys_wait4(child,status,0) 127 gv_check("other process resolve refuses held preparation lock",child>0&&waited==child&&wait_status_rc(status[0])==0,c) 128 sys_flock(held,SYS_LOCK_UN);sys_close(held);ie_close(r) 129 s.epoch="race" 130 let first:i64=sys_fork();if first==0{sys_exit(eg_run(root,s))} 131 let second:i64=sys_fork();if second==0{sys_exit(eg_run(root,s))} 132 var a:i64=9;var b:i64=9 133 if first>0{if sys_wait4(first,status,0)==first{a=wait_status_rc(status[0])}} 134 if second>0{if sys_wait4(second,status,0)==second{b=wait_status_rc(status[0])}} 135 gv_kv("first_creator_exit",a);gv_kv("second_creator_exit",b) 136 gv_check("concurrent create has a completed owner and no invalid success",((a==0||b==0)&&(a==0||a==2||a==3)&&(b==0||b==2||b==3)),c) 137 gv_check("concurrent result has exact expected definition",id_epoch_resolve(root,s,r)==ID_EP_PREPARED_OBSERVED,c);ie_close(r) 138 s.epoch="different-creation-hash";s.creation_sha="0000000000000000000000000000000000000000000000000000000000000000" 139 gv_check("valid independent creation hash can prepare",id_epoch_prepare(root,s,r)==ID_EP_PREPARED_SYNCED,c);ie_close(r) 140 s.creation_sha=sha 141 gv_check("creation fingerprint change cannot resolve",id_epoch_resolve(root,s,r)<0,c);ie_close(r) 142 s.epoch="wrong-snapshot";s.snapshot_sha="0000000000000000000000000000000000000000000000000000000000000000" 143 gv_check("incorrect predecessor digest refuses before creation",id_epoch_prepare(root,s,r)==ID_EP_CONFLICT,c);ie_close(r);s.snapshot_sha=sha 144 s.epoch="missing-ledger" 145 id_epoch_prepare(root,s,r) 146 let movedlen:*i64=sys_mmap(8) as *i64;let moved:*u8=ie_path(r.directory,"ledger.retained",movedlen) 147 sys_renameat(r.ledger,moved);ie_close(r) 148 gv_check("missing prepared ledger does not initialize on resolve",id_epoch_resolve(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r) 149 gv_check("missing prepared ledger does not initialize on prepare replay",id_epoch_prepare(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r) 150 s.epoch="symlink" 151 ie_build(root,s,r);sys_symlinkat(".",r.directory);ie_close(r) 152 let symlink_state:i64=id_epoch_resolve(root,s,r) 153 gv_check("namespace symlink is refused without following it",symlink_state==ID_EP_INCOMPLETE&&(r.system_code==-20||r.system_code==-40),c);ie_close(r) 154 s.epoch="new-work-test";id_epoch_resolve(root,s,r) 155 eg_write(r.manifest,"bad",3);ie_close(r) 156 gv_check("malformed definition refuses resolve",id_epoch_resolve(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r) 157 gv_check("malformed definition is not overwritten by prepare",id_epoch_prepare(root,s,r)==ID_EP_INCOMPLETE,c);ie_close(r) 158 let ev:*NxIdEvidence=sys_mmap(__size_of(NxIdEvidence)) as *NxIdEvidence 159 let legacy_state:i64=id_read_evidence(snapshot,"unseen-proof-key",16,ev) 160 gv_check("legacy malformed ambiguity remains refusal",legacy_state==ID_EVIDENCE_ERROR&&ev.system_code==FIO_EBADMSG,c) 161 id_evidence_close(ev) 162 eg_failures(root,s,c) 163 return gv_verdict("epoch-prepare",c,"Isolated PREPARED only; no activation, admission, writer routing, live mutation or exactly-once claim") 164}