statement('SET search_path TO '.$schema);$checks=[];$ok=function($v,$name)use(&$checks){if(!$v)throw new RuntimeException($name);$checks[]=$name;}; try{ $probe=$phase==='prefix-context-current'?'prefix_tx_probe_current':'prefix_tx_probe';$db->statement('CREATE TABLE '.$probe.'(id int PRIMARY KEY)');$provider=new StockValuation($db,['tenant_id'=>1,'company_id'=>0,'unit_id'=>0],1,'metadata-only',fn()=>null,fn()=>null);$method=new ReflectionMethod($provider,'priorJournals');$book=(object)['id'=>1,'stock_item_id'=>1,'warehouse_id'=>1];$movement=(object)['id'=>10001];$rate=(object)['id'=>1];$before=(array)$db->table('gl_journals')->where('id',10000)->first(); $mode=null;$capturing=false;DB::listen(function($q)use($db,&$mode,&$capturing){if(!$capturing&&str_starts_with($q->sql,'DECLARE stock_prefix_')){$capturing=true;$mode=(array)$db->selectOne("SELECT current_setting('transaction_isolation') isolation,current_setting('transaction_read_only') read_only");}}); $value=$method->invoke($provider,$book,$movement,$rate);$ok($mode===['isolation'=>'repeatable read','read_only'=>'on']&&$value['count']===10000&&$db->transactionLevel()===0,'standalone prefix capture owns and ends repeatable-read read-only transaction'); $db->beginTransaction();try{ $value2=$method->invoke($provider,$book,$movement,$rate);$db->table($probe)->insert(['id'=>1]);$ok($value2===$value&&$db->transactionLevel()===1&&$db->table($probe)->count()===1,'prefix capture preserves caller write transaction and identical evidence'); $db->update("UPDATE gl_journals SET snapshot=jsonb_set(snapshot,'{source,stock_rate_id}','2'::jsonb) WHERE id=10000");$refused=false;try{$method->invoke($provider,$book,$movement,$rate);}catch(DomainException){$refused=true;}$db->table($probe)->insert(['id'=>2]);$ok($refused&&$db->transactionLevel()===1&&$db->table($probe)->count()===2,'late dependency refusal leaves rollback ownership with caller'); $ok((int)$db->selectOne("SELECT count(*) n FROM pg_cursors WHERE name LIKE 'stock_prefix_%'")->n===0,'cursor closed even after refusal inside caller write transaction'); }finally{$db->rollBack();} $ok($db->table($probe)->count()===0&&(array)$db->table('gl_journals')->where('id',10000)->first()===$before,'caller rollback restores all metadata probes'); file_put_contents($out,json_encode(['passed'=>true,'checks'=>$checks,'metadata_only_fixture'=>true,'new_financial_effects'=>0,'caller_transaction_preserved'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo count($checks)." cursor/transaction checks passed.\n"; }finally{$db->statement('SET search_path TO public');}