code wiki / _hdl_build / nx_file_install_session_gate_20260910.nx
nx_file_install_session_gate_20260910.nx source
↩ module page · 189 lines · 18895 B
1// Private real-filesystem session qualification; caller supplies a fresh scratch directory.
2import "nx_mgmt_api.nx"
3import "nx_gate_verdict.nx"
4func sg_file(path:*u8,text:*u8)->i64 {
5 let fd:i64=sys_openat_exclusive(path,MODE_0600);if fd<0 { return fd }
6 let w:*NxFileWriteResult=sys_mmap(__size_of(NxFileWriteResult)) as *NxFileWriteResult
7 return fio_write_sync_fd(fd,text,fi_len(text),w)
8}
9func sg_path(root:*u8,name:*u8)->*u8 {
10 let a:i64=fi_len(root);let b:i64=fi_len(name);let p:*u8=sys_mmap(a+b+2)
11 fi_copy(p,root,a);p[a]=47 as u8;fi_copy(p+a+1,name,b+1);return p
12}
13func sg_same(path:*u8,digest:*u8)->i64 {
14 let scratch:*u8=sys_mmap(3);return (fio_verify_sha256(path,digest,scratch,3)==0) as i64
15}
16func sg_hash(path:*u8,budget:i64,out:*u8)->i64 {
17 let p:*NxFileInstallPlan=sys_mmap(__size_of(NxFileInstallPlan)) as *NxFileInstallPlan
18 fi_plan_init(p);let rc:i64=fi_plan_read(path,budget,p)
19 if rc==0 { sha256_digest(p.bytes,p.length,out) };fi_plan_close(p);return rc
20}
21
22func tg_identity(path:*u8)->i64 {
23 let link:*u8=sys_mmap(4096);let stat:*i64=sys_mmap(144) as *i64
24 let lr:i64=sys_readlinkat(path,link,4095);if lr>=0 { link[lr]=0 as u8 } else { link[0]=0 as u8 }
25 let sr:i64=sys_fstatat(path,stat as *u8)
26 let w:*NxInstallReceiptWriter=sys_mmap(__size_of(NxInstallReceiptWriter)) as *NxInstallReceiptWriter
27 w.bytes=sys_mmap(8192);w.capacity=8192;w.count=0;w.code=0
28 ma_ir_text(w,"{");ma_ir_named_string(w,"path",path);ma_ir_text(w,",");ma_ir_named_number(w,"readlink_rc",lr);ma_ir_text(w,",");ma_ir_named_string(w,"link",link);ma_ir_text(w,",");ma_ir_named_number(w,"stat_rc",sr);ma_ir_text(w,",\"raw_stat_words\":[")
29 var i:i64=0;while i<18 { if i>0 { ma_ir_text(w,",") };ma_ir_number(w,stat[i]);i=i+1 };ma_ir_text(w,"]}\n")
30 if w.code!=0 { return w.code };sys_write(1,w.bytes,w.count);return 0
31}
32
33func tm_app(out:*u8,at:i64,text:*u8)->i64 { let n:i64=fi_len(text);fi_copy(out+at,text,n);return at+n }
34func tm_hex_value(text:*u8)->*u8 { let out:*u8=sys_mmap(32);var i:i64=0;while i<32 { out[i]=(ma_organ_arg_hex(text[i*2] as i64)*16+ma_organ_arg_hex(text[i*2+1] as i64)) as u8;i=i+1 };return out }
35func tm_hex(out:*u8,at:i64,d:*u8)->i64 { let h:*u8="0123456789abcdef";var i:i64=0;while i<32 { let b:i64=d[i] as i64;out[at+i*2]=h[b>>4];out[at+i*2+1]=h[b&15];i=i+1 };return at+64 }
36func tm_token(resp:*u8,n:i64,out:*u8)->i64 {
37 let at:i64=ma_find(resp,n,"{\"token\":\"");if at<0 { return 0 };var k:i64=at+10;var w:i64=0
38 while k<n && resp[k]!=34 as u8 { if w>=255 { return 0 };out[w]=resp[k];w=w+1;k=k+1 };out[w]=0 as u8;return w
39}
40func tm_mounted(root:*u8)->i64 {
41 if fi_path_valid(root)==0 || sys_mkdir(root,0x1c0)!=0 { return 3 };let ctr:*i64=gv_ctr()
42 let live:*u8=sg_path(root,"runner.elf");let staged:*u8=sg_path(root,"staged.elf");let candidate:*u8=sg_path(root,"candidate.elf");let backup:*u8=sg_path(root,"old.elf")
43 let intent:*u8=sg_path(root,"intent");let reverse:*u8=sg_path(root,"reverse");let rcandidate:*u8=sg_path(root,"rollback.elf");let rbackup:*u8=sg_path(root,"rejected.elf")
44 let registry:*u8=sg_path(root,"registry");let policy:*u8=sg_path(root,"policy");let scratch:*u8=sys_mmap(65536)
45 let oldhash:*u8=tm_hex_value("e7c68f4e9913357ac016d0b7134b17fba33cecb034ee431a36c58d62d382965d")
46 let newhash:*u8=tm_hex_value("b947a76722789bea1815402924b7c78e4a49d5b8f8689c6174a118bfdc392729")
47 let prepared:*NxFilePrepareResult=sys_mmap(__size_of(NxFilePrepareResult)) as *NxFilePrepareResult
48 if fi_prepare_expected("/volume1/homes/elderwesto/nishihost/nx_sov_build_run.elf",live,oldhash,0x1ed,scratch,65536,prepared)!=0 { return 4 }
49 if fi_prepare_expected("/volume1/homes/elderwesto/nishihost/buildroot/_build/nx_sov_build_run.sov.elf",staged,newhash,0x1ed,scratch,65536,prepared)!=0 { return 4 }
50 let w:*NxFileWriteResult=sys_mmap(__size_of(NxFileWriteResult)) as *NxFileWriteResult
51 if fi_plan_create(intent,staged,live,candidate,backup,0x1ed,newhash,oldhash,8192,w)!=0 { return 4 }
52 if fi_plan_create(reverse,backup,live,rcandidate,rbackup,0x1ed,oldhash,newhash,8192,w)!=0 { return 4 }
53 let ih:*u8=sys_mmap(32);let rh:*u8=sys_mmap(32);if sg_hash(intent,8192,ih)!=0 || sg_hash(reverse,8192,rh)!=0 { return 4 }
54 let row:*u8=sys_mmap(8192);var at:i64=tm_app(row,0,"nx_sov_build_run.elf toolchain-v2 ");at=tm_app(row,at,staged);at=tm_app(row,at," - - - ");at=tm_app(row,at,live);at=tm_app(row,at," process\n");row[at]=0 as u8;if sg_file(registry,row)!=0 { return 4 }
55 at=tm_app(row,0,"registry ");at=tm_app(row,at,registry);at=tm_app(row,at,"\nreceipt_root ");at=tm_app(row,at,root);at=tm_app(row,at,"\nlive ");at=tm_app(row,at,live)
56 at=tm_app(row,at,"\nqualification /volume1/homes/elderwesto/nishihost/buildroot/knowledge/gates/build-qualification-canonical-adoption-20260910.json\nqualification_sha256 2f4b6c7c95bd3c1f991a01f722fa4f2ebe2b51101374e64126ae5b58bed3b980\ncandidate_sha256 b947a76722789bea1815402924b7c78e4a49d5b8f8689c6174a118bfdc392729\nsnapshot_bytes 65536\nintent_bytes 65536\ncapture_bytes 16384\ndeadline_ms 120000\n");row[at]=0 as u8;if sg_file(policy,row)!=0 { return 4 };ma_tc_policy_path=policy
57 let keys:*u8=sg_path(root,"keys");let store:*u8=sg_path(root,"auth-store")
58 let oprf:*u8=sys_mmap(32);let akp:*u8=sys_mmap(32);let akb:*u8=sys_mmap(33);let edp:*u8=sys_mmap(32);let edb:*u8=sys_mmap(32)
59 if nx_uas_server_keys_load_or_init(keys,oprf,akp,akb,edp,edb)!=NX_UAS_OK { return 5 }
60 let ctx:*NxAuthContext=sys_mmap(256) as *NxAuthContext
61 if nx_auth_context_init(ctx,"tc-route-fixture",16,"tc-route-fixture",16,store as i64,oprf,edp,edb,900,8192,1,1,5,1)!=NX_MAUTH_OK { return 5 }
62 let mnemonic:*u8=sys_mmap(512);let mnemonic_n:*i64=sys_mmap(16) as *i64
63 if nx_modern_auth_register(ctx,"tc_fixture",10,"public fixture password",23,mnemonic,512,mnemonic_n)!=NX_MAUTH_OK { return 5 }
64 let request:*u8=sys_mmap(16384);let response:*u8=sys_mmap(SD_OUTCAP)
65 let login:*u8="POST /api/login HTTP/1.1\r\nHost: x\r\n\r\nhandle=tc_fixture&passphrase=public+fixture+password"
66 let login_n:i64=ma_handle(ctx,login,fi_len(login),"absent-snapshot",response)
67 let token:*u8=sys_mmap(256);let token_n:i64=tm_token(response,login_n,token);if token_n<=0 { return 5 }
68 let body:*u8=sys_mmap(8192);at=tm_app(body,0,"target=nx_sov_build_run.elf&confirm=yes&intent=");at=tm_app(body,at,intent);at=tm_app(body,at,"&expect_intent_sha256=");at=tm_hex(body,at,ih);at=tm_app(body,at,"&reverse_intent=");at=tm_app(body,at,reverse);at=tm_app(body,at,"&expect_reverse_sha256=");at=tm_hex(body,at,rh);body[at]=0 as u8
69 var n:i64=tm_app(request,0,"POST /api/v2/promote_toolchain HTTP/1.1\r\nHost: x\r\n\r\n");n=tm_app(request,n,body)
70 let unauth:i64=ma_handle(ctx,request,n,"absent-snapshot",response)
71 gv_check("mounted-no-session401-no-live-change",ma_find(response,unauth,"HTTP/1.1 401")==0 && sg_same(live,oldhash)==1,ctr)
72 n=tm_app(request,0,"POST /api/v2/promote_toolchain HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: ");n=tm_app(request,n,token);n=tm_app(request,n,"\r\n\r\n");n=tm_app(request,n,body)
73 ma_auth_index_path=sg_path(root,"uid-index");ma_auth_roles_path=sg_path(root,"roles")
74 let unauthorized:i64=ma_handle(ctx,request,n,"absent-snapshot",response)
75 gv_check("mounted-valid-session-without-role403",ma_find(response,unauthorized,"HTTP/1.1 403")==0 && sg_same(live,oldhash)==1,ctr)
76 let uid:*u8=sys_mmap(64);let uidn:*i64=sys_mmap(8) as *i64
77 if nx_sa_validate_handle(ctx,request,n,sys_now_realtime_sec(),uid,64,uidn)!=NX_MAUTH_OK { return 5 }
78 at=ag_hex(row,0,uid,uidn[0]);at=tm_app(row,at,"\ttc_fixture\n");row[at]=0 as u8
79 if sg_file(ma_auth_index_path,row)!=0 || sg_file(ma_auth_roles_path,"tc_fixture\t3\n")!=0 { return 5 }
80 gv_check("actual-signed-owner-level-resolves3",ma_level_of(ctx,request,n)==MA_LVL_ACT,ctr)
81
82 let different:*u8=sg_path(root,"different.elf");let different_intent:*u8=sg_path(root,"different-intent")
83 if fi_prepare_expected("/volume1/homes/elderwesto/nishihost/nx_sov_build_run.elf",different,oldhash,0x1ed,scratch,65536,prepared)!=0 { return 4 }
84 if fi_plan_create(different_intent,different,live,sg_path(root,"different-candidate"),sg_path(root,"different-backup"),0x1ed,oldhash,oldhash,8192,w)!=0 { return 4 }
85 let different_hash:*u8=sys_mmap(32);if sg_hash(different_intent,8192,different_hash)!=0 { return 4 }
86 let bad_request:*u8=sys_mmap(16384);var bn:i64=tm_app(bad_request,0,"POST /api/v2/promote_toolchain HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: ");bn=tm_app(bad_request,bn,token);bn=tm_app(bad_request,bn,"\r\n\r\ntarget=nx_sov_build_run.elf&confirm=yes&intent=");bn=tm_app(bad_request,bn,different_intent);bn=tm_app(bad_request,bn,"&expect_intent_sha256=");bn=tm_hex(bad_request,bn,different_hash);bn=tm_app(bad_request,bn,"&reverse_intent=");bn=tm_app(bad_request,bn,reverse);bn=tm_app(bad_request,bn,"&expect_reverse_sha256=");bn=tm_hex(bad_request,bn,rh)
87 let refused:i64=ma_handle(ctx,bad_request,bn,"absent-snapshot",response)
88 gv_check("valid-qualification-receipt-refuses-different-candidate",ma_find(response,refused,"HTTP/1.1 409")==0 && ma_find(response,refused,"host-live-intent-binding")>=0 && sg_same(live,oldhash)==1,ctr)
89 let accepted:i64=ma_handle(ctx,request,n,"absent-snapshot",response)
90 sys_write(1,response,accepted);sys_write(1,"\n",1)
91 gv_check("mounted-owner-promotes-exact-scratch-runner",ma_find(response,accepted,"HTTP/1.1 200")==0 && ma_find(response,accepted,"toolchain-accepted-journal-durable")>=0 && sg_same(live,newhash)==1 && sg_same(backup,oldhash)==1,ctr)
92 let replay:i64=ma_handle(ctx,request,n,"absent-snapshot",response)
93 gv_check("mounted-repeat-refuses-existing-journal-without-republish",ma_find(response,replay,"HTTP/1.1 409")==0 && sg_same(live,newhash)==1,ctr)
94 let lock:*NxFileTargetLock=sys_mmap(__size_of(NxFileTargetLock)) as *NxFileTargetLock;fio_target_lock_init(lock)
95 gv_check("mounted-outcome-releases-target-lock",fio_target_lock_acquire(live,lock)==0,ctr);fio_target_lock_release(lock)
96 gv_check("mounted-route-classified-as-long",ma_route_is_long("/api/v2/promote_toolchain",fi_len("/api/v2/promote_toolchain"))==1,ctr)
97
98 let legacy_root:*u8=sg_path(root,"legacy");let legacy_build:*u8=sg_path(legacy_root,"buildroot");let legacy_offc:*u8=sg_path(legacy_build,"_offc")
99 if sys_mkdir(legacy_root,0x1c0)!=0 || sys_mkdir(legacy_build,0x1c0)!=0 || sys_mkdir(legacy_offc,0x1c0)!=0 { return 4 }
100 let legacy_live:*u8=sg_path(legacy_offc,"nx_sov_build_run.elf");let prior_cwd:*u8=sys_mmap(4096)
101 if sys_getcwd(prior_cwd,4096)<0 { return 4 }
102 let legacy_prefix:*u8=sys_mmap(fi_len(legacy_root)+2);fi_copy(legacy_prefix,legacy_root,fi_len(legacy_root));legacy_prefix[fi_len(legacy_root)]=47 as u8;legacy_prefix[fi_len(legacy_root)+1]=0 as u8
103 ma_gr_set_fixture(legacy_prefix,0 as *u8,0 as *u8);if sys_chdir(legacy_root)!=0 { return 4 }
104 let legacy_registry:*u8=sg_path(legacy_root,"deploy_targets.conf")
105 at=tm_app(row,0,"nx_sov_build_run.elf toolchain-v2 ");at=tm_app(row,at,staged);at=tm_app(row,at," - - - ");at=tm_app(row,at,live);at=tm_app(row,at," nx_sov_build_run.elf\n");row[at]=0 as u8;if sg_file(legacy_registry,row)!=0 { return 4 }
106 let typed:*NxDeployTargetRecord=sys_mmap(__size_of(NxDeployTargetRecord)) as *NxDeployTargetRecord
107 let invalid_actions:*u8="t toolchain-v2 /source executable - - /live process\n"
108 let invalid_rc:i64=md_target_decode(invalid_actions,fi_len(invalid_actions),"t",1,typed)
109 gv_check("versioned-registry-refuses-legacy-action-fields",invalid_rc==FIO_EBADMSG && md_streq(typed.stage,"versioned-target-actions")==1,ctr);md_target_close(typed)
110 let deploy_req:*u8=sys_mmap(8192);var dn:i64=tm_app(deploy_req,0,"POST /api/deploy HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: ");dn=tm_app(deploy_req,dn,token);dn=tm_app(deploy_req,dn,"\r\n\r\ntarget=nx_sov_build_run.elf&confirm=yes")
111 let versioned_refusal:i64=ma_handle(ctx,deploy_req,dn,"absent-snapshot",response)
112 gv_check("legacy-deploy-refuses-versioned-only-before-artifact-or-exec",ma_find(response,versioned_refusal,"HTTP/1.1 409")==0 && ma_find(response,versioned_refusal,"VERSIONED_OPERATION_REQUIRED")>=0 && sg_same(live,newhash)==1,ctr)
113 let legacy_req:*u8=sys_mmap(8192);var ln:i64=tm_app(legacy_req,0,"POST /api/promote_toolchain HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: ");ln=tm_app(legacy_req,ln,token);ln=tm_app(legacy_req,ln,"\r\n\r\ntarget=nx_sov_build_run.elf&confirm=yes")
114 fio_target_lock_init(lock);if fio_target_lock_acquire(legacy_live,lock)!=0 { return 4 }
115 let busy:i64=ma_handle(ctx,legacy_req,ln,"absent-snapshot",response)
116 gv_check("legacy-route-shares-target-lock-before-install",ma_find(response,busy,"HTTP/1.1 503")==0 && ma_find(response,busy,"TOOLCHAIN_LOCK_UNAVAILABLE")>=0,ctr);fio_target_lock_release(lock)
117 let no_stage:i64=ma_handle(ctx,legacy_req,ln,"absent-snapshot",response)
118 gv_check("legacy-request-contract-preserved-no-staged400",ma_find(response,no_stage,"HTTP/1.1 400")==0 && ma_find(response,no_stage,"no valid staged")>=0,ctr)
119 gv_check("legacy-refusal-releases-shared-lock",fio_target_lock_acquire(legacy_live,lock)==0,ctr);fio_target_lock_release(lock)
120 ma_gr_set_fixture(0 as *u8,0 as *u8,0 as *u8);if sys_chdir(prior_cwd)!=0 { return 4 }
121 return gv_verdict("TOOLCHAIN-MOUNTED",ctr,"real synthetic signed owner; actual reviewed runner copied into scratch, compiler canary executed; production files unchanged")
122}
123
124func main(argc:i64,argv:*i64)->i64 {
125 if argc==3 && md_streq(argv[1] as *u8,"mounted")==1 { return tm_mounted(argv[2] as *u8) }
126 if argc==2 && md_streq(argv[1] as *u8,"identity")==1 {
127 tg_identity("/volume1/homes/elderwesto/nishihost")
128 tg_identity("/volume1/homes/elderwesto/nishihost/_offc")
129 tg_identity("/volume1/homes/elderwesto/nishihost/buildroot")
130 tg_identity("/volume1/homes/elderwesto/nishihost/buildroot/_offc")
131 tg_identity("/volume1/homes/elderwesto/nishihost/nx_sov_build_run.elf")
132 tg_identity("/volume1/homes/elderwesto/nishihost/_offc/nx_sov_build_run.elf")
133 tg_identity("/volume1/homes/elderwesto/nishihost/buildroot/_offc/nx_sov_build_run.elf")
134 return 0
135 }
136
137 if argc!=2 { return 2 };let root:*u8=argv[1] as *u8
138 if fi_path_valid(root)==0 || sys_mkdir(root,0x1c0)!=0 { return 2 }
139 let c:*i64=gv_ctr()
140 let live:*u8=sg_path(root,"live");let source:*u8=sg_path(root,"source")
141 let candidate:*u8=sg_path(root,"candidate");let backup:*u8=sg_path(root,"backup")
142 let intent:*u8=sg_path(root,"intent");let reverse:*u8=sg_path(root,"reverse")
143 let rcandidate:*u8=sg_path(root,"reverse-candidate");let rbackup:*u8=sg_path(root,"rejected-artifact")
144 let old:*u8="OLD-ARTIFACT";let fresh:*u8="NEW-ARTIFACT"
145 let oldhash:*u8=sys_mmap(FI_DIGEST_BYTES);let newhash:*u8=sys_mmap(FI_DIGEST_BYTES)
146 sha256_digest(old,fi_len(old),oldhash);sha256_digest(fresh,fi_len(fresh),newhash)
147 if sg_file(live,old)!=0 || sg_file(source,fresh)!=0 { return 3 }
148 let budget:i64=FI_HEADER_BYTES+FI_DIGEST_BYTES+fi_len(source)+fi_len(live)+fi_len(candidate)+fi_len(backup)+FI_PATH_FIELDS
149 let rbudget:i64=FI_HEADER_BYTES+FI_DIGEST_BYTES+fi_len(backup)+fi_len(live)+fi_len(rcandidate)+fi_len(rbackup)+FI_PATH_FIELDS
150 let w:*NxFileWriteResult=sys_mmap(__size_of(NxFileWriteResult)) as *NxFileWriteResult
151 if fi_plan_create(intent,source,live,candidate,backup,MODE_0600,newhash,oldhash,budget,w)!=0 { return 3 }
152 if fi_plan_create(reverse,backup,live,rcandidate,rbackup,MODE_0600,oldhash,newhash,rbudget,w)!=0 { return 3 }
153 let ih:*u8=sys_mmap(FI_DIGEST_BYTES);let rh:*u8=sys_mmap(FI_DIGEST_BYTES)
154 if sg_hash(intent,budget,ih)!=0 || sg_hash(reverse,rbudget,rh)!=0 { return 3 }
155 let s:*NxFileInstallSession=sys_mmap(__size_of(NxFileInstallSession)) as *NxFileInstallSession
156 let other:*NxFileInstallSession=sys_mmap(__size_of(NxFileInstallSession)) as *NxFileInstallSession
157 let out:*NxFileInstallResult=sys_mmap(__size_of(NxFileInstallResult)) as *NxFileInstallResult
158 let scratch:*u8=sys_mmap(3);fi_session_init(s);fi_session_init(other)
159 gv_check("closed-session-cannot-publish",fi_session_publish(s,scratch,3,out)==FIO_EINVAL && sg_same(live,oldhash)==1,c)
160 gv_check("approval-required-before-io",fi_session_begin(intent,budget,live,0 as *u8,s)==FIO_EINVAL && s.held==0,c)
161 var bad:i64=fi_session_begin(intent,budget,live,newhash,s)
162 gv_check("wrong-intent-refused",bad==FIO_EBADMSG && s.held==0 && sg_same(live,oldhash)==1,c);fi_session_close(s)
163 bad=fi_session_begin(intent,budget,source,ih,s)
164 gv_check("wrong-registry-target-refused",bad==FI_EACCES && s.held==0,c);fi_session_close(s)
165 let began:i64=fi_session_begin(intent,budget,live,ih,s)
166 gv_check("bound-session-holds-lock-without-publication",began==0 && s.held==1 && sg_same(live,oldhash)==1,c)
167 gv_check("active-session-reopen-preserves-lock",fi_session_begin(intent,budget,live,ih,s)==FIO_EEXIST && s.held==1,c)
168 bad=fi_session_begin(intent,budget,live,ih,other)
169 gv_check("concurrent-session-excluded-before-publication",bad!=0 && other.held==0,c);fi_session_close(other)
170 let reader:*NxFileReadRegion=sys_mmap(__size_of(NxFileReadRegion)) as *NxFileReadRegion
171 fio_region_init(reader);if fio_region_open(live,reader)!=0 { return 3 }
172 let published:i64=fi_session_publish(s,scratch,3,out)
173 gv_check("published-atomically-with-durable-backup",published==0 && out.replacement.publication.visible==1 && out.replacement.publication.durable==1 && sg_same(live,newhash)==1 && sg_same(backup,oldhash)==1,c)
174 let prior:*u8=sys_mmap(fi_len(old)+1);let got:i64=fio_region_next(reader,prior,fi_len(old)+1)
175 gv_check("existing-reader-retains-old-inode",got==fi_len(old) && fi_same(prior,old,fi_len(old))==1,c);fio_region_close(reader)
176 bad=fi_session_begin(intent,budget,live,ih,other)
177 gv_check("lock-remains-held-during-canary-window",bad!=0 && other.held==0 && s.held==1,c);fi_session_close(other)
178 gv_check("publication-replay-reconciles",fi_session_publish(s,scratch,3,out)==0 && out.already_published==1,c)
179 gv_check("rollback-null-boundary-refuses",fi_session_reverse(s,0 as *u8,rbudget,rh,scratch,3,out)==FIO_EINVAL && sg_same(live,newhash)==1,c)
180 gv_check("rollback-wrong-digest-refuses",fi_session_reverse(s,reverse,rbudget,ih,scratch,3,out)==FIO_EBADMSG && sg_same(live,newhash)==1,c)
181 gv_check("rollback-must-invert-digest-pair",fi_session_reverse(s,intent,budget,ih,scratch,3,out)==FIO_EBADMSG && sg_same(live,newhash)==1,c)
182 let restored:i64=fi_session_reverse(s,reverse,rbudget,rh,scratch,3,out)
183 gv_check("rollback-restores-old-and-preserves-rejected-artifact",restored==0 && sg_same(live,oldhash)==1 && sg_same(rbackup,newhash)==1 && sg_same(backup,oldhash)==1,c)
184 gv_check("rollback-replay-reconciles",fi_session_reverse(s,reverse,rbudget,rh,scratch,3,out)==0 && out.already_published==1,c)
185 gv_check("explicit-close-releases-lock",fi_session_close(s)==0 && s.held==0 && s.lock.fd<0,c)
186 gv_check("session-can-reacquire-after-close",fi_session_begin(intent,budget,live,ih,other)==0 && other.held==1,c)
187 gv_check("close-is-idempotent",fi_session_close(other)==0 && fi_session_close(other)==0,c)
188 return gv_verdict("FILE-INSTALL-SESSION",c,"real scratch files; cooperating-writer lock spans publication and reverse intent; toolchain route not adopted")
189}