From 702991620c11fcf9fac3602ef50052fb147f6fb4 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 10:52:13 +0200 Subject: [PATCH 01/10] Retain bounded StockSelection compiler capture evidence --- docs/roadmaps/barc-01-useful-play.md | 22 ++++ .../Policy/build-stock-selection.mjs | 104 +++++++++++++----- .../Policy/build-stock-selection.test.mjs | 32 ++++++ 3 files changed, 129 insertions(+), 29 deletions(-) create mode 100644 tests/Broker.NativeProof/Policy/build-stock-selection.test.mjs diff --git a/docs/roadmaps/barc-01-useful-play.md b/docs/roadmaps/barc-01-useful-play.md index b55d543..d71f847 100644 --- a/docs/roadmaps/barc-01-useful-play.md +++ b/docs/roadmaps/barc-01-useful-play.md @@ -390,3 +390,25 @@ capacity and prospective operation authority remain required; all useful-play journeys remain **0/6**. Admission telemetry attempt `bar-stock-correspondence-source-admission-20261003` returned `not-configured`; usage is Unknown. + +## StockSelection compiler capture source — 2026-10-04 + +The canonical [StockSelection producer](../../tests/Broker.NativeProof/Policy/build-stock-selection.mjs) +accepts `--capture-dir ` for prospective qualification. +That route retains separate raw restore/Fable streams, actual nested arguments, +working directory and closed child environment, Fable 5.18 `--verbose` evaluated +source/reference hashes, the temporary output graph and literal runtime-import +rewrite correspondence. Existing builds without capture keep their cleanup route. +The capture records prior descriptors as historical and publishes a new descriptor +only after successful compiler and runtime-byte guards. A captured compiler graph +is observed after compilation and must join an independently frozen parent input +census; it does not establish compiler/package authenticity by itself. + +Thirteen [production-call controls](../../tests/Broker.NativeProof/Policy/build-stock-selection.test.mjs) +pass on the existing Node 24.8 interpreter with every SDK/compiler launch mocked. +They cover raw streams, retained partial failures, timeout/signal refusal, stale +previous descriptors, capture collisions, graph links, runtime mismatch, missing +verbose receipts and authored input drift. No restore, compiler, browser, engine +or game operation was executed. Authentic producer tools and fresh bounded build +admission remain pending. This source slice changes neither runtime authority nor +historical READY/map cause Unknown; useful native play remains **0/6**. diff --git a/tests/Broker.NativeProof/Policy/build-stock-selection.mjs b/tests/Broker.NativeProof/Policy/build-stock-selection.mjs index 96a0beb..99bd63d 100644 --- a/tests/Broker.NativeProof/Policy/build-stock-selection.mjs +++ b/tests/Broker.NativeProof/Policy/build-stock-selection.mjs @@ -1,35 +1,81 @@ import { createHash } from "node:crypto"; -import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, readdirSync, lstatSync, existsSync } from "node:fs"; import { tmpdir } from "node:os"; -import { dirname, join, resolve } from "node:path"; +import { dirname, join, resolve, relative, isAbsolute } from "node:path"; import { fileURLToPath } from "node:url"; import { spawnSync } from "node:child_process"; -const policyRoot=dirname(fileURLToPath(import.meta.url)), proofRoot=dirname(policyRoot), repoRoot=resolve(proofRoot,"../.."), temporary=mkdtempSync(join(tmpdir(),"stock-selection-fable-")), outputRoot=join(policyRoot,"generated"), hash=bytes=>createHash("sha256").update(bytes).digest("hex"), read=path=>readFileSync(path), relativeToRepo=path=>path.slice(repoRoot.length+1).replaceAll("\\","/"); - -try{ - const tools=JSON.parse(readFileSync(join(repoRoot,".config/dotnet-tools.json"),"utf8")),fableVersion=tools.tools.fable.version; - if(fableVersion!=="5.18.0")throw new Error(`expected pinned Fable 5.18.0, found ${fableVersion}`); - const restore=spawnSync("dotnet",["restore",join(policyRoot,"StockSelection.fsproj"),"--locked-mode"],{cwd:repoRoot,encoding:"utf8"}); - if(restore.status!==0)throw new Error(`locked policy restore failed\n${restore.stdout}${restore.stderr}`); - const result=spawnSync("dotnet",["fable",join(policyRoot,"StockSelection.fsproj"),"--outDir",temporary,"--noCache","--noRestore"],{cwd:repoRoot,encoding:"utf8"}); - if(result.status!==0)throw new Error(`Fable compilation failed\n${result.stdout}${result.stderr}`); - const compiledPath=join(temporary,"StockSelection.js"),libraryRoot=join(proofRoot,"node_modules/@fable-org/fable-library-js"); - let compiled=readFileSync(compiledPath,"utf8"); - const imports=[...compiled.matchAll(/\.\/fable_modules\/fable-library-js\.5\.18\.0\/([^"']+)/g)].map(match=>match[1]); - if(imports.length===0)throw new Error("generated policy did not declare its Fable runtime imports"); - for(const imported of new Set(imports)){ - const copied=read(join(temporary,"fable_modules/fable-library-js.5.18.0",imported)),installed=read(join(libraryRoot,imported)); - if(!copied.equals(installed))throw new Error(`Fable runtime import differs from pinned npm package: ${imported}`); - } - compiled=`${compiled.replaceAll("./fable_modules/fable-library-js.5.18.0/","@fable-org/fable-library-js/").trimEnd()}\n`; - mkdirSync(outputRoot,{recursive:true}); - const generatedPath=join(outputRoot,"StockSelection.js"),generatedBytes=Buffer.from(compiled); - writeFileSync(generatedPath,generatedBytes); - const inputs=["Policy/Directory.Build.props","Policy/StockSelection.fs","Policy/StockSelection.fsproj","Policy/StockSelection.packages.lock.json","Policy/build-stock-selection.mjs","package.json","package-lock.json"].map(path=>{const absolute=join(proofRoot,path);return{path:`tests/Broker.NativeProof/${path}`,sha256:hash(read(absolute))}}); - const descriptor={schema:"fsbar.stock-selection-policy-artifact/v1",compiler:{tool:"fable",version:fableVersion},runtime:{package:"@fable-org/fable-library-js",version:"2.8.0"},inputs,output:{path:relativeToRepo(generatedPath),sha256:hash(generatedBytes)}}; - writeFileSync(join(outputRoot,"stock-selection-policy.json"),`${JSON.stringify(descriptor,null,2)}\n`); - process.stdout.write(`stock-selection-policy-generated ${descriptor.output.sha256}\n`); -}finally{ - rmSync(temporary,{recursive:true,force:true}); +const ownPolicyRoot=dirname(fileURLToPath(import.meta.url)); +const hash=bytes=>createHash("sha256").update(bytes).digest("hex"); +const fail=message=>{throw new Error(message)}; +const need=(value,message)=>{if(!value)fail(message)}; +const read=path=>readFileSync(path); +const safeRelative=path=>!isAbsolute(path)&&path!==""&&!path.split(/[\\/]/).includes(".."); +const environmentKeys=["PATH","HOME","LANG","LC_ALL","TMPDIR","DOTNET_ROOT","DOTNET_ROLL_FORWARD","DOTNET_EnableDiagnostics","DOTNET_GENERATE_ASPNET_CERTIFICATE","DOTNET_CLI_HOME","DOTNET_CLI_TELEMETRY_OPTOUT","DOTNET_SKIP_FIRST_TIME_EXPERIENCE","DOTNET_NOLOGO","DOTNET_PROCESSOR_COUNT","NUGET_PACKAGES","NUGET_HTTP_CACHE_PATH","MSBUILDDISABLENODEREUSE"]; +function regular(path){const s=lstatSync(path);need(s.isFile()&&!s.isSymbolicLink()&&s.nlink===1,"regular single-link compiler input/output required");return s} +function beneath(root,path){ + need(safeRelative(path),"closed relative member path");let parent=root;need(lstatSync(parent).isDirectory()&&!lstatSync(parent).isSymbolicLink(),"member root symlink/type");for(const part of path.split("/").slice(0,-1)){parent=join(parent,part);const s=lstatSync(parent);need(s.isDirectory()&&!s.isSymbolicLink(),"member ancestor symlink/type")}return join(root,path); +} +function rows(root){ + const out=[]; + function visit(path){for(const name of readdirSync(path).sort()){const full=join(path,name),s=lstatSync(full);need(!s.isSymbolicLink(),"temporary graph symlink");if(s.isDirectory())visit(full);else{regular(full);out.push({path:relative(root,full).replaceAll("\\","/"),bytes:s.size,sha256:hash(read(full))});need(out.length<=20000,"temporary graph leaf bound")}}} + visit(root);need(out.reduce((n,x)=>n+x.bytes,0)<=1073741824,"temporary graph byte bound");return out; +} +function freshCapture(path){ + need(isAbsolute(path),"capture-dir must be absolute");path=resolve(path);need(!existsSync(path),"capture-dir must be fresh");let parent=dirname(path); + while(true){const s=lstatSync(parent);need(s.isDirectory()&&!s.isSymbolicLink(),"capture ancestor symlink/type");if(parent===dirname(parent))break;parent=dirname(parent)} + mkdirSync(path,{mode:0o700});return path; +} +export function evaluatedInputs(raw,cwd){ + // Fable5.18 Main.fs prints metadata followed by OtherOptions and SourceFiles. + // Preserve the raw log as authority; parsed receipts do not invent pre-compile observations. + const text=raw.toString("utf8"),start=text.indexOf("F# PROJECT: ");need(start>=0&&text.indexOf("F# PROJECT: ",start+1)<0,"one verbose compiler project graph required"); + const block=text.slice(start),lines=block.split(/\r?\n/);need(lines.length>=6&&lines[1].startsWith("FABLE LIBRARY: ")&&lines[2].startsWith("TARGET FRAMEWORK: ")&&lines[3].startsWith("OUTPUT TYPE: ")&&lines[4]==="","typed Fable verbose graph header"); + const entries=[];for(let i=5;i!x.startsWith("-")&&/\.fsi?$/.test(x));const references=entries.filter(x=>x.startsWith("-r:")).map(x=>x.slice(3));need(sources.length>0&&references.length>0,"nonempty actual source/reference graph"); + need(entries.every(x=>x.startsWith("-")||/\.fsi?$/.test(x)),"untyped verbose compiler graph row"); + const pin=(path,role)=>{const absolute=resolve(cwd,path);const s=regular(absolute);return{role,compilerPath:path,absolutePath:absolute,bytes:s.size,sha256:hash(read(absolute)),observation:"after compiler; parent must join pre/post frozen input census"}}; + return{project:lines[0].slice(12),fableLibrary:lines[1].slice(15),targetFramework:lines[2].slice(18),outputType:lines[3].slice(13),options:entries.filter(x=>x.startsWith("-")),sources:sources.map(x=>pin(x,"source")),references:references.map(x=>pin(x,"reference")),rawSHA256:hash(raw),headerCharacterOffset:start}; +} +export function buildStockSelection({captureDir=null,policyRoot=ownPolicyRoot,environment=process.env}={},run=spawnSync){ + const proofRoot=dirname(policyRoot),repoRoot=resolve(proofRoot,"../.."),outputRoot=join(policyRoot,"generated"),capture=captureDir?freshCapture(captureDir):null; + const temporary= capture?join(capture,"fable-output"):mkdtempSync(join(tmpdir(),"stock-selection-fable-")); + if(capture)mkdirSync(temporary,{mode:0o700}); + const save=(name,value)=>{if(capture)writeFileSync(join(capture,name),Buffer.isBuffer(value)?value:`${JSON.stringify(value,null,2)}\n`,{mode:0o600,flag:"wx"})}; + const childEnv=capture?Object.fromEntries(environmentKeys.filter(k=>environment[k]!==undefined).map(k=>[k,environment[k]])):environment; + if(capture){mkdirSync(join(capture,"tmp"),{mode:0o700});childEnv.TMPDIR=join(capture,"tmp")} + const descriptorPath=join(outputRoot,"stock-selection-policy.json"),originalDescriptor=existsSync(descriptorPath)?{sha256:hash(read(descriptorPath)),bytes:read(descriptorPath).length}:null; + const phases=[];let outcome={schema:"fsbar.stock-selection-build-capture/v1",state:"FAILED",captureComplete:false,publishedNewDescriptor:false,originalDescriptor,temporaryRetained:Boolean(capture)},failure=null; + function invoke(name,argv){ + const started=new Date().toISOString(),result=run("dotnet",argv,{cwd:repoRoot,env:childEnv,maxBuffer:33554432,timeout:120000,encoding:null}); + const stdout=Buffer.from(result.stdout??""),stderr=Buffer.from(result.stderr??"");save(`${name}.stdout.raw`,stdout);save(`${name}.stderr.raw`,stderr); + const receipt={name,executable:"dotnet",argv,cwd:repoRoot,environment:childEnv,startedUTC:started,finishedUTC:new Date().toISOString(),actualChildPID:result.pid??null,status:result.status,signal:result.signal??null,error:result.error?{code:result.error.code??null,message:result.error.message}:null,stdout:{bytes:stdout.length,sha256:hash(stdout)},stderr:{bytes:stderr.length,sha256:hash(stderr)}};phases.push(receipt);save(`${name}.json`,receipt); + need(!result.error&&result.status===0&&!result.signal,`${name} failed; raw streams retained in capture-dir`);need(stdout.length<=33554432&&stderr.length<=33554432,"child output bound");return{stdout,stderr}; + } + try{ + const tools=JSON.parse(read(join(repoRoot,".config/dotnet-tools.json"))),version=tools.tools.fable.version;need(version==="5.18.0",`expected pinned Fable 5.18.0, found ${version}`); + const inputPaths=["Policy/Directory.Build.props","Policy/StockSelection.fs","Policy/StockSelection.fsproj","Policy/StockSelection.packages.lock.json","Policy/build-stock-selection.mjs","package.json","package-lock.json"]; + const authored=()=>inputPaths.map(path=>{const absolute=join(proofRoot,path);regular(absolute);return{path:`tests/Broker.NativeProof/${path}`,sha256:hash(read(absolute))}});const before=authored();save("authored-before.json",before); + invoke("restore",["restore",join(policyRoot,"StockSelection.fsproj"),"--locked-mode"]); + const result=invoke("fable",["fable",join(policyRoot,"StockSelection.fsproj"),"--outDir",temporary,"--noCache","--noRestore",...(capture?["--verbose"]:[])]); + if(capture)save("evaluated-inputs.json",evaluatedInputs(result.stdout,repoRoot)); + const compiledPath=join(temporary,"StockSelection.js"),libraryRoot=join(proofRoot,"node_modules/@fable-org/fable-library-js");regular(compiledPath);const original=read(compiledPath);let compiled=original.toString("utf8"); + const imports=[...compiled.matchAll(/\.\/fable_modules\/fable-library-js\.5\.18\.0\/([^"']+)/g)].map(match=>match[1]);need(imports.length>0,"generated policy did not declare its Fable runtime imports");const importedModules=[]; + for(const imported of new Set(imports)){need(safeRelative(imported),"runtime import traversal/absolute path");const copiedPath=beneath(temporary,`fable_modules/fable-library-js.5.18.0/${imported}`),installedPath=beneath(libraryRoot,imported);regular(copiedPath);regular(installedPath);const copied=read(copiedPath),installed=read(installedPath);need(copied.equals(installed),`Fable runtime import differs from pinned npm package: ${imported}`);importedModules.push({path:imported,temporaryPath:copiedPath,installedPath,bytes:copied.length,sha256:hash(copied)})} + const inputs=authored();need(JSON.stringify(inputs)===JSON.stringify(before),"authored compiler input drift");save("authored-after.json",inputs);if(capture)save("temporary-graph.json",rows(temporary)); + compiled=`${compiled.replaceAll("./fable_modules/fable-library-js.5.18.0/","@fable-org/fable-library-js/").trimEnd()}\n`;const generatedBytes=Buffer.from(compiled),generatedPath=join(outputRoot,"StockSelection.js"); + save("rewrite-correspondence.json",{schema:"fsbar.stock-selection-import-rewrite/v1",original:{path:compiledPath,sha256:hash(original)},rewritten:{path:generatedPath,sha256:hash(generatedBytes)},transform:"literal Fable library prefix replacement, trimEnd, one final LF",imports,importedModules}); + if(existsSync(outputRoot))need(lstatSync(outputRoot).isDirectory()&&!lstatSync(outputRoot).isSymbolicLink(),"generated output directory type/symlink");else mkdirSync(outputRoot,{recursive:true});for(const path of [generatedPath,descriptorPath])if(existsSync(path))regular(path); + const descriptor={schema:"fsbar.stock-selection-policy-artifact/v1",compiler:{tool:"fable",version},runtime:{package:"@fable-org/fable-library-js",version:"2.8.0"},inputs,output:{path:relative(repoRoot,generatedPath).replaceAll("\\","/"),sha256:hash(generatedBytes)}}; + writeFileSync(generatedPath,generatedBytes);writeFileSync(descriptorPath,`${JSON.stringify(descriptor,null,2)}\n`); + outcome={...outcome,state:"COMPLETED",captureComplete:Boolean(capture),publishedNewDescriptor:true,descriptorSHA256:hash(read(descriptorPath)),output:descriptor.output};return descriptor; + }catch(error){failure=error;throw error} + finally{ + if(capture){try{save("retained-graph.json",rows(temporary))}catch(error){outcome={...outcome,state:"FAILED",captureComplete:false,graphCustodyFailure:error.message};if(!failure)throw error}finally{save("result.json",{...outcome,failure:failure?.message??null,phases:phases.map(x=>x.name),qualificationBoundary:"Source correctness only until original compiler/tools/packages/input census and actual parent admission are independently accepted"})}} + else rmSync(temporary,{recursive:true,force:true}); + } +} +export function cli(args){need(args.length===0||(args.length===2&&args[0]==="--capture-dir"),"usage: build-stock-selection.mjs [--capture-dir ]");return buildStockSelection({captureDir:args[1]??null})} +if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)){ + try{const descriptor=cli(process.argv.slice(2));process.stdout.write(`stock-selection-policy-generated ${descriptor.output.sha256}\n`)}catch(error){process.stderr.write(`${error.message}\n`);process.exitCode=1} } diff --git a/tests/Broker.NativeProof/Policy/build-stock-selection.test.mjs b/tests/Broker.NativeProof/Policy/build-stock-selection.test.mjs new file mode 100644 index 0000000..3b22bb9 --- /dev/null +++ b/tests/Broker.NativeProof/Policy/build-stock-selection.test.mjs @@ -0,0 +1,32 @@ +import {test} from 'node:test';import assert from 'node:assert/strict'; +import {mkdtempSync,mkdirSync,writeFileSync,readFileSync,rmSync,existsSync,symlinkSync,linkSync} from 'node:fs';import {tmpdir} from 'node:os';import{join}from'node:path'; +import{buildStockSelection,evaluatedInputs,cli}from'./build-stock-selection.mjs'; +function fixture(){ + const root=mkdtempSync(join(tmpdir(),'stock-capture-pure-')),repo=join(root,'repo'),proof=join(repo,'tests/Broker.NativeProof'),policy=join(proof,'Policy');mkdirSync(policy,{recursive:true});mkdirSync(join(repo,'.config'));writeFileSync(join(repo,'.config/dotnet-tools.json'),JSON.stringify({tools:{fable:{version:'5.18.0'}}})); + for(const file of ['Directory.Build.props','StockSelection.fs','StockSelection.fsproj','StockSelection.packages.lock.json','build-stock-selection.mjs'])writeFileSync(join(policy,file),'source');for(const file of ['package.json','package-lock.json'])writeFileSync(join(proof,file),'{}'); + mkdirSync(join(proof,'node_modules/@fable-org/fable-library-js'),{recursive:true});writeFileSync(join(proof,'node_modules/@fable-org/fable-library-js/Array.js'),'runtime');const reference=join(repo,'Fable.Core.dll');writeFileSync(reference,'mock-reference-bytes'); + const capture=join(root,'capture'),calls=[]; + const run=(executable,argv,options)=>{ + calls.push({executable,argv,options});if(argv[0]==='restore')return{status:0,stdout:Buffer.from('restore\r\n'),stderr:Buffer.from([0,1,2])};const output=argv[argv.indexOf('--outDir')+1];mkdirSync(join(output,'fable_modules/fable-library-js.5.18.0'),{recursive:true});writeFileSync(join(output,'fable_modules/fable-library-js.5.18.0/Array.js'),'runtime');writeFileSync(join(output,'StockSelection.js'),'import { x } from "./fable_modules/fable-library-js.5.18.0/Array.js";\n'); + return{status:0,stdout:Buffer.from(`F# PROJECT: tests/Broker.NativeProof/Policy/StockSelection.fsproj\nFABLE LIBRARY: ${output}/fable_modules\nTARGET FRAMEWORK: net10.0\nOUTPUT TYPE: Library\n\n -r:${reference}\n ${policy}/StockSelection.fs\n\n`),stderr:Buffer.from('compiler-warning\n')}; + }; + return{root,repo,proof,policy,capture,calls,run,reference}; +} +function using(body){const f=fixture();try{body(f)}finally{rmSync(f.root,{recursive:true,force:true})}} +const read=(f,n)=>JSON.parse(readFileSync(join(f.capture,n),'utf8')); +test('real production capture retains raw streams, argv/env, evaluated graph and rewrite',()=>using(f=>{ + const descriptor=buildStockSelection({policyRoot:f.policy,captureDir:f.capture,environment:{PATH:'/mock/no-launch',HOME:'/unchanged',TOKEN:'secret'}},f.run);assert.equal(f.calls.length,2);assert.ok(f.calls[1].argv.includes('--verbose'));assert.equal(f.calls[1].options.env.HOME,'/unchanged');assert.equal(f.calls[1].options.env.TOKEN,undefined);assert.equal(f.calls[1].options.env.TMPDIR,join(f.capture,'tmp')); + assert.deepEqual(readFileSync(join(f.capture,'restore.stderr.raw')),Buffer.from([0,1,2]));assert.equal(read(f,'fable.json').cwd,f.repo);assert.equal(read(f,'evaluated-inputs.json').sources.length,1);assert.equal(read(f,'evaluated-inputs.json').references.length,1);assert.equal(read(f,'result.json').captureComplete,true);assert.ok(existsSync(join(f.capture,'fable-output/StockSelection.js')));assert.equal(read(f,'rewrite-correspondence.json').rewritten.sha256,descriptor.output.sha256); +})); +test('restore error retains exact raw failure and no new descriptor',()=>using(f=>{const mock=()=>({status:1,stdout:Buffer.from('out'),stderr:Buffer.from('fail')});assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},mock),/restore failed/);assert.equal(read(f,'result.json').captureComplete,false);assert.equal(read(f,'result.json').publishedNewDescriptor,false);assert.equal(readFileSync(join(f.capture,'restore.stderr.raw'),'utf8'),'fail');assert.equal(existsSync(join(f.policy,'generated/stock-selection-policy.json')),false)})); +test('signal/timeout records refusal and partial compiler graph retained',()=>using(f=>{const mock=(...args)=>{const x=f.run(...args);return args[1][0]==='fable'?{...x,status:null,signal:'SIGTERM',error:{code:'ETIMEDOUT',message:'timeout'}}:x};assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},mock),/fable failed/);assert.equal(read(f,'fable.json').signal,'SIGTERM');assert.equal(read(f,'fable.json').error.code,'ETIMEDOUT');assert.ok(read(f,'retained-graph.json').length>0);assert.equal(read(f,'result.json').captureComplete,false)})); +test('capture collision refuses before mock launches',()=>using(f=>{mkdirSync(f.capture);assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},f.run),/fresh/);assert.equal(f.calls.length,0)})); +test('capture parent symlink refuses before mock launches',()=>using(f=>{const actual=join(f.root,'actual');mkdirSync(actual);symlinkSync(actual,join(f.root,'link'));assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:join(f.root,'link/capture')},f.run),/symlink/);assert.equal(f.calls.length,0)})); +test('runtime hash mismatch fails without publication',()=>using(f=>{writeFileSync(join(f.proof,'node_modules/@fable-org/fable-library-js/Array.js'),'foreign');assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},f.run),/differs/);assert.equal(read(f,'result.json').publishedNewDescriptor,false)})); +test('temporary graph symlink fails with retained custody failure',()=>using(f=>{const mock=(...args)=>{const x=f.run(...args);if(args[1][0]==='fable')symlinkSync(f.reference,join(f.capture,'fable-output/foreign'));return x};assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},mock),/symlink/);assert.equal(read(f,'result.json').captureComplete,false);assert.match(read(f,'result.json').graphCustodyFailure,/symlink/)})); +test('temporary graph hardlink fails',()=>using(f=>{const mock=(...args)=>{const x=f.run(...args);if(args[1][0]==='fable')linkSync(f.reference,join(f.capture,'fable-output/foreign'));return x};assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},mock),/single-link/);assert.equal(read(f,'result.json').captureComplete,false)})); +test('missing actual verbose graph cannot publish descriptor',()=>using(f=>{const mock=(...args)=>({...f.run(...args),stdout:Buffer.from('no graph')});assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},mock),/verbose/);assert.equal(read(f,'result.json').publishedNewDescriptor,false)})); +test('authored drift during compiler run fails',()=>using(f=>{const mock=(...args)=>{const x=f.run(...args);if(args[1][0]==='fable')writeFileSync(join(f.policy,'StockSelection.fs'),'changed');return x};assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},mock),/drift/);assert.equal(read(f,'result.json').captureComplete,false)})); +test('old descriptor remains explicitly historical on compiler failure',()=>using(f=>{mkdirSync(join(f.policy,'generated'));writeFileSync(join(f.policy,'generated/stock-selection-policy.json'),'old');assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},()=>({status:1,stdout:'',stderr:'error'})),/restore failed/);assert.equal(read(f,'result.json').originalDescriptor.bytes,3);assert.equal(read(f,'result.json').publishedNewDescriptor,false);assert.equal(readFileSync(join(f.policy,'generated/stock-selection-policy.json'),'utf8'),'old')})); +test('untyped graph and missing references fail',()=>{assert.throws(()=>evaluatedInputs(Buffer.from('F# PROJECT: p\nFABLE LIBRARY: f\nTARGET FRAMEWORK: t\nOUTPUT TYPE: o\n\n bad\n'),'/'),/source\/reference/)}); +test('unknown CLI options refuse before production build',()=>assert.throws(()=>cli(['--foreign']),/usage/)); From 3d5ef1d5c74fa6b98abf31719d3e8dc6a7f73531 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 11:33:00 +0200 Subject: [PATCH 02/10] Bind explicit stock NuGet configuration in compiler capture --- docs/roadmaps/barc-01-useful-play.md | 8 ++++++-- .../Policy/build-stock-selection.mjs | 16 +++++++++++----- .../Policy/build-stock-selection.test.mjs | 7 ++++++- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/docs/roadmaps/barc-01-useful-play.md b/docs/roadmaps/barc-01-useful-play.md index d71f847..6fa5f2b 100644 --- a/docs/roadmaps/barc-01-useful-play.md +++ b/docs/roadmaps/barc-01-useful-play.md @@ -404,9 +404,13 @@ only after successful compiler and runtime-byte guards. A captured compiler grap is observed after compilation and must join an independently frozen parent input census; it does not establish compiler/package authenticity by itself. -Thirteen [production-call controls](../../tests/Broker.NativeProof/Policy/build-stock-selection.test.mjs) +Seventeen [production-call controls](../../tests/Broker.NativeProof/Policy/build-stock-selection.test.mjs) pass on the existing Node 24.8 interpreter with every SDK/compiler launch mocked. -They cover raw streams, retained partial failures, timeout/signal refusal, stale +The optional `--nuget-config ` argument passes the exact reviewed +stock feed configuration to nested restore and hashes it before/after compilation. +The original 1MiB nested output limit remains enforced. Controls cover explicit +config arguments, drift, invalid paths and duplicate flags as well as raw streams, +retained partial failures, timeout/signal refusal, stale previous descriptors, capture collisions, graph links, runtime mismatch, missing verbose receipts and authored input drift. No restore, compiler, browser, engine or game operation was executed. Authentic producer tools and fresh bounded build diff --git a/tests/Broker.NativeProof/Policy/build-stock-selection.mjs b/tests/Broker.NativeProof/Policy/build-stock-selection.mjs index 99bd63d..6a76783 100644 --- a/tests/Broker.NativeProof/Policy/build-stock-selection.mjs +++ b/tests/Broker.NativeProof/Policy/build-stock-selection.mjs @@ -37,7 +37,7 @@ export function evaluatedInputs(raw,cwd){ const pin=(path,role)=>{const absolute=resolve(cwd,path);const s=regular(absolute);return{role,compilerPath:path,absolutePath:absolute,bytes:s.size,sha256:hash(read(absolute)),observation:"after compiler; parent must join pre/post frozen input census"}}; return{project:lines[0].slice(12),fableLibrary:lines[1].slice(15),targetFramework:lines[2].slice(18),outputType:lines[3].slice(13),options:entries.filter(x=>x.startsWith("-")),sources:sources.map(x=>pin(x,"source")),references:references.map(x=>pin(x,"reference")),rawSHA256:hash(raw),headerCharacterOffset:start}; } -export function buildStockSelection({captureDir=null,policyRoot=ownPolicyRoot,environment=process.env}={},run=spawnSync){ +export function buildStockSelection({captureDir=null,nugetConfig=null,policyRoot=ownPolicyRoot,environment=process.env}={},run=spawnSync){ const proofRoot=dirname(policyRoot),repoRoot=resolve(proofRoot,"../.."),outputRoot=join(policyRoot,"generated"),capture=captureDir?freshCapture(captureDir):null; const temporary= capture?join(capture,"fable-output"):mkdtempSync(join(tmpdir(),"stock-selection-fable-")); if(capture)mkdirSync(temporary,{mode:0o700}); @@ -47,21 +47,23 @@ export function buildStockSelection({captureDir=null,policyRoot=ownPolicyRoot,en const descriptorPath=join(outputRoot,"stock-selection-policy.json"),originalDescriptor=existsSync(descriptorPath)?{sha256:hash(read(descriptorPath)),bytes:read(descriptorPath).length}:null; const phases=[];let outcome={schema:"fsbar.stock-selection-build-capture/v1",state:"FAILED",captureComplete:false,publishedNewDescriptor:false,originalDescriptor,temporaryRetained:Boolean(capture)},failure=null; function invoke(name,argv){ - const started=new Date().toISOString(),result=run("dotnet",argv,{cwd:repoRoot,env:childEnv,maxBuffer:33554432,timeout:120000,encoding:null}); + const started=new Date().toISOString(),result=run("dotnet",argv,{cwd:repoRoot,env:childEnv,maxBuffer:1048576,timeout:120000,encoding:null}); const stdout=Buffer.from(result.stdout??""),stderr=Buffer.from(result.stderr??"");save(`${name}.stdout.raw`,stdout);save(`${name}.stderr.raw`,stderr); const receipt={name,executable:"dotnet",argv,cwd:repoRoot,environment:childEnv,startedUTC:started,finishedUTC:new Date().toISOString(),actualChildPID:result.pid??null,status:result.status,signal:result.signal??null,error:result.error?{code:result.error.code??null,message:result.error.message}:null,stdout:{bytes:stdout.length,sha256:hash(stdout)},stderr:{bytes:stderr.length,sha256:hash(stderr)}};phases.push(receipt);save(`${name}.json`,receipt); - need(!result.error&&result.status===0&&!result.signal,`${name} failed; raw streams retained in capture-dir`);need(stdout.length<=33554432&&stderr.length<=33554432,"child output bound");return{stdout,stderr}; + need(!result.error&&result.status===0&&!result.signal,`${name} failed; raw streams retained in capture-dir`);need(stdout.length<=1048576&&stderr.length<=1048576,"child output bound");return{stdout,stderr}; } try{ + const configPin=()=>{if(!nugetConfig)return null;need(isAbsolute(nugetConfig),"nuget-config must be absolute");const absolute=resolve(nugetConfig);let parent=dirname(absolute);while(true){const st=lstatSync(parent);need(st.isDirectory()&&!st.isSymbolicLink(),"NuGet config ancestor symlink/type");if(parent===dirname(parent))break;parent=dirname(parent)}const st=regular(absolute);return{path:absolute,bytes:st.size,sha256:hash(read(absolute))}};const configBefore=configPin();if(configBefore)save("nuget-config-before.json",configBefore); const tools=JSON.parse(read(join(repoRoot,".config/dotnet-tools.json"))),version=tools.tools.fable.version;need(version==="5.18.0",`expected pinned Fable 5.18.0, found ${version}`); const inputPaths=["Policy/Directory.Build.props","Policy/StockSelection.fs","Policy/StockSelection.fsproj","Policy/StockSelection.packages.lock.json","Policy/build-stock-selection.mjs","package.json","package-lock.json"]; const authored=()=>inputPaths.map(path=>{const absolute=join(proofRoot,path);regular(absolute);return{path:`tests/Broker.NativeProof/${path}`,sha256:hash(read(absolute))}});const before=authored();save("authored-before.json",before); - invoke("restore",["restore",join(policyRoot,"StockSelection.fsproj"),"--locked-mode"]); + invoke("restore",["restore",join(policyRoot,"StockSelection.fsproj"),"--locked-mode",...(configBefore?["--configfile",configBefore.path]:[])]); const result=invoke("fable",["fable",join(policyRoot,"StockSelection.fsproj"),"--outDir",temporary,"--noCache","--noRestore",...(capture?["--verbose"]:[])]); if(capture)save("evaluated-inputs.json",evaluatedInputs(result.stdout,repoRoot)); const compiledPath=join(temporary,"StockSelection.js"),libraryRoot=join(proofRoot,"node_modules/@fable-org/fable-library-js");regular(compiledPath);const original=read(compiledPath);let compiled=original.toString("utf8"); const imports=[...compiled.matchAll(/\.\/fable_modules\/fable-library-js\.5\.18\.0\/([^"']+)/g)].map(match=>match[1]);need(imports.length>0,"generated policy did not declare its Fable runtime imports");const importedModules=[]; for(const imported of new Set(imports)){need(safeRelative(imported),"runtime import traversal/absolute path");const copiedPath=beneath(temporary,`fable_modules/fable-library-js.5.18.0/${imported}`),installedPath=beneath(libraryRoot,imported);regular(copiedPath);regular(installedPath);const copied=read(copiedPath),installed=read(installedPath);need(copied.equals(installed),`Fable runtime import differs from pinned npm package: ${imported}`);importedModules.push({path:imported,temporaryPath:copiedPath,installedPath,bytes:copied.length,sha256:hash(copied)})} + const configAfter=configPin();need(JSON.stringify(configBefore)===JSON.stringify(configAfter),"explicit NuGet config drift");if(configAfter)save("nuget-config-after.json",configAfter); const inputs=authored();need(JSON.stringify(inputs)===JSON.stringify(before),"authored compiler input drift");save("authored-after.json",inputs);if(capture)save("temporary-graph.json",rows(temporary)); compiled=`${compiled.replaceAll("./fable_modules/fable-library-js.5.18.0/","@fable-org/fable-library-js/").trimEnd()}\n`;const generatedBytes=Buffer.from(compiled),generatedPath=join(outputRoot,"StockSelection.js"); save("rewrite-correspondence.json",{schema:"fsbar.stock-selection-import-rewrite/v1",original:{path:compiledPath,sha256:hash(original)},rewritten:{path:generatedPath,sha256:hash(generatedBytes)},transform:"literal Fable library prefix replacement, trimEnd, one final LF",imports,importedModules}); @@ -75,7 +77,11 @@ export function buildStockSelection({captureDir=null,policyRoot=ownPolicyRoot,en else rmSync(temporary,{recursive:true,force:true}); } } -export function cli(args){need(args.length===0||(args.length===2&&args[0]==="--capture-dir"),"usage: build-stock-selection.mjs [--capture-dir ]");return buildStockSelection({captureDir:args[1]??null})} +export function cli(args){ + const options={};need(args.length%2===0,"usage: build-stock-selection.mjs [--capture-dir ] [--nuget-config ]"); + for(let i=0;iusing(f=>{cons test('authored drift during compiler run fails',()=>using(f=>{const mock=(...args)=>{const x=f.run(...args);if(args[1][0]==='fable')writeFileSync(join(f.policy,'StockSelection.fs'),'changed');return x};assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},mock),/drift/);assert.equal(read(f,'result.json').captureComplete,false)})); test('old descriptor remains explicitly historical on compiler failure',()=>using(f=>{mkdirSync(join(f.policy,'generated'));writeFileSync(join(f.policy,'generated/stock-selection-policy.json'),'old');assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture},()=>({status:1,stdout:'',stderr:'error'})),/restore failed/);assert.equal(read(f,'result.json').originalDescriptor.bytes,3);assert.equal(read(f,'result.json').publishedNewDescriptor,false);assert.equal(readFileSync(join(f.policy,'generated/stock-selection-policy.json'),'utf8'),'old')})); test('untyped graph and missing references fail',()=>{assert.throws(()=>evaluatedInputs(Buffer.from('F# PROJECT: p\nFABLE LIBRARY: f\nTARGET FRAMEWORK: t\nOUTPUT TYPE: o\n\n bad\n'),'/'),/source\/reference/)}); -test('unknown CLI options refuse before production build',()=>assert.throws(()=>cli(['--foreign']),/usage/)); +test('unknown CLI options refuse before production build',()=>assert.throws(()=>cli(['--foreign']),/usage|CLI/)); + +test('explicit config is the actual nested restore argument and byte-pinned before/after',()=>using(f=>{const config=join(f.root,'stock.NuGet.Config');writeFileSync(config,'');buildStockSelection({policyRoot:f.policy,captureDir:f.capture,nugetConfig:config},f.run);assert.deepEqual(f.calls[0].argv.slice(-2),['--configfile',config]);assert.deepEqual(read(f,'nuget-config-before.json'),read(f,'nuget-config-after.json'));assert.equal(f.calls[0].options.maxBuffer,1048576)})); +test('explicit config drift refuses descriptor publication',()=>using(f=>{const config=join(f.root,'stock.NuGet.Config');writeFileSync(config,'original');const mock=(...args)=>{const x=f.run(...args);if(args[1][0]==='fable')writeFileSync(config,'changed');return x};assert.throws(()=>buildStockSelection({policyRoot:f.policy,captureDir:f.capture,nugetConfig:config},mock),/config drift/);assert.equal(read(f,'result.json').publishedNewDescriptor,false)})); +test('wrong config path/type/link refuses before nested launch',()=>using(f=>{for(const config of ['relative',join(f.root,'absent'),f.root]){assert.throws(()=>buildStockSelection({policyRoot:f.policy,nugetConfig:config},f.run));assert.equal(f.calls.length,0)}const config=join(f.root,'linked');symlinkSync(f.reference,config);assert.throws(()=>buildStockSelection({policyRoot:f.policy,nugetConfig:config},f.run),/regular/);assert.equal(f.calls.length,0)})); +test('explicit duplicate config CLI is refused',()=>assert.throws(()=>cli(['--nuget-config','/a','--nuget-config','/b']),/CLI/)); From 5d3e56d1efff62e8ceef2fbcfc70706db145b1c9 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 20:06:06 +0200 Subject: [PATCH 03/10] Qualify owning-generation unit attribution preparation --- docs/roadmaps/barc-01-useful-play.md | 44 +++++ .../HighBarCoordinatorService.fs | 12 ++ .../Broker.NativeProof/AttributionControls.fs | 164 ++++++++++++++++++ .../Broker.NativeProof.fsproj | 1 + tests/Broker.NativeProof/LiveHost.fs | 59 +++++++ tests/Broker.NativeProof/Program.fs | 1 + .../tactical-native-journey.spec.js | 95 +++++++++- 7 files changed, 371 insertions(+), 5 deletions(-) create mode 100644 tests/Broker.NativeProof/AttributionControls.fs diff --git a/docs/roadmaps/barc-01-useful-play.md b/docs/roadmaps/barc-01-useful-play.md index 6fa5f2b..20d6c19 100644 --- a/docs/roadmaps/barc-01-useful-play.md +++ b/docs/roadmaps/barc-01-useful-play.md @@ -416,3 +416,47 @@ verbose receipts and authored input drift. No restore, compiler, browser, engine or game operation was executed. Authentic producer tools and fresh bounded build admission remain pending. This source slice changes neither runtime authority nor historical READY/map cause Unknown; useful native play remains **0/6**. + + +## One-produced-unit source qualification — 2026-10-04 + +The [accepted-state diagnostic](../../src/Broker.Protocol/HighBarCoordinatorService.fs) +retains a cloned update only inside the accepted owning-generation fence. The +[optional raw journal](../../tests/Broker.NativeProof/LiveHost.fs) records complete +snapshots, actual creation builder identity, finish events and correlated native +dispatch with bounded run/source/writer/generation provenance. Unsupported create +and finish deltas still invalidate the browser baseline until a complete native +replacement arrives. No new RPC, public contract or native command is introduced. + +The fresh locked Release build passed naturally with the exact SDK/runtime and +signed dependency closure. Six [compiled loopback controls](../../tests/Broker.NativeProof/AttributionControls.fs) +passed through the actual gRPC service and reducer, including stale-generation +refusal and replacement recovery. Full compiler-input, PE and portable-PDB proof +binds the changed compiled bodies to frozen source digest +`adedbff5fe71daab722b6f145e69a10e3e882a2954fdbb5c2c689a5c7b2a4ac7`; +it does not attribute them to the earlier base commit. Separately, actual installed +API 0.2.1 preparation passed genuine imports and exactly one Playwright CLI +list-mode discovery through the unchanged no-process-descendants profile. Binding +change, late input drift, missing capture, extra input and symlink controls refused. +No browser, actor or game ran. Failed predecessor attempts remain failed; their +independently passed phases are reused by exact physical custody. The final capsule +source seal is `f67fae3aa72ea99db0463c41d4512d4b7b0ebb17c04b4160493d4fa530e3f397`. + +This is **source-qualified preparation**, with local routine source delivery still +pending parent PR admission and native merge readback. The compiled product bytes +are unchanged during integration; the new roadmap text requires no compile replay. +`actorEligible=false`, `unitProducedAccepted=false`, and useful native play **0/6** +remain explicit. The [one-unit oracle](../../tests/Broker.NativeProof/tactical-native-journey.spec.js) +requires a genuinely qualified native event source and no-autonomous configuration +for the exact run/source/writer/process/channel/generation before Arm. Controlled +loopback events and passive fixture comments cannot grant that qualification. + +The next source preparation joins the fresh host/receiver to the existing policy's +nineteen named managed, provenance and runtime roles, exact placement/search layout +and physical custody; it also binds the selected external-control plugin and +passive fixture/configuration to actual loaded identities. Root separately admits +a bounded attribution/startup prerequisite and any later one-shot Append Count1 +operation. Predispatch final-read remains a fence; actual creation/finish and a +separate later terminal native sample must prove exactly one product. Full six +useful-play journeys, publication and installed adoption remain open. Native usage +coverage remains unknown where the observer rejected canonical native identity. diff --git a/src/Broker.Protocol/HighBarCoordinatorService.fs b/src/Broker.Protocol/HighBarCoordinatorService.fs index 3f64c30..c2d9b45 100644 --- a/src/Broker.Protocol/HighBarCoordinatorService.fs +++ b/src/Broker.Protocol/HighBarCoordinatorService.fs @@ -8,6 +8,9 @@ open Highbar.V1 module HighBarCoordinatorService = + // Read-only opt-in diagnostics; no new RPC or public service surface. + let private acceptedStateDiagnostics = new System.Diagnostics.DiagnosticListener("FSBar.HighBar.AcceptedState") + type Config = { expectedSchemaVersion: string ownerRule: BrokerState.OwnerRule @@ -360,6 +363,15 @@ module HighBarCoordinatorService = BrokerState.refreshLiveness now service.hub | WireConvert.KeepAliveOnly -> BrokerState.refreshLiveness now service.hub + // Keep generation fencing through the readonly cloned diagnostic. + if acceptedStateDiagnostics.IsEnabled("AcceptedState") then + let disposition = + match result with + | WireConvert.NewSnapshot _ -> "materialized" + | WireConvert.Invalidated _ -> "invalidated" + | WireConvert.Gap _ -> "gap" + | WireConvert.KeepAliveOnly -> "liveness-only" + acceptedStateDiagnostics.Write("AcceptedState", box (struct(owningGeneration, upd.Clone(), disposition))) true else false) if accepted then diff --git a/tests/Broker.NativeProof/AttributionControls.fs b/tests/Broker.NativeProof/AttributionControls.fs new file mode 100644 index 0000000..68fe8b0 --- /dev/null +++ b/tests/Broker.NativeProof/AttributionControls.fs @@ -0,0 +1,164 @@ +namespace Broker.NativeProof + +open System +open System.Collections.Concurrent +open System.Diagnostics +open System.Net +open System.Net.Sockets +open System.Threading +open Grpc.Net.Client +open Broker.Core +open Broker.Protocol +open Highbar.V1 + +// Compiled service controls use a controlled loopback producer, never a game. +module AttributionControls = + let private require condition message = if not condition then invalidOp message + + let private snapshot sequence frame = + let state = StateSnapshot.empty() + state.FrameNumber <- frame + let unit = OwnUnit.empty() + unit.UnitId <- 7u + unit.DefId <- 303u + unit.BuildProgress <- 1f + let position = Vector3.empty() + position.X <- 11f + position.Y <- 7f + position.Z <- 23f + unit.Position <- ValueSome position + state.OwnUnits.Add unit + let update = StateUpdate.empty() + update.Seq <- sequence + update.Frame <- frame + update.Snapshot <- state + update + + let private lifecycle sequence frame created = + let event = DeltaEvent.empty() + if created then + let value = UnitCreatedEvent.empty() + value.UnitId <- 8 + value.BuilderId <- 7 + event.UnitCreated <- value + else + let value = UnitFinishedEvent.empty() + value.UnitId <- 8 + event.UnitFinished <- value + let delta = StateDelta.empty() + delta.Events.Add event + let update = StateUpdate.empty() + update.Seq <- sequence + update.Frame <- frame + update.Delta <- delta + update + + let run () = task { + use lifetime = new CancellationTokenSource(TimeSpan.FromSeconds 25.) + let rows = ConcurrentQueue() + let subscriptions = ConcurrentBag() + let listenerObserver = + { new IObserver with + member _.OnNext listener = + if listener.Name = "FSBar.HighBar.AcceptedState" then + let observer = + { new IObserver> with + member _.OnNext value = + if value.Key = "AcceptedState" then + rows.Enqueue(unbox value.Value) + member _.OnError error = raise error + member _.OnCompleted() = () } + subscriptions.Add(listener.Subscribe observer) + member _.OnError error = raise error + member _.OnCompleted() = () } + use all = DiagnosticListener.AllListeners.Subscribe(listenerObserver) + let portSelection = new TcpListener(IPAddress.Loopback, 0) + portSelection.Start() + let port = (portSelection.LocalEndpoint :?> IPEndPoint).Port + portSelection.Stop() + let audit = ConcurrentQueue() + let! host = ServerHost.start { ServerHost.defaultOptions with listenAddress=sprintf "127.0.0.1:%d" port } (Version(1,0)) audit.Enqueue lifetime.Token + let waitFor name condition = task { + let stop = DateTimeOffset.UtcNow.AddSeconds 8. + while not(condition()) do + if DateTimeOffset.UtcNow > stop then invalidOp ("attribution control timeout: " + name) + do! System.Threading.Tasks.Task.Delay(10,lifetime.Token) + } + let row sequence frame = + rows.ToArray() |> Array.find(fun struct(_,value,_) -> value.Seq=sequence && value.Frame=frame) + try + use channel = GrpcChannel.ForAddress(sprintf "http://127.0.0.1:%d" port) + let producer = HighBarCoordinator.HighBarCoordinatorClient(channel) + let heartbeat name = + let request = HeartbeatRequest.empty() + request.PluginId <- name + request.SchemaVersion <- "1.0.0" + producer.HeartbeatAsync(request,cancellationToken=lifetime.Token).ResponseAsync + let! _ = heartbeat "attribution-old" + use oldPush = producer.PushStateAsync(cancellationToken=lifetime.Token) + do! oldPush.RequestStream.WriteAsync(snapshot 1UL 10u) + do! waitFor "initial snapshot" (fun () -> rows.Count=1) + let struct(generation,baseline,disposition) = row 1UL 10u + require (generation<>Guid.Empty && disposition="materialized" && baseline.Snapshot.OwnUnits.[0].UnitId=7u) "actual baseline/generation missing" + printfn "ATTRIBUTION_CONTROL_PASS baseline-generation" + + do! oldPush.RequestStream.WriteAsync(lifecycle 2UL 11u true) + do! waitFor "created" (fun () -> rows.Count=2) + let struct(createdGeneration,created,createdDisposition) = row 2UL 11u + require (createdGeneration=generation && createdDisposition="invalidated" && created.Delta.Events.[0].UnitCreated.BuilderId=7 && created.Delta.Events.[0].UnitCreated.UnitId=8) "creation provenance/invalidation changed" + require (audit.ToArray() |> Array.exists(function Audit.AuditEvent.CoordinatorStateInvalidated _ -> true | _ -> false)) "actual hub invalidation missing" + printfn "ATTRIBUTION_CONTROL_PASS creation-invalidates-with-builder" + + do! oldPush.RequestStream.WriteAsync(lifecycle 3UL 12u false) + do! waitFor "finished" (fun () -> rows.Count=3) + let struct(finishedGeneration,finished,finishedDisposition) = row 3UL 12u + require (finishedGeneration=generation && finishedDisposition="invalidated" && finished.Delta.Events.[0].UnitFinished.UnitId=8) "completion/invalidation changed" + printfn "ATTRIBUTION_CONTROL_PASS completion-retains-invalid-baseline" + + let replacement = snapshot 4UL 13u + let product = OwnUnit.empty() + product.UnitId <- 8u + product.DefId <- 304u + product.BuildProgress <- 1f + let productPosition = Vector3.empty() + productPosition.X <- 13f + productPosition.Y <- 7f + productPosition.Z <- 25f + product.Position <- ValueSome productPosition + replacement.Snapshot.OwnUnits.Add product + do! oldPush.RequestStream.WriteAsync replacement + do! waitFor "full replacement" (fun () -> rows.Count=4) + let struct(replacementGeneration,replaced,replacementDisposition) = row 4UL 13u + require (replacementGeneration=generation && replacementDisposition="materialized" && replaced.Snapshot.OwnUnits.Count=2) "full replacement missing" + let view,_ = WireConvert.applyHighBarStateUpdate baseline WireConvert.emptyRunningView + let invalid,_ = WireConvert.applyHighBarStateUpdate created view + require (not(WireConvert.hasValidBaseline invalid)) "unsupported creation falsely materialized" + let invalidFinished,_ = WireConvert.applyHighBarStateUpdate finished invalid + require (not(WireConvert.hasValidBaseline invalidFinished)) "finish falsely repairs baseline" + let repaired,_ = WireConvert.applyHighBarStateUpdate replaced invalidFinished + require (WireConvert.hasValidBaseline repaired) "complete replacement failed to repair" + printfn "ATTRIBUTION_CONTROL_PASS full-replacement-recovers" + + // Let the existing owning-generation watchdog detach this controlled stream. + do! waitFor "old generation detach" (fun () -> BrokerState.session host.Hub |> Option.isNone) + let! _ = heartbeat "attribution-new" + use newPush = producer.PushStateAsync(cancellationToken=lifetime.Token) + do! newPush.RequestStream.WriteAsync(snapshot 1UL 100u) + do! waitFor "new generation" (fun () -> rows.Count=5) + let struct(newGeneration,_,newDisposition) = row 1UL 100u + require (newGeneration<>generation && newDisposition="materialized") "replacement generation identity reused" + printfn "ATTRIBUTION_CONTROL_PASS new-generation-distinct" + + do! oldPush.RequestStream.WriteAsync(lifecycle 5UL 999u true) + do! System.Threading.Tasks.Task.Delay(100,lifetime.Token) + require (rows.Count=5) "stale stream emitted accepted attribution" + let tick = BrokerState.session host.Hub |> Option.bind(fun session -> (Session.toReading DateTimeOffset.UtcNow session).telemetry) |> Option.map _.tick + require (tick=Some 100L) "stale stream contaminated replacement" + printfn "ATTRIBUTION_CONTROL_PASS stale-generation-refused" + printfn "ATTRIBUTION_CONTROLS_COMPLETE passed=6 controlledProducer=true gameLaunched=false" + return 0 + finally + lifetime.Cancel() + (host :> IAsyncDisposable).DisposeAsync().AsTask().GetAwaiter().GetResult() + for subscription in subscriptions do subscription.Dispose() + } diff --git a/tests/Broker.NativeProof/Broker.NativeProof.fsproj b/tests/Broker.NativeProof/Broker.NativeProof.fsproj index cde018a..1116d11 100644 --- a/tests/Broker.NativeProof/Broker.NativeProof.fsproj +++ b/tests/Broker.NativeProof/Broker.NativeProof.fsproj @@ -8,6 +8,7 @@ + diff --git a/tests/Broker.NativeProof/LiveHost.fs b/tests/Broker.NativeProof/LiveHost.fs index f70efd6..3005d26 100644 --- a/tests/Broker.NativeProof/LiveHost.fs +++ b/tests/Broker.NativeProof/LiveHost.fs @@ -154,6 +154,65 @@ module LiveHost = trace.Flush();journalComplete<-true) let write (value: objnull) = writeKind "event" value if stock then writeKind "header" (box {|selectedCase="stock-smoke-count1"|}) + // Optional bounded raw attribution journal. It grants no commands, Arm or gameplay. + // This source requires fresh managed qualification; old assemblies cannot emit it. + use nativeAttributionSubscription = + match Environment.GetEnvironmentVariable("BARC_ONE_UNIT_RAW_EVENTS") |> Option.ofObj with + | None -> { new IDisposable with member _.Dispose() = () } + | Some path -> + if not stock || not(Path.IsPathFullyQualified path) || File.Exists path then invalidOp "new private stock attribution path required" + let stream = openStockJournal path + let journal = new StreamWriter(stream) + let gate = obj() + let mutable sequence = 0UL + let mutable totalBytes = 0 + let subscriptions = ResizeArray() + let append generation stateSequence frame disposition kind (value: objnull) = lock gate (fun () -> + sequence <- sequence + 1UL + let row = JsonSerializer.Serialize {|schema="fsbar.barc-one-unit-raw-state-journal/v1";runId=runId;sequence=string sequence;writer=writer;source=source;acceptedGeneration=string generation;stateSequence=string stateSequence;nativeFrame=frame;disposition=disposition;kind=kind;value=value|} + let bytes = System.Text.Encoding.UTF8.GetByteCount row + 1 + if sequence > 8192UL || bytes > 32768 || totalBytes > 4*1024*1024-bytes then + lifetime.Cancel() + invalidOp "native attribution journal bound exceeded" + totalBytes <- totalBytes + bytes + journal.WriteLine row + journal.Flush()) + let listenerObserver = { new IObserver with + member _.OnNext listener = + if listener.Name = "FSBar.HighBar.AcceptedState" then + let observer = { new IObserver> with + member _.OnNext diagnostic = + if diagnostic.Key = "AcceptedState" then + let struct(generation, update, disposition) = unbox diagnostic.Value + match update.Payload with + | ValueSome(Highbar.V1.StateUpdate.Types.Payload.Snapshot snapshot) -> + let units = snapshot.OwnUnits |> Seq.map(fun u -> {|unitId=u.UnitId;definitionId=u.DefId;underConstruction=u.UnderConstruction;buildProgress=u.BuildProgress|}) |> Seq.toArray + append generation update.Seq update.Frame disposition "snapshot" (box {|units=units|}) + | ValueSome(Highbar.V1.StateUpdate.Types.Payload.Delta delta) -> + for event in delta.Events do + match event.Kind with + | ValueSome(Highbar.V1.DeltaEvent.Types.Kind.UnitCreated created) -> + append generation update.Seq update.Frame disposition "unit-created" (box {|unitId=created.UnitId;builderId=created.BuilderId|}) + | ValueSome(Highbar.V1.DeltaEvent.Types.Kind.UnitFinished finished) -> + append generation update.Seq update.Frame disposition "unit-finished" (box {|unitId=finished.UnitId|}) + | ValueSome(Highbar.V1.DeltaEvent.Types.Kind.CommandDispatch dispatch) -> + append generation update.Seq update.Frame disposition "command-dispatch" (box {|batchSequence=string dispatch.BatchSeq;correlationId=string dispatch.ClientCommandId;commandIndex=dispatch.CommandIndex;unitId=dispatch.TargetUnitId;status=string dispatch.Status;dispatchFrame=dispatch.Frame;commandChannelIncarnation=dispatch.ChannelIncarnation|}) + | _ -> () + | _ -> () + member _.OnError _ = lifetime.Cancel() + member _.OnCompleted() = () } + lock gate (fun () -> subscriptions.Add(listener.Subscribe observer)) + member _.OnError _ = lifetime.Cancel() + member _.OnCompleted() = () } + let all = System.Diagnostics.DiagnosticListener.AllListeners.Subscribe listenerObserver + { new IDisposable with + member _.Dispose() = + all.Dispose() + lock gate (fun () -> + for subscription in subscriptions do subscription.Dispose() + journal.Flush() + stream.Flush(true) + journal.Dispose()) } let! host = ServerHost.start { ServerHost.defaultOptions with listenAddress=grpcAddress } (Version(1,0)) ignore lifetime.Token try let lobby: Lobby.LobbyConfig = diff --git a/tests/Broker.NativeProof/Program.fs b/tests/Broker.NativeProof/Program.fs index f4f1064..469e238 100644 --- a/tests/Broker.NativeProof/Program.fs +++ b/tests/Broker.NativeProof/Program.fs @@ -231,6 +231,7 @@ module Program = try (if argv.Length > 0 && argv[0] = "--live-host" then LiveHost.run "barc-live-v1" argv[1..] elif argv.Length > 0 && argv[0] = "--tactical-live-host" then LiveHost.run "barc-live-tactical-v1" argv[1..] + elif argv = [| "--attribution-controls" |] then AttributionControls.run () elif argv.Length > 0 && argv[0] = "--stock-tactical-live-host" then LiveHost.run "barc-live-tactical-stock-v1" argv[1..] else run listenAddress) |> fun task -> task.GetAwaiter().GetResult() with ex -> diff --git a/tests/Broker.NativeProof/tactical-native-journey.spec.js b/tests/Broker.NativeProof/tactical-native-journey.spec.js index 1569e93..db0da3e 100644 --- a/tests/Broker.NativeProof/tactical-native-journey.spec.js +++ b/tests/Broker.NativeProof/tactical-native-journey.spec.js @@ -6,10 +6,13 @@ import { pathToFileURL } from "node:url"; import { basename, dirname, resolve, parse, join } from "node:path"; import { assertCanonicalLifecycle, assertObservedEffects, isExactStaleBasisRefusal, isFreshPhysicalReplacement, loadStockSmokeHandoff, loadTacticalHandoff, plannedChildCount, sanitizedLifecycle, waitForTerminalOrClose } from "./tactical-native-evidence.mjs"; -const enabled=process.env.BARC_RUN_TACTICAL_NATIVE==="1",stockEnabled=process.env.BARC_RUN_STOCK_NATIVE_SMOKE==="1";if(enabled&&stockEnabled)throw new Error("full-six and selected stock smoke routes are mutually exclusive"); +import { parseClosedJson, parseStockTraceJsonl } from "./stock-native-trace.mjs"; + +const enabled=process.env.BARC_RUN_TACTICAL_NATIVE==="1",stockEnabled=process.env.BARC_RUN_STOCK_NATIVE_SMOKE==="1",oneUnitEnabled=process.env.BARC_RUN_ONE_PRODUCED_UNIT==="1";if([enabled,stockEnabled,oneUnitEnabled].filter(Boolean).length>1)throw new Error("native proof routes are mutually exclusive"); const handoff=enabled?await loadTacticalHandoff(process.env.BARC_TACTICAL_HANDOFF):null,stockHandoff=stockEnabled?await loadStockSmokeHandoff(process.env.BARC_STOCK_SMOKE_HANDOFF):null; let stage,codec; -test.beforeAll(async()=>{if(!enabled&&!stockEnabled)return;stage=await mkdtemp(resolve(import.meta.dirname,"node_modules/.barc-tactical-codec-"));await cp(enabled?handoff.codecDirectory:stockHandoff.paths.codecDirectory,stage,{recursive:true});codec=await import(pathToFileURL(resolve(stage,"codec.js")).href)}); +const oneUnitHandoff=oneUnitEnabled?loadOneUnitHandoff(process.env.BARC_ONE_UNIT_HANDOFF,process.env.BARC_ONE_UNIT_HANDOFF_SHA256):null; +test.beforeAll(async()=>{if(!enabled&&!stockEnabled&&!oneUnitEnabled)return;stage=await mkdtemp(resolve(import.meta.dirname,"node_modules/.barc-tactical-codec-"));await cp(enabled?handoff.codecDirectory:(oneUnitEnabled?oneUnitHandoff.paths.codecDirectory:stockHandoff.paths.codecDirectory),stage,{recursive:true});codec=await import(pathToFileURL(resolve(stage,"codec.js")).href)}); test.afterAll(async()=>{if(stage)await rm(stage,{recursive:true,force:true})}); const key=ref=>`${ref.id}:${ref.lifetime}`, sameRef=(a,b)=>String(a?.id??0)===b.id&&String(a?.lifetime??0)===b.lifetime; @@ -22,10 +25,10 @@ const queue=(o,ref,domain)=>actor(o,ref)?.queue?.find(x=>x.domain===domain); const near=(a,b,t=64)=>a&&Math.hypot(a.x-b.x,a.z-b.z)<=t; const startTicks=()=>{const value=readFileSync(`/proc/${process.pid}/stat`,"utf8"),close=value.lastIndexOf(") "),fields=value.slice(close+2).trim().split(/ +/);if(close<0||fields.length<=19)throw new Error("browser writer start identity unavailable");return fields[19]}; -function captureJournal(path,release,runId,source){ +function captureJournal(path,release,runId,source,selectedCase="stock-smoke-count1"){ if(!path||!release||existsSync(path)||existsSync(release))throw new Error("new stock capture/release paths required");const sourcePath=import.meta.filename,writer={pid:process.pid,startTicks:startTicks(),uid:process.geteuid(),executable:process.execPath,sourcePath,sourceSha256:createHash("sha256").update(readFileSync(sourcePath)).digest("hex")},fd=openSync(path,constants.O_WRONLY|constants.O_CREAT|constants.O_EXCL|constants.O_APPEND|constants.O_CLOEXEC,0o600);let sequence=0,closed=false; - const append=(kind,value)=>{if(closed)throw new Error("stock capture already completed");sequence++;writeSync(fd,`${JSON.stringify({schema:"fsbar.barc-stock-browser-capture/v1",runId,sequence:String(sequence),kind,writer,source,value})}\n`)};append("header",{selectedCase:"stock-smoke-count1"}); - return{append,complete(){append("complete",{selectedCase:"stock-smoke-count1"});fsyncSync(fd);closed=true},async hold(){await expect.poll(()=>existsSync(release),{timeout:30000,intervals:[25,50,100,250]}).toBe(true)},close(){closeSync(fd)}}; + const append=(kind,value)=>{if(closed)throw new Error("stock capture already completed");sequence++;writeSync(fd,`${JSON.stringify({schema:"fsbar.barc-stock-browser-capture/v1",runId,sequence:String(sequence),kind,writer,source,value})}\n`)};append("header",{selectedCase}); + return{append,complete(){append("complete",{selectedCase});fsyncSync(fd);closed=true},async hold(){await expect.poll(()=>existsSync(release),{timeout:30000,intervals:[25,50,100,250]}).toBe(true)},close(){closeSync(fd)}}; } // Readiness controls execute this exact source block without loading native handoffs. const pairingState=()=>({observed:false,authSentCount:0,bootstrapDecodedCount:0,decodeFailureCount:0,clientCloseCount:0,closed:false,error:false,overflow:false}); @@ -100,6 +103,7 @@ async function pair(page,j,guest="Manual guest",rows){ await page.goto(j.receiverUrl);await live.getByLabel("Gateway").fill(j.gatewayUrl);await live.getByLabel("Session UUID").fill(j.expectedSessionId);await live.getByLabel("One-time credential").fill(j.credential);await live.getByRole("button",{name:"Pair"}).click(); await expect(live.locator(".catalogue")).not.toContainText("unavailable");await expect(live.locator(".economy")).toContainText("Economy"); await loadSelectedGuest(page,live,j,guest,state); + if(j.deferArm)return live; state.phase="arm-clicked";await live.getByRole("button",{name:"Arm live"}).click(); await expect.poll(()=>state.armSha256).toBe(state.moduleSha256);await expect(live.locator(".authority")).toContainText("arm native confirmed");state.phase="native-confirmed";return live; }catch(error){pairingFailed=true;state.failureCode??=state.phase==="arm-clicked"?"authority-not-confirmed":"pair-or-module-not-ready";await writeArmFailure(live,j,state);throw error} @@ -188,3 +192,84 @@ test.describe("selected stock Count1 smoke",()=>{ }finally{journal.close()} }); }); + + +// ONE_UNIT_SOURCE_BEGIN: dedicated proof, never a smoke or full-six alias. +const oneUnitCase="selected stock one-unit"; +const insistOne=(condition,message)=>{if(!condition)throw new Error(`one-unit refused: ${message}`)}; +const closedOne=(value,keys,label)=>insistOne(value&&typeof value==="object"&&!Array.isArray(value)&&JSON.stringify(Object.keys(value).sort())===JSON.stringify([...keys].sort()),`${label} keys changed`); +function loadOneUnitHandoff(path,sha256){ + insistOne(typeof path==="string"&&path.startsWith("/")&&/^[a-f0-9]{64}$/.test(sha256??""),"pinned handoff required"); + const st=lstatSync(path),bytes=readFileSync(path);insistOne(st.isFile()&&!st.isSymbolicLink()&&st.nlink===1&&(st.mode&0o777)===0o600&&bytes.length<=65536&&createHash("sha256").update(bytes).digest("hex")===sha256,"handoff physical/body pin changed"); + const h=parseClosedJson(bytes.toString("utf8"));closedOne(h,["schema","runId","selectedCase","source","paths","connection","artifacts","actors","setup","attribution"],"handoff"); + insistOne(h.schema==="fsbar.barc-one-produced-unit-handoff/v1"&&h.selectedCase===oneUnitCase,"closed selected case required"); + closedOne(h.source,["fsbarCommit","highbarCommit"],"source");insistOne(Object.values(h.source).every(x=>/^[a-f0-9]{40}$/.test(x)),"exact source commits required"); + closedOne(h.connection,["receiverUrl","gatewayUrl","allowedOrigin","sessionId","credential"],"connection");const receiver=new URL(h.connection.receiverUrl),gateway=new URL(h.connection.gatewayUrl),origin=new URL(h.connection.allowedOrigin);insistOne(receiver.protocol==="http:"&&origin.protocol==="http:"&&gateway.protocol==="ws:"&&[receiver,origin,gateway].every(u=>u.hostname==="127.0.0.1"&&!u.username&&!u.password&&!u.hash)&&receiver.origin===origin.origin&&/^[a-f0-9]{64}$/.test(h.connection.credential),"exact private loopback connection required"); + closedOne(h.artifacts,["guest"],"artifacts");closedOne(h.artifacts.guest,["path","sha256"],"guest");insistOne(/^[a-f0-9]{64}$/.test(h.artifacts.guest.sha256),"actual produced guest digest required"); + closedOne(h.paths,["codecDirectory","capture","release","nativeEvents","stockTrace","eventQualification"],"paths"); + insistOne(Object.values(h.paths).every(p=>typeof p==="string"&&p.startsWith("/")),"absolute capsule paths required"); + closedOne(h.attribution,["qualified","qualificationSha256","writer","acceptedGeneration","processIncarnation","stateChannelIncarnation"],"attribution"); + insistOne(h.attribution.qualified===true&&/^[a-f0-9]{64}$/.test(h.attribution.qualificationSha256),"actual event-source qualification missing"); + const qualification=readFileSync(h.paths.eventQualification);insistOne(qualification.length<=65536&&createHash("sha256").update(qualification).digest("hex")===h.attribution.qualificationSha256,"event-source qualification body changed"); + const qualified=parseClosedJson(qualification.toString("utf8"));insistOne(qualified.schema==="bar.one-unit-attribution-source-qualification/v1"&&qualified.actualPassed===true&&qualified.acceptedGeneration===h.attribution.acceptedGeneration&&qualified.noAutonomousOrders===true&&qualified.runId===h.runId&&JSON.stringify(qualified.source)===JSON.stringify(h.source)&&JSON.stringify(qualified.writer)===JSON.stringify(h.attribution.writer)&&qualified.processIncarnation===h.attribution.processIncarnation&&qualified.stateChannelIncarnation===h.attribution.stateChannelIncarnation&&/^[a-f0-9]{64}$/.test(qualified.noAutonomousConfigurationEvidenceSha256??""),"genuine qualified run/source/writer/generation/no-autonomous configuration missing"); + closedOne(h.actors,["factory"],"actors");closedOne(h.setup,["productDefinitionId","seedDefinitionId","maximumSeconds"],"setup"); + insistOne(h.setup.maximumSeconds===180&&Number.isInteger(h.setup.productDefinitionId)&&h.setup.productDefinitionId>0&&Number.isInteger(h.setup.seedDefinitionId)&&h.setup.seedDefinitionId>0&&h.setup.seedDefinitionId!==h.setup.productDefinitionId,"finite distinct seed/product setup required"); + // Missing capture-source or native feed refuses before browser pairing/Arm. + insistOne(existsSync(h.paths.nativeEvents),"genuine native event feed missing");return h; +} +function nativeOneEvents(h,rows,before){ + const st=lstatSync(h.paths.nativeEvents);insistOne(st.isFile()&&!st.isSymbolicLink()&&st.nlink===1&&st.size<=4*1024*1024,"native event stream physical bound changed"); + const text=readFileSync(h.paths.nativeEvents,"utf8"),lines=text.split("\n");lines.pop();insistOne(lines.length>0&&lines.length<=8192,"native event row bound changed"); + const raw=lines.map((line,index)=>{insistOne(Buffer.byteLength(line)<=32768,"native event record oversized");const r=parseClosedJson(line);closedOne(r,["schema","runId","sequence","kind","writer","source","acceptedGeneration","stateSequence","nativeFrame","disposition","value"],"native raw row");insistOne(r.schema==="fsbar.barc-one-unit-raw-state-journal/v1"&&r.runId===h.runId&&r.sequence===String(index+1)&&JSON.stringify(r.writer)===JSON.stringify(h.attribution.writer)&&JSON.stringify(r.source)===JSON.stringify(h.source)&&r.acceptedGeneration===h.attribution.acceptedGeneration,"native raw origin/generation/sequence changed");insistOne(["snapshot","unit-created","unit-finished","command-dispatch"].includes(r.kind)&&["materialized","invalidated","gap","liveness-only"].includes(r.disposition)&&/^[0-9]+$/.test(r.stateSequence)&&Number.isSafeInteger(r.nativeFrame)&&r.nativeFrame>=0,"unknown native event or basis");return r}); + // Freeze the pre-Arm baseline by its actual browser/native state position. + const snapshot=raw.find(r=>r.kind==="snapshot"&&r.disposition==="materialized"&&r.stateSequence===before.basis.stateSequence&&r.nativeFrame===Number(before.basis.nativeFrame));insistOne(snapshot,"actual same-basis baseline snapshot missing"); + insistOne(snapshot.value.units.length>0&&snapshot.value.units.every(u=>u.underConstruction===false&&u.buildProgress===1),"native baseline contains unfinished units"); + const result=raw.filter(r=>BigInt(r.sequence)>BigInt(snapshot.sequence));result.unshift({...snapshot,kind:"baseline"}); + const stockStat=lstatSync(h.paths.stockTrace);insistOne(stockStat.isFile()&&!stockStat.isSymbolicLink()&&stockStat.nlink===1&&stockStat.size<=16*1024*1024,"stock reader physical bound changed");let stockText=readFileSync(h.paths.stockTrace,"utf8");stockText=stockText.slice(0,stockText.lastIndexOf("\n")+1);const stock=parseStockTraceJsonl(stockText); + for(const r of stock){if(r.reader.status!=="complete")continue;insistOne(r.runId===h.runId&&sameRef(r.context.actor,h.actors.factory)&&r.nativeBasis.processIncarnation===h.attribution.processIncarnation&&r.nativeBasis.stateChannelIncarnation===h.attribution.stateChannelIncarnation,"stock queue origin/basis changed"); + const o=observations(rows).find(o=>o.basis.stateSequence===r.nativeBasis.stateSequence&&o.basis.processIncarnation===r.nativeBasis.processIncarnation&&o.basis.stateChannelIncarnation===r.nativeBasis.stateChannelIncarnation&&o.basis.matchId===r.nativeBasis.matchIncarnation&&Number(o.basis.nativeFrame)===r.nativeBasis.frame),q=queue(o,h.actors.factory,"QUEUE_DOMAIN_FACTORY_PRODUCTION"); + if(r.context.domain!=="production"||q?.complete!==true||q.repeat!==false||String(q.revision)!==r.queue.revision||q.entries?.length!==r.queue.entries.length)continue; + result.push({...snapshot,kind:r.phase==="final-read"?"final-read":"queue-observation",sequence:r.sequence,stateSequence:r.nativeBasis.stateSequence,nativeFrame:r.readFrame,value:{actor:r.context.actor,nativeBasis:r.nativeBasis,catalogueId:r.context.catalogueId,catalogueRevision:r.context.catalogueRevision,queueRevision:r.queue.revision,complete:true,repeat:false,entries:r.queue.entries.map(e=>({definitionId:-e.id,codedOptions:e.codedOptions,tag:e.tag,float32params:e.float32params})),dispatch:r.dispatch}}); + }return result; +} +export function assertOneProducedUnit(rows,before,native,h){ + const submits=rows.filter(r=>r.kind==="submit").map(r=>r.value);insistOne(submits.length===1,"exactly one submission required, no retry");const s=submits[0],f=h.actors.factory; + insistOne(s.intent?.action==="factoryProduce"&&s.intent.actors?.length===1&&sameRef(s.intent.actors[0],f)&&s.intent.factoryProduce?.count===1&&s.intent.factoryProduce.queuePolicy==="TACTICAL_QUEUE_POLICY_APPEND"&&Number(s.intent.factoryProduce.definitionId)===h.setup.productDefinitionId,"one actor Append Count1 definition changed"); + insistOne(plannedChildCount(s.intent)===1&&s.parentId&&s.inputId&&s.module?.sha256&&s.controller&&s.basis,"parent/input/module/controller/basis missing");insistOne(Buffer.from(s.module.sha256,"base64").toString("hex")===h.artifacts.guest.sha256,"submitted module differs from actual produced guest"); + const result=rows.filter(r=>r.kind==="result").map(r=>r.value);insistOne(result.every(r=>r.parentId===s.parentId&&r.childCount===1&&(r.childIndex??0)===0&&sameRef(r.actor,f)&&r.inputId===s.inputId&&JSON.stringify(r.module)===JSON.stringify(s.module)&&JSON.stringify(r.controller)===JSON.stringify(s.controller)&&JSON.stringify(r.basis)===JSON.stringify(s.basis)),"foreign parent/input/module/controller/basis/child lifecycle"); + for(const [stage,status]of[["LIVE_RESULT_STAGE_BROKER_ADMISSION","LIVE_RESULT_STATUS_ACCEPTED"],["LIVE_RESULT_STAGE_NATIVE_ADMISSION","LIVE_RESULT_STATUS_ACCEPTED"],["LIVE_RESULT_STAGE_NATIVE_DISPATCH","LIVE_RESULT_STATUS_APPLIED"]])insistOne(result.filter(r=>r.stage===stage&&r.status===status).length===1,`unique ${stage} result missing`); + insistOne(result.length===3,"extra lifecycle result");const dispatch=result.find(r=>r.stage==="LIVE_RESULT_STAGE_NATIVE_DISPATCH");insistOne(Number.isInteger(dispatch.nativeFrame),"actual dispatch frame missing"); + insistOne(before.basis.processIncarnation===h.attribution.processIncarnation&&before.basis.stateChannelIncarnation===h.attribution.stateChannelIncarnation&&before.basis.matchId===s.basis.matchId,"baseline/session incarnation changed"); + const baseline=native[0];insistOne(baseline?.kind==="baseline"&&baseline.stateSequence===before.basis.stateSequence,"frozen native baseline missing"); + const initial=queue(before,f,"QUEUE_DOMAIN_FACTORY_PRODUCTION");insistOne(initial?.complete&&initial.repeat===false&&initial.entries?.length===0,"empty complete repeat-off baseline missing"); + const observed=observations(rows).filter(o=>BigInt(o.basis.stateSequence)>BigInt(s.basis.stateSequence)&&Number(o.basis.nativeFrame)>dispatch.nativeFrame&&o.basis.processIncarnation===s.basis.processIncarnation&&o.basis.stateChannelIncarnation===s.basis.stateChannelIncarnation&&o.basis.matchId===s.basis.matchId); + const known=new Set(before.units.map(u=>key(u.reference))),created=native.filter(r=>r.kind==="unit-created"&&r.nativeFrame>=dispatch.nativeFrame&&BigInt(r.stateSequence)>BigInt(s.basis.stateSequence)),finished=native.filter(r=>r.kind==="unit-finished"&&r.nativeFrame>=dispatch.nativeFrame&&BigInt(r.stateSequence)>BigInt(s.basis.stateSequence)); + insistOne(created.every(r=>r.disposition==="invalidated")&&finished.every(r=>r.disposition==="invalidated"),"unit lifecycle delta did not retain unsupported-event invalidation"); + const nativeDispatch=native.filter(r=>r.kind==="command-dispatch");insistOne(nativeDispatch.length===1&&String(nativeDispatch[0].value.batchSequence)===String(dispatch.batchSequence)&&String(nativeDispatch[0].value.correlationId)===String(dispatch.correlationId)&&String(nativeDispatch[0].value.unitId)===f.id&&nativeDispatch[0].value.commandChannelIncarnation===dispatch.commandChannelIncarnation&&nativeDispatch[0].value.status==="CommandDispatchApplied"&&nativeDispatch[0].value.dispatchFrame===dispatch.nativeFrame,"unique genuine dispatch event missing or extra native dispatch"); + insistOne(created.length===1&&finished.length===1&&String(created[0].value.builderId)===f.id&&String(created[0].value.unitId)===String(finished[0].value.unitId)&&BigInt(finished[0].sequence)>BigInt(created[0].sequence)&&finished[0].nativeFrame>=created[0].nativeFrame,"unique genuine builder creation and finished events missing"); + const queued=native.filter(r=>r.kind==="queue-observation"&&r.nativeFrame>=dispatch.nativeFrame&&sameRef(r.value.actor,f)&&r.value.complete===true&&r.value.repeat===false&&r.value.entries?.length===1&&Number(r.value.entries[0].definitionId)===h.setup.productDefinitionId&&r.value.entries[0].codedOptions===0&&r.value.entries[0].float32params?.length===0); + insistOne(queued.length>0,"exact native queue entry with options 0 missing"); + const later=observed.filter(o=>Number(o.basis.nativeFrame)>finished[0].nativeFrame&&queue(o,f,"QUEUE_DOMAIN_FACTORY_PRODUCTION")?.complete===true&&queue(o,f,"QUEUE_DOMAIN_FACTORY_PRODUCTION")?.repeat===false&&queue(o,f,"QUEUE_DOMAIN_FACTORY_PRODUCTION")?.entries?.length===0); + insistOne(later.length>=2&&BigInt(later.at(-1).basis.stateSequence)>BigInt(later.at(-2).basis.stateSequence),"two later empty repeat-off snapshots missing"); + for(const o of later){insistOne(native.some(r=>r.kind==="snapshot"&&r.disposition==="materialized"&&r.stateSequence===o.basis.stateSequence&&r.nativeFrame===Number(o.basis.nativeFrame)&&BigInt(r.sequence)>BigInt(finished[0].sequence)),"later browser state lacks complete native replacement after invalidation");const fresh=o.units.filter(u=>!known.has(key(u.reference)));insistOne(fresh.length===1&&String(fresh[0].reference.id)===String(created[0].value.unitId)&&Number(unit(o,fresh[0].reference)?.definitionId)===h.setup.productDefinitionId,"exactly one new product lifetime missing or extra production");} + const final=native.filter(r=>r.kind==="final-read"&&r.nativeFrame<=dispatch.nativeFrame&&sameRef(r.value.actor,f)&&r.value.complete===true&&r.value.repeat===false&&r.value.entries?.length===0); + insistOne(final.length===1,"one genuine predispatch final native fence missing");const d=final[0].value.dispatch;insistOne(d?.matched===true&&String(d.batchSequence)===String(dispatch.batchSequence)&&String(d.correlationId)===String(dispatch.correlationId)&&d.moduleSha256===h.artifacts.guest.sha256&&String(d.moduleGeneration)===String(s.module.generation)&&d.brokerSessionId===s.controller.sessionId&&d.commandChannelIncarnation===dispatch.commandChannelIncarnation&&d.commandIndex===nativeDispatch[0].value.commandIndex&&String(d.expectedQueueRevision)===String(s.intent.actorTacticalBindings?.find(b=>sameRef(b.actor,f))?.queueRevisions?.find(q=>q.domain==="QUEUE_DOMAIN_FACTORY_PRODUCTION")?.revision)&&final[0].value.nativeBasis.stateSequence===s.basis.stateSequence&&final[0].value.nativeBasis.token===s.basis.token&&final[0].value.nativeBasis.matchIncarnation===s.basis.matchId&&final[0].value.catalogueId===s.intent.factoryProduce.catalogueId&&String(final[0].value.catalogueRevision)===String(s.intent.factoryProduce.catalogueRevision)&&String(d.authorityEpoch)===String(s.controller.authorityEpoch),"final native dispatch/module/authority/catalogue/queue-basis join missing"); + const terminal=native.filter(r=>r.kind==="queue-observation"&&r.nativeFrame>=Number(later.at(-1).basis.nativeFrame)&&sameRef(r.value.actor,f)&&r.value.complete===true&&r.value.repeat===false&&r.value.entries?.length===0);insistOne(terminal.length>0,"later terminal native queue read missing");return{schema:"fsbar.barc-one-produced-unit-evidence/v1",runId:h.runId,parentId:s.parentId,product:later.at(-1).units.find(u=>!known.has(key(u.reference))).reference,builder:f,createdSequence:created[0].sequence,finishedSequence:finished[0].sequence,finalReadSequence:final[0].sequence,terminalReadSequence:terminal.at(-1).sequence,unitProduced:true,fullSix:false,nativeUsefulPlay:"0/6"}; +} +test.describe(oneUnitCase,()=>{ + test.skip(!oneUnitEnabled,"requires root-admitted checked one-unit capsule and genuine native attribution feed"); + test("one Append Count1 completes exactly one factory-attributed unit",async({page})=>{ + test.setTimeout(180000);const h=oneUnitHandoff,j={receiverUrl:h.connection.receiverUrl,gatewayUrl:h.connection.gatewayUrl,allowedOrigin:h.connection.allowedOrigin,expectedSessionId:h.connection.sessionId,credential:h.connection.credential,bundledGuestSha256:h.artifacts.guest.sha256,outputPath:h.paths.capture,armFailurePath:`${h.paths.capture}.arm-failure.json`,deferArm:true},journal=captureJournal(h.paths.capture,h.paths.release,h.runId,h.source,oneUnitCase); + try{const rows=await capture(page,journal),live=await pair(page,j,"Manual guest",rows),f=h.actors.factory;await select(page,live,"pointer",[f]); + const seeded=queue(latest(rows),f,"QUEUE_DOMAIN_FACTORY_PRODUCTION");insistOne(seeded?.complete===true&&seeded.repeat===false&&seeded.entries?.length>0&&Number(seeded.entries[0].definitionId)===h.setup.seedDefinitionId,"nonempty distinct seed readiness missing"); + await expect.poll(()=>{const q=queue(latest(rows),f,"QUEUE_DOMAIN_FACTORY_PRODUCTION");return q?.complete===true&&q.repeat===false&&q.entries?.length===0},{timeout:60000}).toBe(true); + const before=structuredClone(latest(rows));insistOne(rows.filter(r=>r.kind==="submit").length===0,"autonomous/baseline submit"); + nativeOneEvents(h,rows,before); // The pinned actual source qualification must separately prove autonomous decisions disabled. + journal.append("one-unit-baseline",before);rows.readiness.phase="arm-clicked";await live.getByRole("button",{name:"Arm live"}).click();await expect.poll(()=>rows.readiness.armSha256).toBe(rows.readiness.moduleSha256);await expect(live.locator(".authority")).toContainText("arm native confirmed");insistOne(rows.readiness.armCount===1,"exactly one Arm required"); + await live.getByLabel("Action").selectOption("factoryProduce");await live.getByLabel("Definition").selectOption(String(h.setup.productDefinitionId));await live.getByLabel("Count").fill("1");await live.getByLabel("Queue policy").selectOption("TACTICAL_QUEUE_POLICY_APPEND");await physicalSubmit(page,live,"pointer");await expect.poll(()=>rows.filter(r=>r.kind==="submit").length,{timeout:5000}).toBe(1);const submit=rows.find(r=>r.kind==="submit").value;await settleSubmit(rows,submit,"applied"); + // No stale-basis replacement: this route submits once or refuses. + let proof;await expect.poll(()=>{try{proof=assertOneProducedUnit(rows,before,nativeOneEvents(h,rows,before),h);return true}catch{return false}},{timeout:90000,intervals:[25,50,100,250]}).toBe(true);journal.append("one-unit-proof",proof);journal.complete();await journal.hold(); + }finally{journal.close()} + }); +}); +// ONE_UNIT_SOURCE_END From 361f5060aea0e340fb8a33da96a91fd234e7fc39 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 20:43:32 +0200 Subject: [PATCH 04/10] Require qualified seed history and fresh one-unit pre-Arm baseline --- docs/roadmaps/barc-01-useful-play.md | 28 +++++++ .../one-unit-prearm.test.mjs | 48 ++++++++++++ .../tactical-native-journey.spec.js | 77 +++++++++++++++---- 3 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 tests/Broker.NativeProof/one-unit-prearm.test.mjs diff --git a/docs/roadmaps/barc-01-useful-play.md b/docs/roadmaps/barc-01-useful-play.md index 20d6c19..052d0f4 100644 --- a/docs/roadmaps/barc-01-useful-play.md +++ b/docs/roadmaps/barc-01-useful-play.md @@ -460,3 +460,31 @@ operation. Predispatch final-read remains a fence; actual creation/finish and a separate later terminal native sample must prove exactly one product. Full six useful-play journeys, publication and installed adoption remain open. Native usage coverage remains unknown where the observer rejected canonical native identity. + +### BARC-01.5f pre-Arm producer source amendment (2026-10-04) + +The six preparation phases are source-qualified through the retained v5 managed +build/attribution controls, v6 compiler/PE/PDB proof and v9 installed capsule +qualification. Their earlier failed overall attempts remain failed. The qualified +managed bodies remain unchanged; this successor changes the one-unit JS consumer +and prepares a private read-only pre-Arm qualifier. Its controlled Python checks +prove source predicates only. Changed JS controls and installed list discovery +still require separate root admission; old discovery qualifies its original bytes. + +Qualification must retain an actual earlier finite nonempty seed queue, native +factory creation/finish, unsupported-event invalidation and later complete +replacement/empty repeat-off evidence. Browser readiness then freezes a fresh +same-run/generation empty baseline containing those finished seed lifetimes and +rechecks source/configuration/PID custody before its single Arm and Append Count1. +This removes the prior nonempty-seed/browser timing contradiction. Missing real +basis/lifetime joins, extra production/dispatch, drift or expiry refuse; no fixture +boolean, extra seed, stale-basis retry or receipt rewrite supplies acceptance. + +The existing canonical nineteen-role PolicyClosure and full dependency/runtime +census are unchanged. Final product/helper graph composition remains blocked on a +qualified genuine successor provenance producer; old build receipts cannot be +relabeled for this candidate. Root owns the finite startup, transition and later +one-shot browser admissions, effect-specific authority checks and PR/integration. +No native startup, browser, actor or production operation was admitted here. +**actorEligible=false; unitProducedAccepted=false; useful play 0/6.** BARC-01.5f +and the original six useful-play journeys remain open. diff --git a/tests/Broker.NativeProof/one-unit-prearm.test.mjs b/tests/Broker.NativeProof/one-unit-prearm.test.mjs new file mode 100644 index 0000000..7c17eac --- /dev/null +++ b/tests/Broker.NativeProof/one-unit-prearm.test.mjs @@ -0,0 +1,48 @@ +// Controlled source checks only. No native handoff, browser, Arm or production effect. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import vm from "node:vm"; +const source=readFileSync(new URL("./tactical-native-journey.spec.js",import.meta.url),"utf8"); +const start=source.indexOf("// ONE_UNIT_SOURCE_BEGIN"),end=source.indexOf("if(oneUnitEnabled)oneUnitHandoff=",start); +assert.ok(start>=0&&end>start); +const context=vm.createContext({Buffer,Set,BigInt,Number,Object,JSON,Error,Array,String, + key:r=>`${r.id}:${r.lifetime}`,sameRef:(a,b)=>a?.id===b?.id&&a?.lifetime===b?.lifetime, + queue:(o,f)=>o?.productionQueue}); +vm.runInContext(source.slice(start,end).replace("export function assertOneProducedUnit","function assertOneProducedUnit")+"\n globalThis.api={validateOneQualification,assertOnePreArm,assertOneProducedUnit};",context); +const {api}=context; +function fixture(){ + const factory={id:"10",lifetime:"5"},seed={id:"11",lifetime:"6"},writer={pid:99,startTicks:"123",uid:1000},source={fsbarCommit:"a".repeat(40),highbarCommit:"b".repeat(40)}; + const h={runId:"controlled",source,actors:{factory},setup:{seedDefinitionId:2,productDefinitionId:3},paths:{nativeEvents:"/controlled/raw",stockTrace:"/controlled/stock"},attribution:{writer,acceptedGeneration:"G",processIncarnation:"P",stateChannelIncarnation:"C"}}; + const pin=path=>({path,sha256:"c".repeat(64),bytes:1,device:1,inode:1,uid:1000,mode:0o600,nlink:1}); + const q={schema:"bar.one-unit-attribution-source-qualification/v1",actualPassed:true,noAutonomousOrders:true,runId:h.runId,source,writer,acceptedGeneration:"G",processIncarnation:"P",stateChannelIncarnation:"C",matchIncarnation:"M",noAutonomousConfigurationEvidenceSha256:"c".repeat(64),seedEvidence:{factory,seedDefinitionId:2,productDefinitionId:3,seedCount:1,initialRawSequence:"1",nonemptyStockSequence:"1",terminalRawSequence:"4",terminalStateSequence:"8",terminalNativeFrame:40,emptyStockSequence:"2",products:[{reference:seed,createdSequence:"2",finishedSequence:"3"}]},inputCustody:{raw:pin("/controlled/raw"),host:pin("/controlled/host"),stock:pin("/controlled/stock"),metadata:pin("/controlled/metadata"),setup:pin("/controlled/setup"),configuration:pin("/controlled/config")},liveProcesses:[{...writer,role:"host"}],completeRecordEvidence:pin("/controlled/policy"),lifecycle:{startedMonotonicNs:"1",transitionDeadlineMonotonicNs:"1000000000",totalDeadlineMonotonicNs:"2000000000"}}; + const before={basis:{processIncarnation:"P",stateChannelIncarnation:"C",matchId:"M",stateSequence:"10",nativeFrame:50},units:[{reference:factory,definitionId:1},{reference:seed,definitionId:2}],productionQueue:{complete:true,repeat:false,entries:[]}}; + const native=[{kind:"baseline",stateSequence:"10",value:{units:[{unitId:10,underConstruction:false,buildProgress:1},{unitId:11,underConstruction:false,buildProgress:1}]}}]; + return {h,q,before,native,rows:[]}; +} +test("retained earlier nonempty history permits a fresh current empty baseline",()=>{ + const f=fixture();assert.equal(api.assertOnePreArm(f.h,f.q,f.rows,f.before,f.native),f.before); +}); +test("qualification tampering and foreign owning identity refuse before Arm",()=>{ + const changes=[f=>f.q.actualPassed=false,f=>f.q.noAutonomousOrders=false,f=>f.q.runId="foreign",f=>f.q.acceptedGeneration="foreign",f=>f.q.stateChannelIncarnation="foreign",f=>f.q.source={...f.q.source,fsbarCommit:"d".repeat(40)},f=>f.q.seedEvidence.finishedSequence="0",f=>f.q.seedEvidence.products[0].finishedSequence="2",f=>f.q.seedEvidence.seedCount=2,f=>f.q.seedEvidence.emptyStockSequence="1",f=>f.q.inputCustody.raw.path="/other",f=>f.q.liveProcesses[0].startTicks="999",f=>f.q.extra=true]; + for(const change of changes){const f=fixture();change(f);let armCount=0;assert.throws(()=>{api.assertOnePreArm(f.h,f.q,f.rows,f.before,f.native);armCount++},/one-unit refused/);assert.equal(armCount,0)} +}); +test("current stale/nonempty/unfinished baseline and later dispatch refuse",()=>{ + const changes=[f=>f.before.basis.stateSequence="6",f=>f.before.basis.stateChannelIncarnation="other",f=>f.before.productionQueue.entries=[{definitionId:2}],f=>f.before.productionQueue.repeat=true,f=>f.before.productionQueue.complete=false,f=>f.native[0].value.units[1].underConstruction=true,f=>f.before.units.pop(),f=>f.rows.push({kind:"submit"}),f=>f.native.push({kind:"command-dispatch"}),f=>f.native.push({kind:"unit-created"}),f=>f.native.push({kind:"unit-finished"}),f=>f.native.push({kind:"snapshot",disposition:"gap"}),f=>f.native[0].stateSequence="9"]; + for(const change of changes){const f=fixture();change(f);assert.throws(()=>api.assertOnePreArm(f.h,f.q,f.rows,f.before,f.native),/one-unit refused/)} +}); +test("production source checks late extra creation and dispatch before freezing baseline",()=>{ + assert.match(source,/production or gap after seed qualification before baseline/); + assert.match(source,/\["unit-created","unit-finished","command-dispatch"\]\.includes\(r.kind\)/); + assert.match(source,/assertOnePreArm\(h,qualified,rows,before,nativeOneEvents/); + assert.ok(source.indexOf("recheckOneQualification(h);return h")>0); +}); +test("single-submit assertion rejects a retry before lifecycle evaluation",()=>{ + assert.throws(()=>api.assertOneProducedUnit([{kind:"submit",value:{}},{kind:"submit",value:{}}],{},[],{}),/exactly one submission required, no retry/); +}); + +test("handoff qualification initializes after lexical validators and before registration",()=>{ + assert.ok(source.indexOf("let oneUnitHandoff=null")1)throw new Error("native proof routes are mutually exclusive"); const handoff=enabled?await loadTacticalHandoff(process.env.BARC_TACTICAL_HANDOFF):null,stockHandoff=stockEnabled?await loadStockSmokeHandoff(process.env.BARC_STOCK_SMOKE_HANDOFF):null; let stage,codec; -const oneUnitHandoff=oneUnitEnabled?loadOneUnitHandoff(process.env.BARC_ONE_UNIT_HANDOFF,process.env.BARC_ONE_UNIT_HANDOFF_SHA256):null; +let oneUnitHandoff=null; // Initialized after the closed one-unit validators, before browser hooks run. test.beforeAll(async()=>{if(!enabled&&!stockEnabled&&!oneUnitEnabled)return;stage=await mkdtemp(resolve(import.meta.dirname,"node_modules/.barc-tactical-codec-"));await cp(enabled?handoff.codecDirectory:(oneUnitEnabled?oneUnitHandoff.paths.codecDirectory:stockHandoff.paths.codecDirectory),stage,{recursive:true});codec=await import(pathToFileURL(resolve(stage,"codec.js")).href)}); test.afterAll(async()=>{if(stage)await rm(stage,{recursive:true,force:true})}); @@ -198,6 +198,56 @@ test.describe("selected stock Count1 smoke",()=>{ const oneUnitCase="selected stock one-unit"; const insistOne=(condition,message)=>{if(!condition)throw new Error(`one-unit refused: ${message}`)}; const closedOne=(value,keys,label)=>insistOne(value&&typeof value==="object"&&!Array.isArray(value)&&JSON.stringify(Object.keys(value).sort())===JSON.stringify([...keys].sort()),`${label} keys changed`); +const qualificationOneKeys=["schema","actualPassed","noAutonomousOrders","runId","source","writer","acceptedGeneration","processIncarnation","stateChannelIncarnation","matchIncarnation","noAutonomousConfigurationEvidenceSha256","seedEvidence","inputCustody","liveProcesses","completeRecordEvidence","lifecycle"]; +function validateOneQualification(h,q){ + closedOne(q,qualificationOneKeys,"qualification"); + insistOne(q.schema==="bar.one-unit-attribution-source-qualification/v1"&&q.actualPassed===true&&q.noAutonomousOrders===true&&q.runId===h.runId&&JSON.stringify(q.source)===JSON.stringify(h.source)&&JSON.stringify(q.writer)===JSON.stringify(h.attribution.writer)&&q.acceptedGeneration===h.attribution.acceptedGeneration&&q.processIncarnation===h.attribution.processIncarnation&&q.stateChannelIncarnation===h.attribution.stateChannelIncarnation&&typeof q.matchIncarnation==="string"&&q.matchIncarnation.length>0,"qualification owning identity changed"); + closedOne(q.seedEvidence,["factory","seedDefinitionId","productDefinitionId","seedCount","initialRawSequence","nonemptyStockSequence","terminalRawSequence","terminalStateSequence","terminalNativeFrame","emptyStockSequence","products"],"seed evidence");const e=q.seedEvidence; + insistOne(sameRef(e.factory,h.actors.factory)&&e.seedDefinitionId===h.setup.seedDefinitionId&&e.productDefinitionId===h.setup.productDefinitionId&&Number.isInteger(e.seedCount)&&e.seedCount>0&&e.seedCount<=16&&Array.isArray(e.products)&&e.products.length===e.seedCount,"reviewed distinct finite seed history missing"); + const decimal=x=>typeof x==="string"&&/^[1-9][0-9]*$/.test(x)&&BigInt(x)<=18446744073709551615n; + insistOne([e.initialRawSequence,e.nonemptyStockSequence,e.terminalRawSequence,e.terminalStateSequence,e.emptyStockSequence].every(decimal)&&BigInt(e.terminalRawSequence)>BigInt(e.initialRawSequence)&&BigInt(e.emptyStockSequence)>BigInt(e.nonemptyStockSequence)&&Number.isInteger(e.terminalNativeFrame)&&e.terminalNativeFrame>=0,"seed history causality changed"); + for(const product of e.products){closedOne(product,["reference","createdSequence","finishedSequence"],"seed product");closedOne(product.reference,["id","lifetime"],"seed reference");insistOne(decimal(product.reference.id)&&decimal(product.reference.lifetime)&&decimal(product.createdSequence)&&decimal(product.finishedSequence)&&BigInt(product.createdSequence)>BigInt(e.initialRawSequence)&&BigInt(product.finishedSequence)>BigInt(product.createdSequence)&&BigInt(product.finishedSequence)key(p.reference))).size===e.seedCount&&new Set(e.products.map(p=>p.reference.id)).size===e.seedCount,"duplicate seed lifetime"); + closedOne(q.inputCustody,["raw","host","stock","metadata","setup","configuration"],"qualified inputs");insistOne(q.inputCustody.raw.path===h.paths.nativeEvents&&q.inputCustody.stock.path===h.paths.stockTrace&&q.inputCustody.configuration.sha256===q.noAutonomousConfigurationEvidenceSha256,"retained feed/configuration join changed"); + closedOne(q.lifecycle,["startedMonotonicNs","transitionDeadlineMonotonicNs","totalDeadlineMonotonicNs"],"owned lifecycle");const l=q.lifecycle;insistOne(Object.values(l).every(decimal)&&BigInt(l.startedMonotonicNs)0&&q.liveProcesses.length<=4,"owned live process census missing"); + for(const process of q.liveProcesses){closedOne(process,["pid","startTicks","uid","role"],"live process");insistOne(Number.isInteger(process.pid)&&process.pid>0&&decimal(process.startTicks)&&Number.isInteger(process.uid)&&process.uid>=0&&["host","engine"].includes(process.role),"live process identity changed")} + insistOne(q.liveProcesses.some(p=>p.role==="host"&&p.pid===q.writer.pid&&p.startTicks===q.writer.startTicks&&p.uid===q.writer.uid),"live host writer join missing");return q; +} +function readPinnedOne(pin,limit,growing=false){ + closedOne(pin,["path","sha256","bytes","device","inode","uid","mode","nlink"],"physical pin");insistOne(typeof pin.path==="string"&&pin.path.startsWith("/")&&resolve(pin.path)===pin.path&&/^[a-f0-9]{64}$/.test(pin.sha256)&&Number.isSafeInteger(pin.bytes)&&pin.bytes>0&&pin.bytes<=limit,"physical pin format changed"); + for(let parent=dirname(pin.path);;parent=dirname(parent)){const st=lstatSync(parent);insistOne(st.isDirectory()&&!st.isSymbolicLink(),"input ancestor link");if(parent==="/")break} + const fd=openSync(pin.path,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_CLOEXEC); + try{const st=fstatSync(fd),check=()=>{const now=fstatSync(fd),named=lstatSync(pin.path);insistOne(now.isFile()&&!named.isSymbolicLink()&&now.nlink===1&&now.dev===pin.device&&now.ino===pin.inode&&now.uid===pin.uid&&(now.mode&0o777)===pin.mode&&named.dev===now.dev&&named.ino===now.ino&&now.size<=limit&&(growing?now.size>=pin.bytes:now.size===pin.bytes),"retained physical custody changed")};check();const body=Buffer.alloc(pin.bytes);let offset=0;while(offset0,"retained input short read");offset+=count}insistOne(createHash("sha256").update(body).digest("hex")===pin.sha256,"retained input prefix changed");check();return body}finally{closeSync(fd)} +} +function readQualifiedOne(h){ + const st=lstatSync(h.paths.eventQualification);insistOne(st.isFile()&&!st.isSymbolicLink()&&st.nlink===1&&(st.mode&0o777)===0o600&&st.size>0&&st.size<=65536,"qualification physical custody changed"); + return readPinnedOne({path:h.paths.eventQualification,sha256:h.attribution.qualificationSha256,bytes:st.size,device:st.dev,inode:st.ino,uid:st.uid,mode:0o600,nlink:1},65536); +} +function mappedOneConfiguration(q,cfg){ + const engines=q.liveProcesses.filter(p=>p.role==="engine");insistOne(engines.length===1,"current engine custody missing");const fd=openSync(`/proc/${engines[0].pid}/maps`,constants.O_RDONLY|constants.O_CLOEXEC),bytes=Buffer.alloc(1024*1024+1);let offset=0; + try{while(offset{const m=/^\S+\s+\S+\s+\S+\s+([0-9a-f]+):([0-9a-f]+)\s+(\d+)\s+(.+)$/.exec(line);if(!m||m[4]!==pin.path)return false;const major=BigInt(`0x${m[1]}`),minor=BigInt(`0x${m[2]}`),dev=((major&4095n)<<8n)|(minor&255n)|((minor&~255n)<<12n)|((major&~4095n)<<32n);return dev===BigInt(pin.device)&&BigInt(m[3])===BigInt(pin.inode)}),"engine/plugin mapped custody changed before Arm")} +} +function recheckOneQualification(h){ + const bytes=readQualifiedOne(h);insistOne(bytes.length<=65536&&createHash("sha256").update(bytes).digest("hex")===h.attribution.qualificationSha256,"qualification changed before Arm");const q=validateOneQualification(h,parseClosedJson(bytes.toString("utf8"))); + // process.hrtime and Python monotonic_ns share CLOCK_MONOTONIC on admitted Linux. + const now=process.hrtime.bigint();insistOne(now>=BigInt(q.lifecycle.startedMonotonicNs)&&now{insistOne(depth<=16&&++count<=1024,"configuration pin traversal bound");if(value&&typeof value==="object"){if(typeof value.path==="string"&&typeof value.sha256==="string"&&"inode"in value){const body=readPinnedOne(value,128*1024*1024);if(["loaded","configuration","review","setupCompletion","aiSlots","buildReceipt","canonicalReceipt","completeRecordEvidence"].includes(name))walk(parseClosedJson(body.toString("utf8")),depth+1,name)}else for(const [key,child]of Object.entries(value))walk(child,depth+1,key)}};const cfg=parseClosedJson(readPinnedOne(q.inputCustody.configuration,65536).toString("utf8"));walk(cfg);mappedOneConfiguration(q,cfg);walk(q.completeRecordEvidence,0,"completeRecordEvidence"); + for(const p of q.liveProcesses){const text=readFileSync(`/proc/${p.pid}/stat`,"utf8"),tail=text.slice(text.lastIndexOf(")")+2).trim().split(/\s+/),uid=/^Uid:\s+(\d+)\s+/m.exec(readFileSync(`/proc/${p.pid}/status`,"utf8"));insistOne(!["Z","X"].includes(tail[0])&&tail[19]===p.startTicks&&uid&&Number(uid[1])===p.uid,"live PID generation changed before Arm")} + return q; +} +function assertOnePreArm(h,q,rows,before,native){ + validateOneQualification(h,q);const e=q.seedEvidence,f=h.actors.factory,b=before?.basis; + insistOne(b&&b.processIncarnation===q.processIncarnation&&b.stateChannelIncarnation===q.stateChannelIncarnation&&b.matchId===q.matchIncarnation&&BigInt(b.stateSequence)>=BigInt(e.terminalStateSequence)&&Number(b.nativeFrame)>=e.terminalNativeFrame,"fresh same-generation baseline missing"); + const current=queue(before,f,"QUEUE_DOMAIN_FACTORY_PRODUCTION");insistOne(current?.complete===true&¤t.repeat===false&¤t.entries?.length===0,"fresh empty repeat-off baseline missing"); + const rawUnits=native[0]?.value?.units;insistOne(Array.isArray(before.units)&&Array.isArray(rawUnits)&&rawUnits.length>0&&rawUnits.every(u=>u.underConstruction===false&&u.buildProgress===1),"fresh baseline contains unfinished units"); + insistOne(e.products.every(p=>before.units.some(u=>sameRef(u.reference,p.reference)&&Number(u.definitionId)===h.setup.seedDefinitionId)),"qualified seed lifetime missing from fresh baseline"); + insistOne(rows.every(r=>!["submit","result"].includes(r.kind))&&native[0]?.kind==="baseline"&&native[0].stateSequence===b.stateSequence&&native.every(r=>!["unit-created","unit-finished","command-dispatch"].includes(r.kind)&&r.disposition!=="gap"),"pre-Arm dispatch or missing native baseline"); + return before; +} function loadOneUnitHandoff(path,sha256){ insistOne(typeof path==="string"&&path.startsWith("/")&&/^[a-f0-9]{64}$/.test(sha256??""),"pinned handoff required"); const st=lstatSync(path),bytes=readFileSync(path);insistOne(st.isFile()&&!st.isSymbolicLink()&&st.nlink===1&&(st.mode&0o777)===0o600&&bytes.length<=65536&&createHash("sha256").update(bytes).digest("hex")===sha256,"handoff physical/body pin changed"); @@ -210,22 +260,22 @@ function loadOneUnitHandoff(path,sha256){ insistOne(Object.values(h.paths).every(p=>typeof p==="string"&&p.startsWith("/")),"absolute capsule paths required"); closedOne(h.attribution,["qualified","qualificationSha256","writer","acceptedGeneration","processIncarnation","stateChannelIncarnation"],"attribution"); insistOne(h.attribution.qualified===true&&/^[a-f0-9]{64}$/.test(h.attribution.qualificationSha256),"actual event-source qualification missing"); - const qualification=readFileSync(h.paths.eventQualification);insistOne(qualification.length<=65536&&createHash("sha256").update(qualification).digest("hex")===h.attribution.qualificationSha256,"event-source qualification body changed"); - const qualified=parseClosedJson(qualification.toString("utf8"));insistOne(qualified.schema==="bar.one-unit-attribution-source-qualification/v1"&&qualified.actualPassed===true&&qualified.acceptedGeneration===h.attribution.acceptedGeneration&&qualified.noAutonomousOrders===true&&qualified.runId===h.runId&&JSON.stringify(qualified.source)===JSON.stringify(h.source)&&JSON.stringify(qualified.writer)===JSON.stringify(h.attribution.writer)&&qualified.processIncarnation===h.attribution.processIncarnation&&qualified.stateChannelIncarnation===h.attribution.stateChannelIncarnation&&/^[a-f0-9]{64}$/.test(qualified.noAutonomousConfigurationEvidenceSha256??""),"genuine qualified run/source/writer/generation/no-autonomous configuration missing"); + const qualification=readQualifiedOne(h);insistOne(qualification.length<=65536&&createHash("sha256").update(qualification).digest("hex")===h.attribution.qualificationSha256,"event-source qualification body changed"); + const qualified=parseClosedJson(qualification.toString("utf8")); closedOne(h.actors,["factory"],"actors");closedOne(h.setup,["productDefinitionId","seedDefinitionId","maximumSeconds"],"setup"); insistOne(h.setup.maximumSeconds===180&&Number.isInteger(h.setup.productDefinitionId)&&h.setup.productDefinitionId>0&&Number.isInteger(h.setup.seedDefinitionId)&&h.setup.seedDefinitionId>0&&h.setup.seedDefinitionId!==h.setup.productDefinitionId,"finite distinct seed/product setup required"); // Missing capture-source or native feed refuses before browser pairing/Arm. - insistOne(existsSync(h.paths.nativeEvents),"genuine native event feed missing");return h; + insistOne(existsSync(h.paths.nativeEvents),"genuine native event feed missing");validateOneQualification(h,qualified);recheckOneQualification(h);return h; } function nativeOneEvents(h,rows,before){ const st=lstatSync(h.paths.nativeEvents);insistOne(st.isFile()&&!st.isSymbolicLink()&&st.nlink===1&&st.size<=4*1024*1024,"native event stream physical bound changed"); - const text=readFileSync(h.paths.nativeEvents,"utf8"),lines=text.split("\n");lines.pop();insistOne(lines.length>0&&lines.length<=8192,"native event row bound changed"); - const raw=lines.map((line,index)=>{insistOne(Buffer.byteLength(line)<=32768,"native event record oversized");const r=parseClosedJson(line);closedOne(r,["schema","runId","sequence","kind","writer","source","acceptedGeneration","stateSequence","nativeFrame","disposition","value"],"native raw row");insistOne(r.schema==="fsbar.barc-one-unit-raw-state-journal/v1"&&r.runId===h.runId&&r.sequence===String(index+1)&&JSON.stringify(r.writer)===JSON.stringify(h.attribution.writer)&&JSON.stringify(r.source)===JSON.stringify(h.source)&&r.acceptedGeneration===h.attribution.acceptedGeneration,"native raw origin/generation/sequence changed");insistOne(["snapshot","unit-created","unit-finished","command-dispatch"].includes(r.kind)&&["materialized","invalidated","gap","liveness-only"].includes(r.disposition)&&/^[0-9]+$/.test(r.stateSequence)&&Number.isSafeInteger(r.nativeFrame)&&r.nativeFrame>=0,"unknown native event or basis");return r}); + const text=readFileSync(h.paths.nativeEvents,"utf8");insistOne(text.endsWith("\n"),"native partial record refused");const lines=text.split("\n");lines.pop();insistOne(lines.length>0&&lines.length<=8192,"native event row bound changed"); + const raw=lines.map((line,index)=>{insistOne(Buffer.byteLength(line)+1<=32768,"native event record oversized");const r=parseClosedJson(line);closedOne(r,["schema","runId","sequence","kind","writer","source","acceptedGeneration","stateSequence","nativeFrame","disposition","value"],"native raw row");insistOne(r.schema==="fsbar.barc-one-unit-raw-state-journal/v1"&&r.runId===h.runId&&r.sequence===String(index+1)&&JSON.stringify(r.writer)===JSON.stringify(h.attribution.writer)&&JSON.stringify(r.source)===JSON.stringify(h.source)&&r.acceptedGeneration===h.attribution.acceptedGeneration,"native raw origin/generation/sequence changed");insistOne(["snapshot","unit-created","unit-finished","command-dispatch"].includes(r.kind)&&["materialized","invalidated","gap","liveness-only"].includes(r.disposition)&&/^[0-9]+$/.test(r.stateSequence)&&Number.isSafeInteger(r.nativeFrame)&&r.nativeFrame>=0,"unknown native event or basis");return r}); // Freeze the pre-Arm baseline by its actual browser/native state position. const snapshot=raw.find(r=>r.kind==="snapshot"&&r.disposition==="materialized"&&r.stateSequence===before.basis.stateSequence&&r.nativeFrame===Number(before.basis.nativeFrame));insistOne(snapshot,"actual same-basis baseline snapshot missing"); insistOne(snapshot.value.units.length>0&&snapshot.value.units.every(u=>u.underConstruction===false&&u.buildProgress===1),"native baseline contains unfinished units"); - const result=raw.filter(r=>BigInt(r.sequence)>BigInt(snapshot.sequence));result.unshift({...snapshot,kind:"baseline"}); - const stockStat=lstatSync(h.paths.stockTrace);insistOne(stockStat.isFile()&&!stockStat.isSymbolicLink()&&stockStat.nlink===1&&stockStat.size<=16*1024*1024,"stock reader physical bound changed");let stockText=readFileSync(h.paths.stockTrace,"utf8");stockText=stockText.slice(0,stockText.lastIndexOf("\n")+1);const stock=parseStockTraceJsonl(stockText); + const q=validateOneQualification(h,parseClosedJson(readQualifiedOne(h).toString("utf8")));for(const r of raw.filter(r=>BigInt(r.sequence)>BigInt(q.seedEvidence.terminalRawSequence)&&BigInt(r.sequence)<=BigInt(snapshot.sequence)))insistOne(!["unit-created","unit-finished","command-dispatch"].includes(r.kind)&&r.disposition!=="gap","production or gap after seed qualification before baseline");const result=raw.filter(r=>BigInt(r.sequence)>BigInt(snapshot.sequence));result.unshift({...snapshot,kind:"baseline"}); + const stockStat=lstatSync(h.paths.stockTrace);insistOne(stockStat.isFile()&&!stockStat.isSymbolicLink()&&stockStat.nlink===1&&stockStat.size<=16*1024*1024,"stock reader physical bound changed");let stockText=readFileSync(h.paths.stockTrace,"utf8");insistOne(stockText.endsWith("\n"),"stock partial record refused");const stock=parseStockTraceJsonl(stockText); for(const r of stock){if(r.reader.status!=="complete")continue;insistOne(r.runId===h.runId&&sameRef(r.context.actor,h.actors.factory)&&r.nativeBasis.processIncarnation===h.attribution.processIncarnation&&r.nativeBasis.stateChannelIncarnation===h.attribution.stateChannelIncarnation,"stock queue origin/basis changed"); const o=observations(rows).find(o=>o.basis.stateSequence===r.nativeBasis.stateSequence&&o.basis.processIncarnation===r.nativeBasis.processIncarnation&&o.basis.stateChannelIncarnation===r.nativeBasis.stateChannelIncarnation&&o.basis.matchId===r.nativeBasis.matchIncarnation&&Number(o.basis.nativeFrame)===r.nativeBasis.frame),q=queue(o,h.actors.factory,"QUEUE_DOMAIN_FACTORY_PRODUCTION"); if(r.context.domain!=="production"||q?.complete!==true||q.repeat!==false||String(q.revision)!==r.queue.revision||q.entries?.length!==r.queue.entries.length)continue; @@ -256,15 +306,16 @@ export function assertOneProducedUnit(rows,before,native,h){ insistOne(final.length===1,"one genuine predispatch final native fence missing");const d=final[0].value.dispatch;insistOne(d?.matched===true&&String(d.batchSequence)===String(dispatch.batchSequence)&&String(d.correlationId)===String(dispatch.correlationId)&&d.moduleSha256===h.artifacts.guest.sha256&&String(d.moduleGeneration)===String(s.module.generation)&&d.brokerSessionId===s.controller.sessionId&&d.commandChannelIncarnation===dispatch.commandChannelIncarnation&&d.commandIndex===nativeDispatch[0].value.commandIndex&&String(d.expectedQueueRevision)===String(s.intent.actorTacticalBindings?.find(b=>sameRef(b.actor,f))?.queueRevisions?.find(q=>q.domain==="QUEUE_DOMAIN_FACTORY_PRODUCTION")?.revision)&&final[0].value.nativeBasis.stateSequence===s.basis.stateSequence&&final[0].value.nativeBasis.token===s.basis.token&&final[0].value.nativeBasis.matchIncarnation===s.basis.matchId&&final[0].value.catalogueId===s.intent.factoryProduce.catalogueId&&String(final[0].value.catalogueRevision)===String(s.intent.factoryProduce.catalogueRevision)&&String(d.authorityEpoch)===String(s.controller.authorityEpoch),"final native dispatch/module/authority/catalogue/queue-basis join missing"); const terminal=native.filter(r=>r.kind==="queue-observation"&&r.nativeFrame>=Number(later.at(-1).basis.nativeFrame)&&sameRef(r.value.actor,f)&&r.value.complete===true&&r.value.repeat===false&&r.value.entries?.length===0);insistOne(terminal.length>0,"later terminal native queue read missing");return{schema:"fsbar.barc-one-produced-unit-evidence/v1",runId:h.runId,parentId:s.parentId,product:later.at(-1).units.find(u=>!known.has(key(u.reference))).reference,builder:f,createdSequence:created[0].sequence,finishedSequence:finished[0].sequence,finalReadSequence:final[0].sequence,terminalReadSequence:terminal.at(-1).sequence,unitProduced:true,fullSix:false,nativeUsefulPlay:"0/6"}; } +if(oneUnitEnabled)oneUnitHandoff=loadOneUnitHandoff(process.env.BARC_ONE_UNIT_HANDOFF,process.env.BARC_ONE_UNIT_HANDOFF_SHA256); test.describe(oneUnitCase,()=>{ test.skip(!oneUnitEnabled,"requires root-admitted checked one-unit capsule and genuine native attribution feed"); test("one Append Count1 completes exactly one factory-attributed unit",async({page})=>{ test.setTimeout(180000);const h=oneUnitHandoff,j={receiverUrl:h.connection.receiverUrl,gatewayUrl:h.connection.gatewayUrl,allowedOrigin:h.connection.allowedOrigin,expectedSessionId:h.connection.sessionId,credential:h.connection.credential,bundledGuestSha256:h.artifacts.guest.sha256,outputPath:h.paths.capture,armFailurePath:`${h.paths.capture}.arm-failure.json`,deferArm:true},journal=captureJournal(h.paths.capture,h.paths.release,h.runId,h.source,oneUnitCase); try{const rows=await capture(page,journal),live=await pair(page,j,"Manual guest",rows),f=h.actors.factory;await select(page,live,"pointer",[f]); - const seeded=queue(latest(rows),f,"QUEUE_DOMAIN_FACTORY_PRODUCTION");insistOne(seeded?.complete===true&&seeded.repeat===false&&seeded.entries?.length>0&&Number(seeded.entries[0].definitionId)===h.setup.seedDefinitionId,"nonempty distinct seed readiness missing"); - await expect.poll(()=>{const q=queue(latest(rows),f,"QUEUE_DOMAIN_FACTORY_PRODUCTION");return q?.complete===true&&q.repeat===false&&q.entries?.length===0},{timeout:60000}).toBe(true); - const before=structuredClone(latest(rows));insistOne(rows.filter(r=>r.kind==="submit").length===0,"autonomous/baseline submit"); - nativeOneEvents(h,rows,before); // The pinned actual source qualification must separately prove autonomous decisions disabled. + // Qualification retains the genuine earlier nonempty queue and its natural drain. + // Browser readiness is a fresh empty baseline, so no seed/browser race is needed. + const before=structuredClone(latest(rows)),qualified=recheckOneQualification(h); + assertOnePreArm(h,qualified,rows,before,nativeOneEvents(h,rows,before)); journal.append("one-unit-baseline",before);rows.readiness.phase="arm-clicked";await live.getByRole("button",{name:"Arm live"}).click();await expect.poll(()=>rows.readiness.armSha256).toBe(rows.readiness.moduleSha256);await expect(live.locator(".authority")).toContainText("arm native confirmed");insistOne(rows.readiness.armCount===1,"exactly one Arm required"); await live.getByLabel("Action").selectOption("factoryProduce");await live.getByLabel("Definition").selectOption(String(h.setup.productDefinitionId));await live.getByLabel("Count").fill("1");await live.getByLabel("Queue policy").selectOption("TACTICAL_QUEUE_POLICY_APPEND");await physicalSubmit(page,live,"pointer");await expect.poll(()=>rows.filter(r=>r.kind==="submit").length,{timeout:5000}).toBe(1);const submit=rows.find(r=>r.kind==="submit").value;await settleSubmit(rows,submit,"applied"); // No stale-basis replacement: this route submits once or refuses. From 8652dc540b0dad57c29fc2531f330d09544e4931 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 22:25:39 +0200 Subject: [PATCH 05/10] Preserve exact seed attribution helper source for provenance --- .../Gadgets/barc_stock_queue_fixture.lua | 213 ++++ .../Gadgets/barc_stock_queue_reader.lua | 323 ++++++ .../fixture/tests/test_prearm_observation.py | 22 + .../helper/buffered_writer.py | 51 + .../helper/capture_custody.py | 77 ++ .../helper/dynamic_mapping.py | 85 ++ .../helper/growing_log.py | 450 ++++++++ .../helper/loaded_custody.py | 36 + .../helper/mapping_profile.py | 63 + .../helper/private_io.py | 98 ++ .../helper/runtime_identity.py | 168 +++ .../helper/seed_policy.py | 109 ++ .../helper/settings_custody.py | 59 + .../helper/snapshot_publication.py | 29 + .../helper/stock_bootstrap.py | 210 ++++ .../helper/stock_config.py | 114 ++ .../helper/test_buffered_writer.py | 102 ++ .../helper/test_capture_custody.py | 76 ++ .../helper/test_file_flush.py | 43 + .../helper/test_growing_log.py | 1016 +++++++++++++++++ .../helper/test_loaded_custody.py | 51 + .../helper/test_prearm_startup.py | 36 + .../helper/test_proc_bytes.py | 26 + .../helper/test_seed_policy.py | 69 ++ .../helper/test_settings_custody.py | 26 + .../seed-attribution-helper/mechanics.py | 169 +++ .../native_evidence.py | 135 +++ .../seed-attribution-helper/outer_custody.py | 159 +++ .../seed-attribution-helper/qualify_prearm.py | 182 +++ .../seed-attribution-helper/runtime_socket.py | 65 ++ .../seed-attribution-helper/startup_runner.py | 416 +++++++ .../test_native_evidence.py | 28 + .../test_qualify_prearm.py | 96 ++ 33 files changed, 4802 insertions(+) create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/LuaRules/Gadgets/barc_stock_queue_fixture.lua create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/LuaRules/Gadgets/barc_stock_queue_reader.lua create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/test_prearm_observation.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/buffered_writer.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/capture_custody.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/dynamic_mapping.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/growing_log.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/loaded_custody.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/mapping_profile.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/private_io.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/runtime_identity.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/seed_policy.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/settings_custody.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/snapshot_publication.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/stock_bootstrap.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/stock_config.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_buffered_writer.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_capture_custody.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_file_flush.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_growing_log.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_loaded_custody.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_prearm_startup.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_proc_bytes.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_seed_policy.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_settings_custody.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/mechanics.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/native_evidence.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/outer_custody.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/qualify_prearm.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/runtime_socket.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/startup_runner.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_native_evidence.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_qualify_prearm.py diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/LuaRules/Gadgets/barc_stock_queue_fixture.lua b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/LuaRules/Gadgets/barc_stock_queue_fixture.lua new file mode 100644 index 0000000..7de1f25 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/LuaRules/Gadgets/barc_stock_queue_fixture.lua @@ -0,0 +1,213 @@ +function gadget:GetInfo() + return { + name = "HighBar BARC Stock Queue Fixture", + desc = "Setup-only actors and bounded pre-readiness production for BARC-01.5 stock qualification", + author = "HighBar", + version = "3", + date = "2026-10-01", + license = "MIT", + layer = 1003, + enabled = true, + } +end + +-- Finite observations only. Hex strings prevent whitespace/log framing ambiguity. +local options = Spring.GetModOptions() or {} +local nonce = options.highbar_barc_prearm_nonce +local prearm = type(nonce) == "string" and #nonce >= 1 and #nonce <= 64 + and nonce:match("^[%w._-]+$") ~= nil +local function hex(value) + if type(value) ~= "string" or #value > 4096 then return nil end + if value == "" then return "-" end + return (value:gsub(".", function(c) return string.format("%02x", string.byte(c)) end)) +end +local function observe(kind, frame, fields) + if not prearm then return end + local keys = {} + for key in pairs(fields) do keys[#keys + 1] = key end + table.sort(keys) + local row = "BARC_PREARM_V1 kind=" .. kind .. " nonce=" .. nonce .. " frame=" .. tostring(frame) + for _, key in ipairs(keys) do row = row .. " " .. key .. "=" .. tostring(fields[key]) end + Spring.Echo(row) +end +local function rejected(frame, reason) observe("refused", frame, {reason=reason}) end +local paths = {"LuaRules/Gadgets/barc_stock_queue_fixture.lua", "LuaRules/Gadgets/barc_stock_queue_reader.lua"} + +if not gadgetHandler:IsSyncedCode() then + local observed = false + function gadget:GameFrame(frame) + if observed or not prearm or frame < 1 then return end + observed = true + -- Unsynced only: synced GetAIInfo returns deliberately substituted identities. + local teams = Spring.GetTeamList() + if type(teams) ~= "table" or #teams > 256 then return rejected(frame, "team_inventory") end + for _, team in ipairs(teams) do + local id, name, host, short, version, aiOptions = Spring.GetAIInfo(team) + if id ~= nil then + if type(aiOptions) ~= "table" or short == "UNKNOWN" or short == "SYNCED_NOSHORTNAME" then + return rejected(frame, "ai_identity") + end + local keys, encoded = {}, {} + for key in pairs(aiOptions) do keys[#keys + 1] = key end + if #keys > 32 then return rejected(frame, "ai_options_bound") end + table.sort(keys) + for _, key in ipairs(keys) do + local k, value = hex(key), hex(aiOptions[key]) + if not k or not value then return rejected(frame, "ai_options") end + encoded[#encoded + 1] = k .. ":" .. value + end + if not hex(name) or not hex(short) or not hex(version) then return rejected(frame, "ai_strings") end + observe("ai", frame, {team=team, id=id, name=hex(name), host=host, + short=hex(short), version=hex(version), options=hex(table.concat(encoded, ","))}) + end + end + for _, key in ipairs({"Headless", "LogFlush", "LogFlushLevel"}) do + local value = Spring.GetConfigInt(key) + if type(value) ~= "number" or value % 1 ~= 0 then return rejected(frame, "effective_config") end + observe("config", frame, {key=hex(key), type="int", value=hex(tostring(value))}) + end + for _, path in ipairs(paths) do + local archive = VFS.GetArchiveContainingFile(path, VFS.ZIP) + local absolute = VFS.GetFileAbsolutePath(path, VFS.ZIP) + if archive == nil and absolute == nil then return rejected(frame, "vfs_lookup") end + observe("vfs", frame, {path=hex(path), archive=hex(archive or ""), absolute=hex(absolute or "")}) + end + end + return +end + +local OPT_ENABLE = "highbar_barc_stock_fixture" +local OPT_TEAM = "highbar_barc_stock_team" +local OPT_ENEMY_TEAM = "highbar_barc_stock_enemy_team" +local PREFIX = "highbar_barc_stock_fixture" +local OLD_PRODUCTION_COUNT = 3 +local done = false +local phase = "actors" +local selected = nil + +local function enabled(value) + return value == true or value == 1 or value == "1" +end + +local function fail(reason) + done = true + rejected(Spring.GetGameFrame(), reason) + Spring.Echo(string.format("%s kind=setup status=failed reason=%s", PREFIX, reason)) +end + +local function liveTeam(teamID) + if type(teamID) ~= "number" or teamID < 0 or teamID % 1 ~= 0 then return false end + local _, _, isDead = Spring.GetTeamInfo(teamID, false) + return isDead == false +end + +local function create(name, x, z, facing, teamID) + local def = UnitDefNames[name] + if not def or type(def.id) ~= "number" then return nil, "missing_definition_" .. name end + local y = Spring.GetGroundHeight(x, z) + local unitID = Spring.CreateUnit(name, x, y, z, facing, teamID) + if type(unitID) ~= "number" then return nil, "create_failed_" .. name end + return unitID, nil +end + +local function setup(frame) + local options = Spring.GetModOptions() or {} + if not enabled(options[OPT_ENABLE]) then return end + local teamID = tonumber(options[OPT_TEAM]) + local enemyTeamID = tonumber(options[OPT_ENEMY_TEAM]) + if not liveTeam(teamID) or not liveTeam(enemyTeamID) or teamID == enemyTeamID then + return fail("invalid_distinct_live_teams") + end + + local factory, factoryError = create("armvp", 1024, 1024, "south", teamID) + if not factory then return fail(factoryError) end + local builder, builderError = create("armck", 1248, 1024, "south", teamID) + if not builder then return fail(builderError) end + local enemy, enemyError = create("corak", 1536, 1024, "north", enemyTeamID) + if not enemy then return fail(enemyError) end + if factory == builder or factory == enemy or builder == enemy then + return fail("actor_identity_collision") + end + + Spring.SetTeamResource(teamID, "ms", 10000) + Spring.SetTeamResource(teamID, "es", 10000) + Spring.SetTeamResource(teamID, "m", 8000) + Spring.SetTeamResource(teamID, "e", 8000) + Spring.SetUnitAlwaysVisible(enemy, true) + + local oldDefinition = UnitDefNames.armfav + local distinctDefinition = UnitDefNames.armflash + if not oldDefinition or not distinctDefinition or oldDefinition.id == distinctDefinition.id then + return fail("production_definitions_missing_or_equal") + end + if not prearm then return fail("prearm_nonce_required") end + for _, path in ipairs(paths) do + local bytes = VFS.LoadFile(path, VFS.ZIP) + if type(bytes) ~= "string" or #bytes == 0 or #bytes > 131072 then return fail("vfs_content_bound") end + local hash = VFS.CalculateHash(bytes, 1) -- actual engine SHA-512, never SHA-256 + if type(hash) ~= "string" or #hash ~= 128 or not hash:match("^[0-9a-f]+$") then return fail("vfs_hash") end + observe("content", frame, {path=hex(path), bytes=#bytes, sha512=hash}) + end + if not hex(Game.mapName) or not hex(Game.gameName) or type(Game.mapChecksum) ~= "string" + or type(Game.modChecksum) ~= "string" then return fail("archive_identity") end + observe("archives", frame, {map=hex(Game.mapName), game=hex(Game.gameName), + map_sha512=Game.mapChecksum, game_sha512=Game.modChecksum}) + selected = {factory=factory, builder=builder, enemy=enemy, team=teamID, enemyTeam=enemyTeamID, + seed=oldDefinition.id, product=distinctDefinition.id} + GG.BARCPrearmEmpty = {nonce=nonce, factory=factory, team=teamID, observedFrame=nil} + observe("actors", frame, {team=teamID, enemy=enemyTeamID, factory=factory, builder=builder, + target=enemy, seed=oldDefinition.id, product=distinctDefinition.id}) + phase = "await-empty" +end + +local function enqueue(frame) + local factory, oldDefinition = selected.factory, {id=selected.seed} + -- Set passive before issuing any order; a refusal can never trigger a retry. + done = true + phase = "passive" + for index = 1, OLD_PRODUCTION_COUNT do + local optionsBits = 0 + if Spring.GiveOrderToUnit(factory, -oldDefinition.id, {}, optionsBits) == false then + return fail("old_production_order_refused") + end + observe("order", frame, {factory=factory, ordinal=index, definition=oldDefinition.id, count=1, options=optionsBits}) + end + local queue = Spring.GetFactoryCommands(factory, OLD_PRODUCTION_COUNT + 1) or {} + if #queue ~= OLD_PRODUCTION_COUNT then return fail("old_production_count_changed") end + for index = 1, OLD_PRODUCTION_COUNT do + if not queue[index] or queue[index].id ~= -oldDefinition.id then + return fail("old_production_order_changed") + end + end + + for index = 1, OLD_PRODUCTION_COUNT do + local command = queue[index] + if type(command.tag) ~= "number" or command.tag < 0 or command.tag % 1 ~= 0 + or type(command.options) ~= "table" or command.options.coded ~= 0 then return fail("queue_fields") end + observe("queue", frame, {factory=factory, ordinal=index, definition=oldDefinition.id, + tag=command.tag, options=command.options.coded}) + end + observe("complete", frame, {factory=factory, count=OLD_PRODUCTION_COUNT, + empty_frame=GG.BARCPrearmEmpty.observedFrame}) + local teamID, enemyTeamID, builder, enemy = selected.team, selected.enemyTeam, selected.builder, selected.enemy + local distinctDefinition = {id=selected.product} + + done = true + Spring.Echo(string.format( + "%s kind=ready frame=%d team=%d enemy_team=%d factory=%d builder=%d visible_enemy=%d old_definition=%d distinct_product=%d old_production_count=%d post_ready_mutation=zero", + PREFIX, frame, teamID, enemyTeamID, factory, builder, enemy, + oldDefinition.id, distinctDefinition.id, OLD_PRODUCTION_COUNT)) +end + +function gadget:GameFrame(frame) + if done or frame < 1 then return end + if phase == "actors" then return setup(frame) end + if phase == "await-empty" then + local observed = GG.BARCPrearmEmpty + if observed and observed.nonce == nonce and observed.factory == selected.factory + and type(observed.observedFrame) == "number" and frame > observed.observedFrame then + observe("empty", observed.observedFrame, {factory=selected.factory, observed_frame=observed.observedFrame}) + enqueue(frame) + end + end +end diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/LuaRules/Gadgets/barc_stock_queue_reader.lua b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/LuaRules/Gadgets/barc_stock_queue_reader.lua new file mode 100644 index 0000000..ec2359f --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/LuaRules/Gadgets/barc_stock_queue_reader.lua @@ -0,0 +1,323 @@ +function gadget:GetInfo() + return { + name = "BARC Stock Queue Reader", + desc = "Bounded team-scoped stock production and rally observation", + author = "FS-GG", + version = "1", + date = "2026-10-01", + license = "MIT", + layer = 0, + enabled = true, + } +end + +-- Stock AICallback::CallRules selects LuaRules' synced handle. The generic +-- RecvSkirmishAIMessage comment does not change that explicit dispatch route. +if not gadgetHandler:IsSyncedCode() then + return +end + +local REQUEST_ROUTE = "BARC_QUEUE_REQUEST/" +local REQUEST_MAGIC = "BARC_QUEUE_REQUEST/1" +local RESPONSE_MAGIC = "BARC_QUEUE_RESPONSE/1" +local BRIDGE = "barc-stock-queue-reader-v1" +local MAX_BYTES = 8192 +local MAX_LINE_BYTES = 512 +local MAX_ENTRIES = 64 +local PROBE_ENTRIES = 65 +local MAX_PARAMS = 16 +local MAX_TOTAL_PARAMS = 256 +local HEX = "0123456789abcdef" + +local function byteBand(a, b) + local result, place = 0, 1 + for _ = 1, 8 do + if a % 2 == 1 and b % 2 == 1 then result = result + place end + a, b, place = math.floor(a / 2), math.floor(b / 2), place * 2 + end + return result +end + +local function byteBxor(a, b) + local result, place = 0, 1 + for _ = 1, 8 do + if a % 2 ~= b % 2 then result = result + place end + a, b, place = math.floor(a / 2), math.floor(b / 2), place * 2 + end + return result +end + +local function wordFromHex(value) + local word = {} + for index = 1, 8, 2 do word[#word + 1] = assert(tonumber(value:sub(index, index + 1), 16)) end + return word +end + +local function wordXor(a, b, c) + local result = {} + for index = 1, 4 do + local value = byteBxor(a[index], b[index]) + result[index] = c and byteBxor(value, c[index]) or value + end + return result +end + +local function wordAnd(a, b) + return {byteBand(a[1], b[1]), byteBand(a[2], b[2]), byteBand(a[3], b[3]), byteBand(a[4], b[4])} +end + +local function wordNot(a) + return {255 - a[1], 255 - a[2], 255 - a[3], 255 - a[4]} +end + +local function wordShiftRight(a, count) + local result, whole, bits = {}, math.floor(count / 8), count % 8 + for index = 1, 4 do + local source = index - whole + local value = source >= 1 and math.floor(a[source] / (2 ^ bits)) or 0 + if bits > 0 and source > 1 then value = value + (a[source - 1] % (2 ^ bits)) * (2 ^ (8 - bits)) end + result[index] = value + end + return result +end + +local function wordShiftLeft(a, count) + local result, whole, bits = {}, math.floor(count / 8), count % 8 + for index = 1, 4 do + local source = index + whole + local value = source <= 4 and (a[source] * (2 ^ bits)) % 256 or 0 + if bits > 0 and source < 4 then value = value + math.floor(a[source + 1] / (2 ^ (8 - bits))) end + result[index] = value + end + return result +end + +local function wordRotateRight(a, count) + return wordXor(wordShiftRight(a, count), wordShiftLeft(a, 32 - count)) +end + +local function wordAdd(...) + local values, result, carry = {...}, {}, 0 + for index = 4, 1, -1 do + local total = carry + for valueIndex = 1, #values do total = total + values[valueIndex][index] end + result[index], carry = total % 256, math.floor(total / 256) + end + return result +end + +local function wordHex(value) + local chars = {} + for index = 1, 4 do + local byte = value[index] + chars[#chars + 1] = HEX:sub(math.floor(byte / 16) + 1, math.floor(byte / 16) + 1) + chars[#chars + 1] = HEX:sub((byte % 16) + 1, (byte % 16) + 1) + end + return table.concat(chars) +end + +local SHA_K_HEX = { + "428a2f98","71374491","b5c0fbcf","e9b5dba5","3956c25b","59f111f1","923f82a4","ab1c5ed5", + "d807aa98","12835b01","243185be","550c7dc3","72be5d74","80deb1fe","9bdc06a7","c19bf174", + "e49b69c1","efbe4786","0fc19dc6","240ca1cc","2de92c6f","4a7484aa","5cb0a9dc","76f988da", + "983e5152","a831c66d","b00327c8","bf597fc7","c6e00bf3","d5a79147","06ca6351","14292967", + "27b70a85","2e1b2138","4d2c6dfc","53380d13","650a7354","766a0abb","81c2c92e","92722c85", + "a2bfe8a1","a81a664b","c24b8b70","c76c51a3","d192e819","d6990624","f40e3585","106aa070", + "19a4c116","1e376c08","2748774c","34b0bcb5","391c0cb3","4ed8aa4a","5b9cca4f","682e6ff3", + "748f82ee","78a5636f","84c87814","8cc70208","90befffa","a4506ceb","bef9a3f7","c67178f2", +} +local SHA_K = {} +for index = 1, #SHA_K_HEX do SHA_K[index] = wordFromHex(SHA_K_HEX[index]) end + +local function sha256(message) + local bytes = {} + for index = 1, #message do bytes[index] = message:byte(index) end + local bitLength = #bytes * 8 + bytes[#bytes + 1] = 0x80 + while (#bytes % 64) ~= 56 do bytes[#bytes + 1] = 0 end + local lengthBytes = {} + for index = 8, 1, -1 do + lengthBytes[index] = bitLength % 256 + bitLength = math.floor(bitLength / 256) + end + for index = 1, 8 do bytes[#bytes + 1] = lengthBytes[index] end + + local h = {} + for index, value in ipairs({"6a09e667","bb67ae85","3c6ef372","a54ff53a","510e527f","9b05688c","1f83d9ab","5be0cd19"}) do + h[index] = wordFromHex(value) + end + for offset = 1, #bytes, 64 do + local w = {} + for index = 0, 15 do + local base = offset + index * 4 + w[index] = {bytes[base], bytes[base + 1], bytes[base + 2], bytes[base + 3]} + end + for index = 16, 63 do + local x, y = w[index - 15], w[index - 2] + local s0 = wordXor(wordRotateRight(x, 7), wordRotateRight(x, 18), wordShiftRight(x, 3)) + local s1 = wordXor(wordRotateRight(y, 17), wordRotateRight(y, 19), wordShiftRight(y, 10)) + w[index] = wordAdd(w[index - 16], s0, w[index - 7], s1) + end + local a,b,c,d,e,f,g,hh = h[1],h[2],h[3],h[4],h[5],h[6],h[7],h[8] + for index = 0, 63 do + local s1 = wordXor(wordRotateRight(e, 6), wordRotateRight(e, 11), wordRotateRight(e, 25)) + local choice = wordXor(wordAnd(e, f), wordAnd(wordNot(e), g)) + local t1 = wordAdd(hh, s1, choice, SHA_K[index + 1], w[index]) + local s0 = wordXor(wordRotateRight(a, 2), wordRotateRight(a, 13), wordRotateRight(a, 22)) + local majority = wordXor(wordAnd(a, b), wordAnd(a, c), wordAnd(b, c)) + local t2 = wordAdd(s0, majority) + hh,g,f,e,d,c,b,a = g,f,e,wordAdd(d, t1),c,b,a,wordAdd(t1, t2) + end + h[1],h[2],h[3],h[4] = wordAdd(h[1],a),wordAdd(h[2],b),wordAdd(h[3],c),wordAdd(h[4],d) + h[5],h[6],h[7],h[8] = wordAdd(h[5],e),wordAdd(h[6],f),wordAdd(h[7],g),wordAdd(h[8],hh) + end + local parts = {} + for index = 1, 8 do parts[index] = wordHex(h[index]) end + return table.concat(parts) +end + +local function packedHex(bytes, littleEndian) + if type(bytes) ~= "string" or #bytes ~= 4 then return nil end + local chars = {} + if littleEndian then + for index = 4, 1, -1 do chars[#chars + 1] = wordHex({0, 0, 0, bytes:byte(index)}):sub(7, 8) end + else + for index = 1, 4 do chars[#chars + 1] = wordHex({0, 0, 0, bytes:byte(index)}):sub(7, 8) end + end + return table.concat(chars) +end + +local onePacked = VFS.PackF32(1.0) +local oneNativeHex = packedHex(onePacked, false) +local PACK_LITTLE_ENDIAN = oneNativeHex == "0000803f" +local PACK_ENDIAN_VALID = PACK_LITTLE_ENDIAN or oneNativeHex == "3f800000" +local PACK_ZERO_VALID = PACK_ENDIAN_VALID + and packedHex(VFS.PackF32(0.0), PACK_LITTLE_ENDIAN) == "00000000" + and packedHex(VFS.PackF32(-1 / math.huge), PACK_LITTLE_ENDIAN) == "80000000" + +local function float32Hex(value) + if type(value) ~= "number" or value ~= value or value == math.huge or value == -math.huge then + return nil, "nonfinite-float" + end + if not PACK_ENDIAN_VALID then return nil, "float-pack-unsupported" end + local encoded = packedHex(VFS.PackF32(value), PACK_LITTLE_ENDIAN) + if not encoded then return nil, "float-pack-unsupported" end + if value == 0 and not PACK_ZERO_VALID then return nil, "negative-zero-unsupported" end + return encoded +end + +local function integerInRange(value, minimum, maximum) + return type(value) == "number" and value == math.floor(value) and value >= minimum and value <= maximum +end + +local function splitLines(message) + if message:sub(-1) ~= "\n" then return nil end + local lines, position = {}, 1 + while position <= #message do + local newline = message:find("\n", position, true) + if not newline then return nil end + if newline - position + 1 > MAX_LINE_BYTES then return nil end + lines[#lines + 1] = message:sub(position, newline - 1) + position = newline + 1 + end + return lines +end + +local function parseRequest(message) + if type(message) ~= "string" or #message > MAX_BYTES or message:find("\0", 1, true) then return nil end + for index = 1, #message do if message:byte(index) > 127 then return nil end end + local lines = splitLines(message) + if not lines or #lines ~= 6 or lines[6] ~= "end" then return nil end + local length = lines[2]:match("^length=(%d%d%d%d%d%d%d%d)$") + local bridge = lines[3]:match("^bridge=([%w%-]+)$") + local domain = lines[4]:match("^domain=([%l]+)$") + local unitText = lines[5]:match("^unit=(%-?%d+)$") + local unit = unitText and tonumber(unitText) or nil + if not length or tonumber(length) ~= #message or not bridge or not domain or not integerInRange(unit, 0, 2147483647) then return nil end + if domain ~= "production" and domain ~= "rally" then return nil end + local reason + if lines[1] ~= REQUEST_MAGIC then reason = lines[1]:sub(1, #REQUEST_ROUTE) == REQUEST_ROUTE and "wrong-version" or "malformed" + elseif bridge ~= BRIDGE then reason = "missing-bridge" end + return { bridge=bridge, domain=domain, unit=unit, reason=reason } +end + +local function response(requestHash, domain, unit, status, reason, rows) + local lines = { RESPONSE_MAGIC, "length=00000000", "bridge=" .. BRIDGE, "request-sha256=" .. requestHash, + "status=" .. status, "domain=" .. domain, "unit=" .. tostring(unit) } + if reason then lines[#lines + 1] = "reason=" .. reason end + lines[#lines + 1] = "count=" .. tostring(#rows) + for _, row in ipairs(rows) do lines[#lines + 1] = "row=" .. row end + lines[#lines + 1] = "end" + local document = table.concat(lines, "\n") .. "\n" + document = document:gsub("length=00000000", string.format("length=%08d", #document), 1) + if #document > MAX_BYTES then return nil end + for _, line in ipairs(lines) do if #line + 1 > MAX_LINE_BYTES then return nil end end + return document +end + +local function unavailable(message, request, reason) + return response(sha256(message), request.domain, request.unit, "unavailable", reason, {}) +end + +local function encodeRows(domain, commands) + if type(commands) ~= "table" then return nil, "malformed" end + if #commands > MAX_ENTRIES then return nil, "overflow" end + local rows, totalParams = {}, 0 + for index = 1, #commands do + local command = commands[index] + local options = type(command) == "table" and command.options or nil + local params = type(command) == "table" and command.params or nil + if type(options) ~= "table" or type(params) ~= "table" + or not integerInRange(command.id, -2147483648, 2147483647) + or not integerInRange(options.coded, 0, 65535) + or not integerInRange(command.tag, -2147483648, 2147483647) then return nil, "malformed" end + if #params > MAX_PARAMS then return nil, "overflow" end + totalParams = totalParams + #params + if totalParams > MAX_TOTAL_PARAMS then return nil, "overflow" end + local encoded = {} + for paramIndex = 1, #params do + local bits, reason = float32Hex(params[paramIndex]) + if not bits then return nil, reason end + encoded[paramIndex] = bits + end + local row = table.concat({domain, tostring(command.id), tostring(options.coded), tostring(command.tag), table.concat(encoded, ",")}, "|") + if #row + 5 > MAX_LINE_BYTES then return nil, "oversize" end + rows[index] = row + end + return rows +end + +function gadget:RecvSkirmishAIMessage(aiTeam, message) + if type(message) ~= "string" or message:sub(1, #REQUEST_ROUTE) ~= REQUEST_ROUTE then return nil end + local request = parseRequest(message) + if not request then return nil end + if request.reason then return unavailable(message, request, request.reason) end + if not Spring.ValidUnitID(request.unit) or Spring.GetUnitTeam(request.unit) ~= aiTeam then + return unavailable(message, request, "wrong-team") + end + local unitDefID = Spring.GetUnitDefID(request.unit) + if not unitDefID or not UnitDefs[unitDefID] or UnitDefs[unitDefID].isFactory ~= true then + return unavailable(message, request, "not-factory") + end + local commands + if request.domain == "production" then commands = Spring.GetFactoryCommands(request.unit, PROBE_ENTRIES) + else commands = Spring.GetUnitCommands(request.unit, PROBE_ENTRIES) end + local rows, reason = encodeRows(request.domain, commands) + if not rows then return unavailable(message, request, reason) end + local result = response(sha256(message), request.domain, request.unit, "ok", nil, rows) + if result then + -- Read-only optional handshake, after a successfully encoded real empty response. + -- No orders, no wire/response changes; absent/inert without the selected fixture. + local options = Spring.GetModOptions() or {} + local observed = GG.BARCPrearmEmpty + if request.domain == "production" and #rows == 0 and observed + and observed.factory == request.unit and observed.team == aiTeam + and observed.nonce == options.highbar_barc_prearm_nonce + and (options.highbar_barc_stock_fixture == "1" or options.highbar_barc_stock_fixture == 1 + or options.highbar_barc_stock_fixture == true) and observed.observedFrame == nil then + observed.observedFrame = Spring.GetGameFrame() + end + return result + end + return unavailable(message, request, "oversize") +end diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/test_prearm_observation.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/test_prearm_observation.py new file mode 100644 index 0000000..572ac0f --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/test_prearm_observation.py @@ -0,0 +1,22 @@ +"""Pure controlled Lua-call semantic model; no Lua/native execution or evidence.""" +import unittest,pathlib,hashlib +ROOT=pathlib.Path(__file__).resolve().parents[1]/'LuaRules/Gadgets' +class Fixture(unittest.TestCase): + def model(self,frames,observed=None,options=0): + phase='actors';orders=[];factory=10 + for frame in frames: + if phase=='actors':phase='await-empty';continue + if phase=='await-empty'and observed is not None and frame>observed: + phase='passive' + for ordinal in range(1,4):orders.append((factory,2,1 if options==0 else 5,options,ordinal,frame)) + return orders + def test_empty_before_one_shot_plain_seed(self): + self.assertEqual(self.model(range(20)),[]);orders=self.model(range(20),observed=2);self.assertEqual(len(orders),3);self.assertTrue(all(x[2:4]==(1,0)and x[-1]==3 for x in orders));self.assertNotEqual(sum(x[2]for x in self.model(range(20),2,32)),3) + def test_actual_source_gate_and_passive_transition(self): + text=(ROOT/'barc_stock_queue_fixture.lua').read_text();self.assertIn('frame > observed.observedFrame',text);self.assertLess(text.index('phase = "passive"'),text.index('Spring.GiveOrderToUnit'));self.assertEqual(text.count('Spring.GiveOrderToUnit('),1);self.assertIn('VFS.CalculateHash(bytes, 1)',text) + def test_observer_hook_preserves_wire_source(self): + old=pathlib.Path('/home/developer/.local/share/fs-gg-private/bar-stock-policy968-plain-fixture-successor-20261003/packet/runtime-data/AI/Skirmish/highBar/stable/barc-stock-observer/LuaRules/Gadgets/barc_stock_queue_reader.lua').read_text();new=(ROOT/'barc_stock_queue_reader.lua').read_text();a=new.index(' if result then\n');z=new.index(' return unavailable(message, request, "oversize")',a);self.assertEqual(new[:a]+' if result then return result end\n'+new[z:],old) + hook=new[a:z];self.assertNotIn('GiveOrder',hook);self.assertIn('#rows == 0',hook);self.assertIn('observed.observedFrame == nil',hook) + def test_unsynced_only_configuration(self): + text=(ROOT/'barc_stock_queue_fixture.lua').read_text();start=text.index('if not gadgetHandler:IsSyncedCode() then');end=text.index('local OPT_ENABLE',start);self.assertIn('Spring.GetAIInfo(team)',text[start:end]);self.assertNotIn('Spring.GetAIInfo(',text[end:]);self.assertNotIn('Spring.GetConfigInt(',text[end:]);self.assertNotIn('SYNCED_NOSHORTNAME',text[end:]);self.assertNotEqual(hashlib.sha512(b'controlled').hexdigest(),hashlib.sha256(b'controlled').hexdigest()) +if __name__=='__main__':unittest.main() diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/buffered_writer.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/buffered_writer.py new file mode 100644 index 0000000..1188879 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/buffered_writer.py @@ -0,0 +1,51 @@ +"""Public Recoil2639 FileSink-style libc full-buffered ordinary-file writer. +FileSink.cpp:123-130,214-223: fopen(w), setvbuf(_IOFBF,8192), fprintf newline. +No severity-triggered fflush, engine, content, or private inputs. +""" +import base64,ctypes,os,sys +ATLAS=(b'CTextureRenderAtlas::CreateAtlasTexture()[0] atlas=IconsAtlas_0 FBO::ready=1',b'CTextureRenderAtlas::CreateAtlasTexture()[1] atlas=IconsAtlas_0 atlasRendered=0') +ROOTS=(b'[DataDirLocater::Check] Using read-write data directory: /public/write/',b'[DataDirLocater::Check] Using read-only data directory: /public/data/',b'[DataDirLocater::Check] Isolation Mode!',b'[SpringApp::Init] Spring writeable configuration directory: /public/write/') +class BufferedWriter: + def __init__(self,path): + self.libc=ctypes.CDLL(None) + self.libc.fopen.argtypes=(ctypes.c_char_p,ctypes.c_char_p);self.libc.fopen.restype=ctypes.c_void_p + self.libc.setvbuf.argtypes=(ctypes.c_void_p,ctypes.c_void_p,ctypes.c_int,ctypes.c_size_t) + self.libc.fprintf.argtypes=(ctypes.c_void_p,ctypes.c_char_p) + self.libc.fflush.argtypes=(ctypes.c_void_p,);self.libc.fclose.argtypes=(ctypes.c_void_p,) + self.stream=self.libc.fopen(os.fsencode(path),b'w');assert self.stream + os.chmod(path,0o600) + assert self.libc.setvbuf(self.stream,None,0,8192)==0 # Linux _IOFBF + self.path=path;self.records=0 + def record(self,text): + assert b'\x00' not in text and b'\n' not in text + assert self.libc.fprintf(self.stream,b'%s\n',ctypes.c_char_p(text))>0 + self.records+=1 + def partial_eof(self): + previous=os.stat(self.path).st_size + for index in range(512): # <=64KiB public records per command + self.record(ATLAS[self.records%2]);size=os.stat(self.path).st_size + if size>previous: + with open(self.path,'rb') as reader:reader.seek(-1,2);last=reader.read(1) + if last!=b'\n':return size + previous=size + raise RuntimeError('no partial EOF in bounded public replay') + def close(self):self.libc.fclose(self.stream) +def main(): + os.umask(0o077);writer=BufferedWriter(sys.argv[1]) + try: + records=ROOTS if len(sys.argv)==2 else base64.b64decode(sys.argv[2]).splitlines() + for record in records:writer.record(record) + writer.partial_eof() + if len(sys.argv)>2:assert writer.libc.fflush(writer.stream)==0 + print('ready',flush=True) + for command in sys.stdin: + command=command.strip() + if command=='grow':writer.partial_eof() + elif command=='growflush': + writer.partial_eof();assert writer.libc.fflush(writer.stream)==0 + elif command=='flush':assert writer.libc.fflush(writer.stream)==0 + elif command=='close':break + else:raise RuntimeError('closed writer command') + print('done',flush=True) + finally:writer.close() +if __name__=='__main__':main() diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/capture_custody.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/capture_custody.py new file mode 100644 index 0000000..eb87da6 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/capture_custody.py @@ -0,0 +1,77 @@ +"""Exact immutable capture inputs copied to the fresh owned execution capsule.""" +import copy,hashlib,math,os,stat,time +from pathlib import Path +from private_io import SHA,atomic_bytes_new,atomic_new,canonical,components,hash_artifact,need,read_bytes,read_json +from settings_custody import inventory +from growing_log import _source_set +MAX_FILES=8192 +MAX_TOTAL=128*1024*1024 +MAX_FILE=64*1024*1024 +SPEC='tests/Broker.NativeProof/tactical-native-journey.spec.js' + +def _names(root): + names=set() + for base,dirs,files in os.walk(root,followlinks=False): + for name in dirs:need(not (Path(base)/name).is_symlink(),'capture symlink directory') + for name in files: + p=Path(base)/name;need(not p.is_symlink(),'capture symlink file');names.add(str(p.relative_to(root)));need(len(names)<=MAX_FILES,'capture file census cap') + return names + +def _target(config,attempt): + attempt=components(str(attempt));need(str(attempt)==config['roots']['attemptRoot'],'capture attempt escape') + need(attempt.stat().st_uid==os.geteuid() and stat.S_IMODE(attempt.stat().st_mode)==0o700,'capture attempt custody') + browser=components(str(attempt/'browser'));need(browser.stat().st_uid==os.geteuid() and stat.S_IMODE(browser.stat().st_mode)==0o700,'capture browser root custody') + return browser/'execution-capsule' + +def prepare_capture_capsule(config,attempt,config_sha256,deadline): + need(type(deadline) in (int,float) and math.isfinite(deadline) and deadline<=time.monotonic()+180,'capture fixed operation deadline') + def checkpoint():need(time.monotonic()0,'host-dynamic-page-size') + ordinary=[];rows=[];objects=set();last_end=0 + for line in lines: + fields=line.split(None,5);name=fields[5] if len(fields)==6 else '' + if name!=NAME: + need(not name.startswith('/memfd:') and not name.endswith(' (deleted)'),'host-dynamic-unidentified-deleted-map') + ordinary.append(line);continue + match=ROW.fullmatch(line);need(match is not None,'host-dynamic-malformed-row') + begin,end,perms,offset,major,minor,inode,name=match.groups() + begin,end,offset,major,minor,inode=(int(begin,16),int(end,16),int(offset,16),int(major,16),int(minor,16),int(inode)) + need(perms in PROFILE['permissions'],'host-dynamic-permissions') + need(0=last_end and begin%page_size==end%page_size==0,'host-dynamic-range') + need(0<=offset1 and held['uid']==os.geteuid(),'host-dynamic-owned-process') + pid=held['pid'];need(os.stat(f'/proc/{pid}').st_uid==held['uid'] and int(start_ticks(pid))==int(held['startTicks']),'host-dynamic-process-reuse') + need(os.readlink(f'/proc/{pid}/exe')==executable,'host-dynamic-executable') + actual=os.stat(f'/proc/{pid}/exe');expected=os.stat(executable,follow_symlinks=False) + need(stat.S_ISREG(expected.st_mode) and (actual.st_dev,actual.st_ino)==(expected.st_dev,expected.st_ino),'host-dynamic-executable-inode') + return {'pid':pid,'startTicks':int(held['startTicks']),'uid':held['uid'],'executable':executable,'executableDevice':actual.st_dev,'executableInode':actual.st_ino} + +def _fd_stat(info): + return {'device':info.st_dev,'deviceMajor':os.major(info.st_dev),'deviceMinor':os.minor(info.st_dev),'inode':info.st_ino,'size':info.st_size,'mode':info.st_mode,'uid':info.st_uid,'nlink':info.st_nlink} + +def owner_metadata(held,executable,rows): + before=held_process(held,executable);pid=held['pid'];matches=[] + # Iterate boundedly rather than allocating an unbounded list of FD names. + with os.scandir(f'/proc/{pid}/fd') as entries: + for count,entry in enumerate(entries,1): + need(count<=PROFILE['fds'],'host-dynamic-fd-bound');need(entry.name.isascii() and entry.name.isdecimal(),'host-dynamic-fd-name') + path=f'/proc/{pid}/fd/{entry.name}' + try:link=os.readlink(path) + except FileNotFoundError:continue + need(len(os.fsencode(link))<=PROFILE['linkBytes'],'host-dynamic-link-bound') + if link!=NAME:continue + # Follow only the exact matched proc FD, for stat metadata, never data. + first=_fd_stat(os.stat(path));fdinfo=proc_bytes(pid,f'fdinfo/{entry.name}',PROFILE['fdinfoBytes']).decode('ascii') + need(os.readlink(path)==NAME,'host-dynamic-fd-link-race');last=_fd_stat(os.stat(path)) + need(first==last,'host-dynamic-fd-stat-race') + matches.append({'fd':int(entry.name),'link':link,'stat':first,'fdinfo':fdinfo}) + after=held_process(held,executable);need(before==after,'host-dynamic-process-race') + # Recheck every captured descriptor after census and process check. + for item in matches: + path=f"/proc/{pid}/fd/{item['fd']}" + need(os.readlink(path)==NAME and _fd_stat(os.stat(path))==item['stat'],'host-dynamic-owner-race') + need(held_process(held,executable)==before,'host-dynamic-final-process-race') + return validate_owner(rows,matches,before,after) + +def validate_owner(rows,matches,before,after): + """Pure metadata join also used by explicitly mocked source controls.""" + need(before==after,'host-dynamic-process-race');need(len(matches)<=PROFILE['fds'],'host-dynamic-fd-bound') + objects=set() + for item in matches: + info=item['stat'];need(type(item['fd']) is int and item['fd']>=0 and item['link']==NAME,'host-dynamic-fd-identity') + need(stat.S_ISREG(info['mode']) and info['uid']==before['uid'] and info['nlink']==0 and type(info['size']) is int and 0<=info['size']=0 and value['consumedRevision']<=value['validatedRevision']<=value['observedRevision'],'coherent growing log counters') + phases={'accepted':'validated','pending':'sampled','refused':'invalid','unknown':'unknown'} + need(status in phases and value['phase']==phases[status],'growing log status/phase mismatch') + previous=prior or {'validatedRevision':0,'validatedBoundary':None,'consumedRevision':0,'consumedBoundary':None,'consumedPrefix':None,'consumption':None} + need(value['consumedRevision']==previous['consumedRevision'] and value['consumedBoundary']==previous['consumedBoundary'] and value['consumedPrefix']==previous['consumedPrefix'] and value['consumption']==previous['consumption'],'candidate cannot manufacture consumption') + if status in ('accepted','pending'): + prefix=value['prefix'];cut=raw.rfind(b'\n')+1 + need(isinstance(prefix,dict) and set(prefix)=={'rawBytes','rawSha256','completeBytes','completeSha256','tailBytes','tailSha256','completeRecords','tailClass'},'closed raw/P/T descriptor') + expected={'rawBytes':len(raw),'rawSha256':hashlib.sha256(raw).hexdigest(),'completeBytes':cut,'completeSha256':hashlib.sha256(raw[:cut]).hexdigest(),'tailBytes':len(raw)-cut,'tailSha256':hashlib.sha256(raw[cut:]).hexdigest(),'completeRecords':raw.count(b'\n')} + need(all(type(prefix[key]) is type(item) and prefix[key]==item for key,item in expected.items()),'exact raw/P/T byte join') + need(type(prefix['tailClass']) is str and prefix['tailClass'] in ('empty','atlas','pending','pending-utf8','refused-nul','refused-utf8'),'closed F# tail class') + if status=='accepted':need(value['candidatePrefix']==prefix and prefix['completeRecords']>0 and prefix['tailClass'] in ('empty','atlas'),'safe F# tail/root candidate') + if status=='accepted': + need(value['observedRevision']==value['validatedRevision']==revision and value['validatedBoundary']==boundary and value['bytes']==sample_bytes and value['sha256']==sample_sha256 and value['stickyInvalid'] is False and value['reason'] is None,'fresh growing log decision') + elif status=='pending': + previous=prior or {'validatedRevision':0,'validatedBoundary':None,'consumedRevision':0,'consumedBoundary':None} + need(value['observedRevision']==revision and value['bytes']==sample_bytes and value['sha256']==sample_sha256 and value['stickyInvalid'] is False and type(value['reason']) is str and len(value['reason'])>0,'fresh pending growing log decision') + need(value['validatedRevision']==previous['validatedRevision'] and value['validatedBoundary']==previous['validatedBoundary'] and value['consumedRevision']==previous['consumedRevision'] and value['consumedBoundary']==previous['consumedBoundary'],'pending growing log history mismatch') + else: + need(value['stickyInvalid'] is True and type(value['reason']) is str and len(value['reason'])>0,'terminal growing log state mismatch') + return value + +class GrowingLog: + def __init__(self,path,process,fd,opened,writer): + self.path=str(path);self.process=dict(process);self.fd=fd;self.device=opened.st_dev;self.inode=opened.st_ino + self.previous=b'';self.revision=0;self.state=None;self.revoked=False;self.writer=writer;self.closure_sha256=None;self.failure_checkpoint='policy-scope-join';self.last_policy_observation=None;self.settlement_transcript=[];self.settlement_effects=[];self._reset_counters(None) + def _reset_counters(self,boundary): + self.counter_started=time.monotonic();self.counter_cause=None;self.counter_deadline=None + self.counters={'boundary':boundary,'probesBegun':0,'probesCompleted':0,'readOperations':0,'readCalls':0,'readCallsInOperation':0,'requestedReadBytes':0,'bytesReadInOperation':0,'evaluationsBegun':0,'evaluationsCompleted':0,'growthAfterEvaluation':0,'maximumObservedBytes':0,'sampleBytes':0,'completePrefixBytes':0,'tailBytes':0} + prefix=self.previous.rfind(b'\n')+1 + self.counters.update(sampleBytes=len(self.previous),completePrefixBytes=prefix,tailBytes=len(self.previous)-prefix) + def _counter_observation(self,check): + elapsed=max(0,int((time.monotonic()-self.counter_started)*1000000)) + return {'schema':'fsbar.barc-infolog-mechanical-observation/v1',**self.counters,'elapsedMicroseconds':elapsed,'settlementBudgetMicroseconds':None if self.counter_deadline is None else max(0,int((self.counter_deadline-self.counter_started)*1000000)),'terminalCheck':check,'terminalCause':self.counter_cause or ('settlement-deadline' if check=='infolog-record-settlement-deadline' else 'other'),'sampleSha256':hashlib.sha256(self.previous).hexdigest(),'nativeAcceptance':False} + def _retain_counter_observation(self,error,config): + check=getattr(error,'_barc_failure_observation',{}).get('check',self.failure_checkpoint) + observation=self._counter_observation(check) + error._barc_infolog_mechanical_observation=observation + # Separate mechanical evidence: never alters the typed failure projection + # or acceptance. Missing publication is explicitly visible on the error. + try: + need(self.counters['boundary'] in BOUNDARIES,'closed counter boundary') + name='infolog-mechanical-'+self.counters['boundary']+'.json' + root=components(config['roots']['attemptRoot']) + encoded=(json.dumps(observation,separators=(',',':'),allow_nan=False)+'\n').encode() + atomic_bytes_new(str(root/name),encoded,4096) + atomic_new(str(root/(name+'.receipt.json')),{'schema':'fsbar.barc-infolog-mechanical-receipt/v1','file':name,'bytes':len(encoded),'sha256':hashlib.sha256(encoded).hexdigest(),'sourceSetSha256':_source_set(config),'nativeAcceptance':False}) + error._barc_infolog_mechanical_retained=True + except Exception:error._barc_infolog_mechanical_retained=False + @classmethod + def acquire(cls,path,process): + check='infolog-path';fd=None + try: + path=components(str(canonical(str(path))));before=path.lstat() + check='infolog-parent';parent=path.parent.stat() + need(parent.st_uid==os.geteuid() and stat.S_ISDIR(parent.st_mode) and stat.S_IMODE(parent.st_mode)==0o700,'growing log private parent') + check='infolog-file-custody';need(stat.S_ISREG(before.st_mode) and before.st_uid==os.geteuid() and stat.S_IMODE(before.st_mode)==0o600 and before.st_nlink==1 and before.st_size<=MAX_LOG,'growing log custody/bound') + check='infolog-process';need(set(process)=={'pid','startTicks','uid'} and process['uid']==os.geteuid() and start_ticks(process['pid'])==process['startTicks'],'growing log producer identity') + check='infolog-open';fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_CLOEXEC|os.O_NONBLOCK) + check='infolog-descriptor-identity' + opened=os.fstat(fd);need((opened.st_dev,opened.st_ino,opened.st_uid,stat.S_IMODE(opened.st_mode),opened.st_nlink)==(before.st_dev,before.st_ino,before.st_uid,0o600,1),'growing log identity changed') + writer=cls._writer(process,path,opened) + check='infolog-final-generation' + need(start_ticks(process['pid'])==process['startTicks'],'growing log producer changed') + return cls(path,process,fd,opened,writer) + except BaseException as error: + failure=mark_failure(error,check) + if fd is not None: + try:os.close(fd) + except OSError:setattr(failure,'_barc_cleanup_unknown',True) + raise failure + @staticmethod + def _writer(process,path,opened,deadline=None): + def check_deadline(): + if deadline is not None:GrowingLog._deadline(deadline) + try: + check_deadline();pid=process['pid'];names=os.listdir(f'/proc/{pid}/fd');check_deadline();need(len(names)<=MAX_FDS,'growing log writer fd bound');matches=[] + for name in names: + check_deadline() + if not name.isdigit():continue + try: + current=os.stat(f'/proc/{pid}/fd/{name}') + check_deadline() + if (current.st_dev,current.st_ino)!=(opened.st_dev,opened.st_ino):continue + value=proc_bytes(pid,f'fdinfo/{name}',4096).decode('ascii') + check_deadline() + flags=re.search(r'^flags:\s+([0-7]+)$',value,re.MULTILINE);position=re.search(r'^pos:\s+([0-9]+)$',value,re.MULTILINE) + if flags and position: + bits=int(flags[1],8) + if bits&os.O_ACCMODE in (os.O_WRONLY,os.O_RDWR):matches.append({'fd':int(name),'flags':bits,'position':int(position[1])}) + except FileNotFoundError:continue + except BaseException as error:raise mark_failure(error,'infolog-writer-census') + try: + check_deadline() + need(len(matches)==1,'growing log writer unavailable or ambiguous') + need(start_ticks(pid)==process['startTicks'],'growing log writer generation changed') + check_deadline() + return matches[0] + except BaseException as error:raise mark_failure(error,'infolog-writer-match') + def close(self): + if self.fd is not None: + os.close(self.fd);self.fd=None + @staticmethod + def _deadline(deadline): + if time.monotonic()>=deadline: + raise mark_failure(Refused('growing log record settlement deadline'),'infolog-record-settlement-deadline','deadline',None) + def _read_exact(self,length,deadline=None): + chunks=[];offset=0;calls=0 + self.counters['readOperations']+=1;self.counters['readCallsInOperation']=0;self.counters['requestedReadBytes']=length;self.counters['bytesReadInOperation']=0 + while offset=MAX_PREAD_CALLS: + self.counter_cause='exact-read-call-cap' + raise mark_failure(Refused('growing log record exact-read exhausted'),'infolog-record-settlement-exhausted','refused',None) + self.counters['readCalls']+=1;self.counters['readCallsInOperation']+=1 + block=os.pread(self.fd,min(65536,length-offset),offset);need(block,'growing log truncated');chunks.append(block);offset+=len(block) + calls+=1;self.counters['bytesReadInOperation']=offset + if deadline is not None:self._deadline(deadline) + return b''.join(chunks) + def _sample(self,deadline): + try:return self._sample_impl(deadline) + except BaseException: + self.revoked=True;raise + def _custody(self, expected=None, exact_size=False, final=False, deadline=None): + def checkpoint(name): + if final:self.failure_checkpoint='infolog-final-'+name + if deadline is not None:self._deadline(deadline) + checkpoint('scope') + need(not self.revoked and self.fd is not None,'growing log scope revoked or closed') + pid=self.process['pid'] + checkpoint('process') + need(os.stat(f'/proc/{pid}').st_uid==self.process['uid']==os.geteuid() and start_ticks(pid)==self.process['startTicks'],'growing log producer changed') + checkpoint('path');path=components(self.path) + checkpoint('parent');parent=path.parent.stat() + need(parent.st_uid==os.geteuid() and stat.S_ISDIR(parent.st_mode) and stat.S_IMODE(parent.st_mode)==0o700,'growing log private parent changed') + checkpoint('named-identity');named=path.lstat() + need((named.st_dev,named.st_ino)==(self.device,self.inode),'growing log named identity changed') + checkpoint('descriptor-identity');opened=os.fstat(self.fd) + need((opened.st_dev,opened.st_ino)==(self.device,self.inode),'growing log descriptor identity changed') + checkpoint('file-custody') + need(stat.S_ISREG(named.st_mode) and named.st_uid==os.geteuid() and stat.S_IMODE(named.st_mode)==0o600 and named.st_nlink==1,'growing log custody') + self.counters['maximumObservedBytes']=max(self.counters['maximumObservedBytes'],opened.st_size) + checkpoint('size-cap');need(opened.st_size<=MAX_LOG,'growing log bound') + writer=self._writer(self.process,path,opened,deadline) + if final:self.final_read_started=time.monotonic() + if expected is not None: + checkpoint('size-regression');need(opened.st_size>=len(expected) and (not exact_size or opened.st_size==len(expected)),'growing log changed after policy') + checkpoint('prefix-read');actual=self._read_exact(len(expected),deadline) + checkpoint('prefix-drift');need(actual==expected,'growing log prefix changed after read') + if final: + # The size at L must be observed after the complete prefix reread. + # Never return the earlier pre-read fstat as a final horizon. + read_end=time.monotonic() + # Recheck permanent changes during the reread, then observe size last. + self._custody(deadline=deadline) + opened=os.fstat(self.fd);at=time.monotonic() + need((opened.st_dev,opened.st_ino)==(self.device,self.inode) and opened.st_size>=len(expected),'growing log final identity/size regression') + need(opened.st_size<=MAX_LOG,'growing log bound') + self.final_observation={'readStartMicroseconds':max(0,int((self.final_read_started-self.counter_started)*1000000)),'readEndMicroseconds':max(0,int((read_end-self.counter_started)*1000000)),'linearizedMicroseconds':max(0,int((at-self.counter_started)*1000000)),'rawBytes':opened.st_size} + return opened,writer + def _sample_impl(self,deadline): + self._deadline(deadline) + opened,_=self._custody(deadline=deadline) + need(len(self.previous)<=opened.st_size,'growing log truncated') + length=opened.st_size;self.counters['maximumObservedBytes']=max(self.counters['maximumObservedBytes'],length);raw=self._read_exact(length,deadline) + # This second retained-FD read closes the permanent overwrite window + # after the first read. The custody check also reopens no authority: + # any failure makes this object sticky-revoked in _sample. + after,writer=self._custody(raw,False,deadline=deadline) + need(after.st_size>=length,'growing log truncated during sample') + intact=raw.startswith(self.previous) + need(intact,'growing log prefix changed') + self._deadline(deadline) + return raw,intact,writer + def _settle(self,deadline,probes,raw_mode=False,pending_raw=None): + while probes0,'growing log policy stdin stalled');offset+=written + except BrokenPipeError: + offset=len(encoded) + if offset==len(encoded): + selector.unregister(input_fd);process.stdin.close();stdin_open=False + elif key.data=='stdout' and stdout_open: + block=os.read(output_fd,min(65536,131073-transport_count)) + if block: + transport_count+=len(block);output.extend(block);need(transport_count<=131072,'growing log policy output bound') + if not ready and b'\n' in output: + line,tail=bytes(output).split(b'\n',1) + value=json.loads(line.decode('utf-8'),object_pairs_hook=_unique) + need(set(value)=={'schema','invocationId','closureSha256','pid','startTicks','uid','phase'} and value['schema']=='fsbar.barc-runtime-evidence-policy-ready/v2' and value['phase']=='ready' and value['invocationId']==invocation and value['closureSha256']==closure_sha and value['pid']==process.pid and value['startTicks']==start_ticks(process.pid) and value['uid']==os.geteuid(),'policy ready join') + ready=True;ready_line=line;ready_value=value;output=bytearray(tail);selector.register(input_fd,selectors.EVENT_WRITE,'stdin');input_registered=True + else: + selector.unregister(output_fd);process.stdout.close();stdout_open=False + if not ready and stdin_open: + process.stdin.close();stdin_open=False + elif key.data=='stderr' and stderr_open: + block=os.read(error_fd,min(65536,131073-transport_count)) + if block: + transport_count+=len(block);diagnostic.extend(block);need(transport_count<=131072,'growing log policy combined output bound') + else: + selector.unregister(error_fd);process.stderr.close();stderr_open=False + if process.poll() is not None and stdin_open: + if input_registered:selector.unregister(input_fd) + process.stdin.close();stdin_open=False + remaining=policy_deadline-time.monotonic() + if remaining<=0:raise subprocess.TimeoutExpired([path],5) + code=process.wait(timeout=remaining);need(code in (0,2),'growing log policy execution') + need(ready and ready_line is not None,'policy readiness unavailable') + check='policy-completion' + try:final=json.loads(bytes(output).decode('utf-8'),object_pairs_hook=_unique) + except (UnicodeError,json.JSONDecodeError) as error:raise Refused('invalid policy completion') from error + need(set(final)=={'schema','invocationId','closureSha256','pid','startTicks','uid','phase','result'} and final['schema']=='fsbar.barc-runtime-evidence-policy-completed/v2' and final['invocationId']==invocation and final['closureSha256']==closure_sha and final['pid']==process.pid and final['startTicks']==ready_value['startTicks'] and final['uid']==os.geteuid() and final['phase']=='completed','policy completion join') + self.last_policy_observation=_policy_observation(bytes(diagnostic)) if diagnostic else None + self.last_invocation={'invocationId':invocation,'closureSha256':closure_sha,'pid':process.pid,'startTicks':ready_value['startTicks'],'uid':os.geteuid(),'readyPhase':ready_value['phase'],'completedPhase':final['phase']} + return (json.dumps(final['result'],separators=(',',':'))+'\n').encode() + except BaseException as error: + observation=_policy_observation(bytes(diagnostic)) if diagnostic else None + if observation is not None:check=POLICY_TO_MECHANICAL[observation['checkpoint']] + outcome='deadline' if isinstance(error,subprocess.TimeoutExpired) else None + if process is not None and process.poll() is None: + process.kill() + try:process.wait(timeout=max(0,policy_deadline-time.monotonic())) + except subprocess.TimeoutExpired as settlement:raise mark_failure(Refused('growing log policy child settlement unknown'),check,'unexpected',observation) from settlement + raise mark_failure(error,check,outcome,observation) + finally: + if selector is not None:selector.close() + if process is not None and process.stdin is not None and not process.stdin.closed:process.stdin.close() + if process is not None and process.stdout is not None and not process.stdout.closed:process.stdout.close() + if process is not None and process.stderr is not None and not process.stderr.closed:process.stderr.close() + def consume(self,boundary,config,deadline): + self._reset_counters(boundary) + try:return self._consume_impl(boundary,config,deadline) + except BaseException as error: + self.revoked=True + error=mark_failure(error,self.failure_checkpoint) + if self.state is not None and not self.state['stickyInvalid']: + before=self.state;fact=getattr(error,'_barc_failure_observation',{}) + unavailable=fact.get('outcome')=='os-unavailable' + self.state={**before,'phase':'unknown' if unavailable else 'invalid','stickyInvalid':True,'candidatePrefix':None,'reason':fact.get('check',self.failure_checkpoint)} + self.settlement_effects.append({'kind':'revoke','unavailable':unavailable,'reason':self.state['reason'],'beforeState':before,'afterState':self.state}) + self._retain_counter_observation(error,config) + raise error + def _consume_impl(self,boundary,config,deadline): + need(boundary in BOUNDARIES and isinstance(deadline,(int,float)),'closed growing log boundary/deadline') + policy_deadline=min(deadline,self.counter_started+5);self.counter_deadline=policy_deadline + attempt_id=1 if self.state is None else self.state['attemptId']+1 + frozen_deadline=min(5000000,max(0,int((policy_deadline-self.counter_started)*1000000))) + self.settlement_effects.append({'kind':'boundary','boundary':boundary,'attemptId':attempt_id,'deadlineMicroseconds':frozen_deadline,'beforeState':self.state}) + policy=config['artifacts']['runtimeEvidencePolicy'];closure=config['artifacts']['runtimeEvidencePolicyClosure'] + self.failure_checkpoint='policy-artifact';hash_artifact(policy['path'],policy['sha256'],268435456) + self.failure_checkpoint='policy-closure-precheck';closure_value=self._closure(closure['path'],closure['sha256'],policy_deadline) + apphost=next(row for row in closure_value['managed'] if row['role']=='apphost') + self.failure_checkpoint='policy-apphost-join' + need((policy['path'],policy['sha256'])==(apphost['path'],apphost['sha256']),'launched apphost/closure join') + self.failure_checkpoint='policy-scope-join' + if self.closure_sha256 is None:self.closure_sha256=closure['sha256'] + need(self.closure_sha256==closure['sha256'],'policy closure changed within growing-log scope') + expected={'runId':config['runId'],'sourceSetSha256':_source_set(config),'apphostSha256':policy['sha256'],'closureSha256':closure['sha256'],'writeRoot':config['commands']['engine'][5],'dataRoot':config['commands']['engine'][3]} + last=None;probes=0 + for _ in range(MAX_SAMPLES): + need(time.monotonic()0,'growing log policy deadline') + self.failure_checkpoint='policy-transport';self.last_policy_observation=None + self.counters['evaluationsBegun']+=1 + output=self._run_policy(policy['path'],closure['path'],closure['sha256'],encoded,policy_deadline) + self.counters['evaluationsCompleted']+=1 + try:value=json.loads(output.decode('utf-8'),object_pairs_hook=_unique,parse_constant=lambda _:(_ for _ in ()).throw(Refused('nonfinite policy JSON'))) + except (UnicodeError,json.JSONDecodeError) as error:raise Refused('invalid growing log policy result') from error + self.failure_checkpoint='policy-result-join' + need(set(value)=={'schema','status','state'} and value['schema']=='fsbar.barc-growing-log-policy-result/v3' and value['status'] in ('accepted','pending','refused','unknown') and isinstance(value['state'],dict),'closed growing log policy result') + identity=dict(expected);identity.update({'pid':self.process['pid'],'startTicks':self.process['startTicks'],'uid':self.process['uid'],'device':str(self.device),'inode':str(self.inode),'path':self.path}) + sample_sha256=hashlib.sha256(raw).hexdigest() + next_state=_closed_state(value['state'],value['status'],boundary,identity,self.revision,len(raw),sample_sha256,self.state,raw);last=value['status'] + need(next_state['attemptId']==attempt_id and next_state['probeCount']==probes and next_state['evaluationCount']==self.counters['evaluationsCompleted'] and next_state['deadlineMicroseconds']==frozen_deadline,'frozen boundary budget/result join') + before_state=self.state + self.state=next_state + # Qualification consumes this bounded in-memory trace to prove the + # actual policy transition and its position among settlement probes. + self.settlement_effects.append({'kind':'policy','boundary':boundary,'status':last,'expected':expected,'observation':{key:request['observation'][key] for key in ('pid','startTicks','uid','device','inode','path','revision','bytes','sha256','attemptId','probeCount','evaluationCount','deadlineMicroseconds')},'prefix':next_state['prefix'],'rawBase64':request['observation']['logBase64'],'invocation':self.last_invocation,'beforeState':before_state,'afterState':next_state}) + # This is only a scoped candidate. Growth + # observed after policy evaluation receives a new revision instead. + if last in ('refused','unknown'): + self.failure_checkpoint='policy-result-join';self.revoked=True + raise mark_failure(Refused('growing log policy refused or unknown'),self.failure_checkpoint,'policy-nonaccepted',self.last_policy_observation) + self.failure_checkpoint='infolog-final-refresh';current,_=self._custody(raw,False,True,policy_deadline) + self.settlement_effects.append({'kind':'final-observation','boundary':boundary,'revision':self.revision,**self.final_observation,'rawSha256':sample_sha256}) + self.failure_checkpoint='policy-final-precheck' + hash_artifact(policy['path'],policy['sha256'],268435456) + self._closure(closure['path'],closure['sha256'],policy_deadline) + need(time.monotonic()0,'proc positive byte bound') + chunks=[];length=0 + with open(f'/proc/{pid}/{name}','rb',buffering=0) as stream: + while length<=maximum: + block=stream.read(min(65536,maximum+1-length)) + if not block:break + chunks.append(block);length+=len(block) + need(length<=maximum,'proc byte bound');return b''.join(chunks) + +def start_ticks(pid): + value=proc_bytes(pid,'stat',16384).decode();return value.rsplit(') ',1)[1].split()[19] +def verify_process(process,engine,plugin,producer_path=None): + need(set(process)=={'pid','startTicks','uid'},'closed process identity') + pid=process['pid'];need(type(pid) is int and pid>1 and process['uid']==os.geteuid(),'owned selected process') + need(os.stat(f'/proc/{pid}').st_uid==os.geteuid() and start_ticks(pid)==process['startTicks'],'PID/start mismatch') + need(os.readlink(f'/proc/{pid}/exe')==engine['path'],'runtime executable mismatch') + actual_exe=os.stat(f'/proc/{pid}/exe');expected_exe=os.stat(engine['path'],follow_symlinks=False) + need((actual_exe.st_dev,actual_exe.st_ino)==(expected_exe.st_dev,expected_exe.st_ino),'runtime executable inode mismatch') + hash_artifact(engine['path'],engine['sha256']);info=hash_artifact(plugin['path'],plugin['sha256']) + lines=proc_bytes(pid,'maps',1048576).decode().splitlines();matches=[] + for line in lines: + fields=line.split(None,5) + if len(fields)==6 and fields[5]==plugin['path']: + matches.append(fields) + need(matches and all(int(fields[4])==info.st_ino and fields[3]==f'{os.major(info.st_dev):02x}:{os.minor(info.st_dev):02x}' for fields in matches),'mapped module inode mismatch') + if producer_path is not None: + target=os.stat(producer_path,follow_symlinks=False);names=os.listdir(f'/proc/{pid}/fd');need(len(names)<=256,'producer fd bound') + found=False + for name in names: + try: + current=os.stat(f'/proc/{pid}/fd/{name}') + if (current.st_dev,current.st_ino)==(target.st_dev,target.st_ino): + info=proc_bytes(pid,f"fdinfo/{name}",4096).decode() + flags=re.search(r"^flags:\s+([0-7]+)$",info,re.MULTILINE) + if flags: + value=int(flags[1],8) + if value&os.O_ACCMODE==os.O_WRONLY and value&os.O_APPEND: found=True + except FileNotFoundError: continue + need(found,'actual producer open fd unavailable') + need(start_ticks(pid)==process['startTicks'],'process changed during validation') + return {'pid':pid,'startTicks':process['startTicks'],'engineSha256':engine['sha256'],'pluginSha256':plugin['sha256']} + +def verify_capture_writer(expected,writer,node,source): + need(set(expected)=={'pid','startTicks','uid'} and set(writer)=={'pid','startTicks','uid','executable','sourcePath','sourceSha256'},'closed capture writer identity') + need({key:writer[key] for key in expected}==expected,'root-selected capture writer mismatch') + pid=expected['pid'];need(type(pid) is int and pid>1 and expected['uid']==os.geteuid(),'owned capture writer') + need(os.stat(f'/proc/{pid}').st_uid==os.geteuid() and start_ticks(pid)==expected['startTicks'],'capture PID/start mismatch') + need(os.readlink(f'/proc/{pid}/exe')==node['path'] and writer['executable']==node['path'],'capture executable mismatch') + hash_artifact(node['path'],node['sha256']);hash_artifact(source['path'],source['sha256']) + need(writer['sourcePath']==source['path'] and writer['sourceSha256']==source['sha256'],'capture source mismatch') + need(start_ticks(pid)==expected['startTicks'],'capture process changed during validation') + return {'pid':pid,'startTicks':expected['startTicks'],'sourceSha256':source['sha256']} + +def _observed_file(path,maximum=268435456): + try: + before=os.stat(path,follow_symlinks=False) + if not stat.S_ISREG(before.st_mode) or before.st_size>maximum:return {'available':False,'reason':'nonregular-or-oversized'} + fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK|os.O_CLOEXEC) + try: + first=os.fstat(fd);h=hashlib.sha256();size=0 + while True: + block=os.read(fd,65536) + if not block:break + size+=len(block) + if size>maximum:return {'available':False,'reason':'oversized'} + h.update(block) + last=os.fstat(fd) + if (first.st_dev,first.st_ino,first.st_size,first.st_mtime_ns,first.st_ctime_ns)!=(last.st_dev,last.st_ino,last.st_size,last.st_mtime_ns,last.st_ctime_ns):return {'available':False,'reason':'changed-during-read'} + return {'available':True,'device':first.st_dev,'inode':first.st_ino,'bytes':size,'sha256':h.hexdigest()} + finally:os.close(fd) + except OSError:return {'available':False,'reason':'unavailable'} + +def _closure_refuse(code,diagnostic_path,fields,allowed_count,executable_count): + document={'schema':'fsbar.barc-stock-runtime-map-diagnostic/v1','status':'refused','failureCode':code,'allowedCount':allowed_count,'executableMapCount':executable_count,'candidate':fields} + if diagnostic_path is not None: + try:atomic_new(str(diagnostic_path),document) + except Exception:pass + raise RuntimeClosureRefused(code) + +def verify_executable_map_lines(lines,allowed,diagnostic_path=None): + """Require every file-backed executable map to match one pinned path/inode.""" + seen=set();executable=[] + for line in lines: + fields=line.split(None,5) + if len(fields)!=6 or 'x' not in fields[1] or not fields[5].startswith('/'):continue + executable.append(fields) + need(len(executable)<=512,'executable mapping count bound') + for fields in executable: + raw=fields[5];deleted=raw.endswith(' (deleted)');candidate_raw=raw[:-10] if deleted else raw + path=os.path.realpath(candidate_raw);need(len(path.encode())<=4096,'executable mapping path bound') + admitted=allowed.get(path);candidate={'path':path,'mappedDevice':fields[3],'mappedInode':fields[4],'deleted':deleted,'admitted':admitted is not None,'expectedSha256':None if admitted is None else admitted['sha256'],'observed':_observed_file(path)} + if deleted:_closure_refuse('runtime-map-deleted',diagnostic_path,candidate,len(allowed),len(executable)) + if admitted is None:_closure_refuse('runtime-map-unadmitted',diagnostic_path,candidate,len(allowed),len(executable)) + try:info=os.stat(path,follow_symlinks=False) + except OSError:_closure_refuse('runtime-map-missing',diagnostic_path,candidate,len(allowed),len(executable)) + if int(fields[4])!=info.st_ino or fields[3]!=f'{os.major(info.st_dev):02x}:{os.minor(info.st_dev):02x}':_closure_refuse('runtime-map-identity-drift',diagnostic_path,candidate,len(allowed),len(executable)) + try:hash_artifact(path,admitted['sha256'],268435456) + except (Refused,OSError):_closure_refuse('runtime-map-content-drift',diagnostic_path,candidate,len(allowed),len(executable)) + seen.add(path) + if not seen:_closure_refuse('runtime-map-none',diagnostic_path,{},len(allowed),len(executable)) + return seen + +def verify_runtime_closure(process,libraries_artifact,diagnostic_path=None): + try: + need(set(process)=={'pid','startTicks','uid'},'closed runtime process identity') + pid=process['pid'];need(os.stat(f'/proc/{pid}').st_uid==os.geteuid() and start_ticks(pid)==process['startTicks'],'runtime closure PID/start mismatch') + except BaseException as error:raise mark_failure(error,'runtime-closure-process') + try: + from private_io import read_json + value=read_json(libraries_artifact['path'],libraries_artifact['sha256'],4*1024*1024) + except BaseException as error:raise mark_failure(error,'runtime-closure-input') + try: + allowed={} + for item in value['resolvedElfClosure']: + resolved=os.path.realpath(item['resolvedPath']);need(resolved==item['resolvedPath'],'closure resolved path drift');allowed[resolved]=item + for item in value['binaries']: + resolved=os.path.realpath(item['path']);allowed[resolved]={'sha256':item['sha256']} + except BaseException as error:raise mark_failure(error,'runtime-closure-resolve') + try:lines=proc_bytes(pid,'maps',4*1024*1024).decode().splitlines() + except BaseException as error:raise mark_failure(error,'runtime-map-census') + try:seen=verify_executable_map_lines(lines,allowed,diagnostic_path) + except BaseException as error:raise mark_failure(error,'runtime-map-validation') + try: + if os.path.realpath(os.readlink(f'/proc/{pid}/exe')) not in seen:_closure_refuse('runtime-engine-map-missing',diagnostic_path,{},len(allowed),len(seen)) + except BaseException as error:raise mark_failure(error,'runtime-executable-map') + try: + if start_ticks(pid)!=process['startTicks']:_closure_refuse('runtime-process-identity-drift',diagnostic_path,{},len(allowed),len(seen)) + except BaseException as error:raise mark_failure(error,'runtime-final-generation') + return seen + +def acquire_data_root_evidence(log_path,process): + """Acquire the held log descriptor; typed F# owns all root parsing and decisions.""" + from growing_log import GrowingLog + try: + need(set(process)=={'pid','startTicks','uid'},'closed runtime process identity') + pid=process['pid'];need(os.stat(f'/proc/{pid}').st_uid==os.geteuid() and process['uid']==os.geteuid() and start_ticks(pid)==process['startTicks'],'data-root PID/start mismatch') + except BaseException as error:raise mark_failure(error,'infolog-process') + try:return GrowingLog.acquire(log_path,process) + except BaseException as error:raise mark_failure(error,getattr(error,'_barc_failure_observation',{}).get('check','infolog-path')) + + +def verify_host_dynamic_map_lines(lines,allowed,held,executable,profile): + """Only startup's explicit host profile may classify generated mappings.""" + from dynamic_mapping import classify,owner_metadata + ordinary,rows=classify(lines,'host',profile,os.sysconf('SC_PAGE_SIZE')) + try:metadata=owner_metadata(held,executable,rows) + except OSError as error:raise RuntimeClosureRefused('host-dynamic-owner-unavailable') from error + seen=verify_executable_map_lines(ordinary,allowed) + return seen,metadata diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/seed_policy.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/seed_policy.py new file mode 100644 index 0000000..ae9c933 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/seed_policy.py @@ -0,0 +1,109 @@ +"""Literal owned direct-apphost transport; inherited canonical infolog semantics. +Inert without the selected startup runner's concrete live custody context. +""" +import os,time,json,hashlib,selectors,signal,subprocess,pathlib +from growing_log import GrowingLog,_unique,_policy_observation,POLICY_TO_MECHANICAL,MAX_POLICY_INPUT +from private_io import need,Refused +from runtime_identity import mark_failure +class SeedPolicyContext: + def __init__(self,profile,admission,owner,children,actor_selector,streams,counts,deadline): + self.profile=profile;self.admission=admission;self.owner=owner;self.children=children;self.actor_selector=actor_selector;self.streams=streams;self.counts=counts;self.deadline=deadline;self.evaluations=0;self.active=None;self.reaped=[] + def pump(self,deadline): + # This is the real actor selector/owner/quota, not an injected success callback. + import startup_runner as runner + need(time.monotonic()0,'policy settlement Unknown: deadline') + code=child.wait(timeout=remaining);self.owner.leaders.discard(child.pid);self.reaped.append({'identity':ident,'exitCode':code,'reaped':True});self.active=None;return code +class SeedGrowingLog(GrowingLog): + def bind(self,context): + need(type(context)is SeedPolicyContext and not hasattr(self,'context'),'closed concrete policy context');self.context=context;return self + def _run_policy(self,path,closure_path,closure_sha,encoded,deadline): + ctx=self.context;p=ctx.profile['policy'];need((path,closure_path,closure_sha)==(p['apphost'],p['closurePath'],p['closureSha256']),'literal policy input mismatch');need(p['environment']=={'PATH':'/usr/bin:/bin'} and len(encoded)<=MAX_POLICY_INPUT,'literal policy environment/input cap') + invocation=hashlib.sha256(os.urandom(32)).hexdigest();child=None;sel=None;diagnostic=bytearray();all_stdout=bytearray();output=bytearray();ready=None;offset=0;count=0;check='policy-child-start';stdin_open=True;input_registered=False + try: + child=ctx.launch(invocation,deadline);ident=ctx.owner.records[child.pid] + inp,out,err=child.stdin.fileno(),child.stdout.fileno(),child.stderr.fileno() + for fd in [inp,out,err]:os.set_blocking(fd,False) + sel=selectors.DefaultSelector();sel.register(out,selectors.EVENT_READ,'stdout');sel.register(err,selectors.EVENT_READ,'stderr') + while sel.get_map()or stdin_open: + ctx.pump(deadline);left=deadline-time.monotonic();need(left>0,'policy five-second deadline');check='policy-ready'if ready is None else 'policy-transport' + for key,_ in sel.select(min(.02,left)): + if key.data=='stdin': + try:written=os.write(inp,encoded[offset:offset+65536]) + except BrokenPipeError as e:raise Refused('policy request broken pipe')from e + need(written>0,'policy stdin stalled');offset+=written + if offset==len(encoded):sel.unregister(inp);child.stdin.close();stdin_open=False + else: + block=os.read(key.fd,min(65536,131073-count));count+=len(block);need(count<=131072,'combined policy output cap') + if not block: + sel.unregister(key.fd) + if key.data=='stdout': + need(ready is not None,'policy ready unavailable') + need(not stdin_open,'policy output ended before complete request') + continue + if key.data=='stderr':diagnostic.extend(block);continue + all_stdout.extend(block);output.extend(block) + if ready is None and b'\n'in output: + line,tail=bytes(output).split(b'\n',1);ready=json.loads(line.decode(),object_pairs_hook=_unique) + expected={'schema':'fsbar.barc-runtime-evidence-policy-ready/v2','invocationId':invocation,'closureSha256':closure_sha,'pid':ident['pid'],'startTicks':ident['startTicks'],'uid':ident['uid'],'phase':'ready'} + need(ready==expected,'policy exact ready/current-process join');output=bytearray(tail);sel.register(inp,selectors.EVENT_WRITE,'stdin');input_registered=True + if child.poll()is not None and stdin_open:raise Refused('policy exited before full ready/request') + need(ready is not None and offset==len(encoded),'ready before exact request');code=ctx.settle(child,deadline);need(code in [0,2],'policy natural exit') + check='policy-completion';final=json.loads(bytes(output).decode(),object_pairs_hook=_unique) + need(set(final)==set(ready)|{'result'} and all(final[k]==ready[k]for k in ready if k not in ['schema','phase']) and final['schema']=='fsbar.barc-runtime-evidence-policy-completed/v2' and final['phase']=='completed','one completed/final-closure frame') + self.last_policy_observation=_policy_observation(bytes(diagnostic))if diagnostic else None + self.last_invocation={k:ready[k]for k in ['invocationId','closureSha256','pid','startTicks','uid']};self.last_invocation.update(readyPhase='ready',completedPhase='completed') + from snapshot_publication import bytes_new,json_new + import startup_runner as runner + root=pathlib.Path(runner.ROOT)/'evidence';n=ctx.evaluations + frames=bytes_new(root/('policy-'+str(n)+'.stdout.raw'),bytes(all_stdout),131072) + stderr=bytes_new(root/('policy-'+str(n)+'.stderr.raw'),bytes(diagnostic),131072) + json_new(root/('policy-'+str(n)+'.transport.json'),{'requestSha256':hashlib.sha256(encoded).hexdigest(),'requestBytes':len(encoded),'ready':ready,'completed':final,'stdout':frames,'stderr':stderr,'ownedSettlement':ctx.reaped[-1],'deadlineMonotonic':deadline}) + return (json.dumps(final['result'],separators=(',',':'))+'\n').encode() + except BaseException as error: + from snapshot_publication import bytes_new,json_new + import startup_runner as runner + root=pathlib.Path(runner.ROOT)/'evidence';n=ctx.evaluations + try: + stdout_ack=bytes_new(root/('policy-'+str(n)+'.failure.stdout.raw'),bytes(all_stdout),131072) + stderr_ack=bytes_new(root/('policy-'+str(n)+'.failure.stderr.raw'),bytes(diagnostic),131072) + json_new(root/('policy-'+str(n)+'.failure.json'),{'primaryError':str(error),'checkpoint':check,'requestSha256':hashlib.sha256(encoded).hexdigest(),'requestBytes':len(encoded),'stdout':stdout_ack,'stderr':stderr_ack,'nativeAcceptance':False}) + except BaseException as publication:error._barc_secondary_publication_failure=str(publication) + observation=_policy_observation(bytes(diagnostic))if diagnostic else None + if observation is not None:check=POLICY_TO_MECHANICAL[observation['checkpoint']] + if child is not None and ctx.active is child: + try:ctx.settle(child,deadline,force=True) + except BaseException as settlement:raise mark_failure(Refused('policy held child settlement Unknown'),check,'unexpected',observation)from settlement + raise mark_failure(error,check,None,observation) + finally: + if sel is not None:sel.close() + if child is not None and ctx.active is None: + for stream in [child.stdin,child.stdout,child.stderr]: + if stream is not None and not stream.closed:stream.close() diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/settings_custody.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/settings_custody.py new file mode 100644 index 0000000..e240e1c --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/settings_custody.py @@ -0,0 +1,59 @@ +"""Root-selected immutable settings seed and attempt-scoped engine output.""" +import hashlib,os,stat +from pathlib import Path +from private_io import Refused,atomic_bytes_new,atomic_new,canonical,components,hash_artifact,need,read_bytes,read_json +MAX_SETTINGS=65536 +MAX_INVENTORY=16*1024*1024 +MAX_FILES=65536 + +def inventory(config): + root=components(config['roots']['packetRoot']);need(root.stat().st_uid==os.geteuid() and stat.S_IMODE(root.stat().st_mode)==0o700,'private packet root') + value=read_json(str(root/'packet-inventory.json'),config['packetSha256'],MAX_INVENTORY) + need(value['root']==str(root) and isinstance(value['files'],list) and len(value['files'])<=MAX_FILES,'bounded packet census') + need(value['fileCount']==len(value['files']),'packet census count') + names=set() + for row in value['files']: + path=row['path'];need(isinstance(path,str) and not path.startswith('/') and all(part not in ('','.','..') for part in path.split('/')),'packet census relative path') + need(path not in names,'duplicate packet census path');names.add(path) + return root,value + +def validate_engine_argv(original,actual,attempt): + need(isinstance(original,list) and isinstance(actual,list) and len(original)==len(actual)==9,'closed engine argv') + need(all(actual[i]==original[i] for i in range(9) if i!=7),'unexpected engine argv change') + need(actual[7]==str(canonical(str(attempt))/'engine'/'springsettings.cfg'),'derived config path mismatch') + need(actual[7]!=original[7],'engine settings must be isolated from seed') + return actual + +def prepare_engine_settings(config,attempt): + attempt=components(str(attempt));need(str(attempt)==config['roots']['attemptRoot'],'attempt output escape') + need(attempt.stat().st_uid==os.geteuid() and stat.S_IMODE(attempt.stat().st_mode)==0o700,'private attempt root') + root,value=inventory(config);original=list(config['commands']['engine']);need(len(original)==9,'closed seed engine argv') + seed=components(original[7]);need(seed.is_relative_to(root),'seed escapes packet') + rel=str(seed.relative_to(root));row=next((x for x in value['files'] if x['path']==rel),None);need(row is not None,'seed absent from admitted census') + before=hash_artifact(str(seed),row['sha256'],MAX_SETTINGS);need(before.st_nlink==1 and before.st_uid==os.geteuid() and stat.S_IMODE(before.st_mode)==0o400,'immutable single-linked seed') + data=read_bytes(str(seed),MAX_SETTINGS,private=False);digest=hashlib.sha256(data).hexdigest();need(digest==row['sha256'] and len(data)==row['bytes'],'seed bytes mismatch') + output=canonical(str(attempt/'engine'/'springsettings.cfg'));parent=components(str(output.parent));need(parent.is_relative_to(attempt) and parent.stat().st_uid==os.geteuid() and stat.S_IMODE(parent.stat().st_mode)==0o700,'private engine output parent') + # Exclusive publication refuses an existing file or symlink without following it. + copied=atomic_bytes_new(str(output),data,MAX_SETTINGS);after=output.lstat();need(after.st_nlink==1 and stat.S_IMODE(after.st_mode)==0o600 and after.st_uid==os.geteuid() and (after.st_dev,after.st_ino)!=(before.st_dev,before.st_ino),'fresh single-linked writable output') + hash_artifact(str(seed),digest,MAX_SETTINGS);need(copied==digest,'exact seed copy') + actual=list(original);actual[7]=str(output);validate_engine_argv(original,actual,attempt) + receipt={'schema':'fsbar.barc-stock-engine-settings-copy/v1','seedPath':str(seed),'seedSha256':digest,'outputPath':str(output),'copiedSha256':copied,'actualEngineArgv':actual,'originalEngineArgv':original,'packetSha256':config['packetSha256'],'seedDevice':before.st_dev,'seedInode':before.st_ino,'outputDevice':after.st_dev,'outputInode':after.st_ino,'nativeAcceptance':False} + atomic_new(str(attempt/'engine-settings-copy.json'),receipt);return actual,receipt + +def verify_packet_unchanged(config): + root,value=inventory(config);expected={row['path']:row for row in value['files']};actual=set() + for base,dirs,files in os.walk(root,followlinks=False): + for name in dirs:need(not (Path(base)/name).is_symlink(),'packet symlink directory') + for name in files: + p=Path(base)/name;need(not p.is_symlink(),'packet symlink file');rel=str(p.relative_to(root)) + if rel=='packet-inventory.json':continue + need(len(actual)0) +def _unique(pairs): + out={} + for key,value in pairs:need(key not in out,'duplicate JSON key');out[key]=value + return out +def _decode(data,label):return json.loads(data.decode('utf-8'),object_pairs_hook=_unique,parse_constant=lambda _:(_ for _ in ()).throw(Refused(f'nonfinite {label} JSON'))) +def canonical_b64(value,size=None): + try:raw=base64.b64decode(value,validate=True) + except Exception:return False + return bool(raw) and (size is None or len(raw)==size) and base64.b64encode(raw).decode()==value +def _writer(value):exact(value,{'pid','startTicks','uid'},'writer');need(type(value['pid']) is int and value['pid']>1 and u64(value['startTicks']) and value['uid']==os.geteuid(),'writer identity') +def _writable_append_fd(pid,path): + target=os.stat(path,follow_symlinks=False);names=os.listdir(f'/proc/{pid}/fd');need(len(names)<=256,'writer fd bound');found=False + for name in names: + try: + current=os.stat(f'/proc/{pid}/fd/{name}') + if (current.st_dev,current.st_ino)!=(target.st_dev,target.st_ino):continue + info=proc_bytes(pid,f'fdinfo/{name}',16384).decode();match=re.search(r'^flags:\s+([0-7]+)$',info,re.MULTILINE) + if match: + flags=int(match.group(1),8);found=found or flags&os.O_ACCMODE==os.O_WRONLY and bool(flags&os.O_APPEND) + except FileNotFoundError:continue + need(found,'actual writable append FD unavailable') +def _same_process(writer,child): + _writer(writer);need(child.poll() is None and writer['pid']==child.pid,'selected writer process');need(os.stat(f'/proc/{child.pid}').st_uid==os.geteuid() and start_ticks(child.pid)==writer['startTicks'],'writer PID/start mismatch') +def _loopback_url(value,schemes): + parsed=urllib.parse.urlsplit(value);need(parsed.scheme in schemes and parsed.hostname=='127.0.0.1' and parsed.port and not parsed.username and not parsed.password and not parsed.fragment,'closed loopback URL') +def _validated_result(value,raw,path,return_snapshot): + if not return_snapshot:return value + return value,{'raw':raw,'sha256':digest(raw),'bytes':len(raw),'sourcePath':str(path),'writer':dict(value['writer']),'authentication':'Observed'} + +def persist_authenticated_snapshots(directory,snapshots): + """Publish only exact bytes already validated; never reread producer files.""" + from snapshot_publication import bytes_new,json_new + receipts={} + for label,(value,snapshot) in snapshots.items(): + need(label in ('ready','metadata','setup') and snapshot['authentication']=='Observed' and digest(snapshot['raw'])==snapshot['sha256'],'validated snapshot binding') + raw_path=str(Path(directory)/('authenticated-'+label+'.raw')) + need(bool(snapshot['raw']),'authenticated nonempty snapshot') + raw_ack=bytes_new(raw_path,snapshot['raw'],1048576) + parsed_ack=json_new(str(Path(directory)/('authenticated-'+label+'.json')),value,1048576) + receipts[label]={k:v for k,v in snapshot.items() if k!='raw'} + receipts[label].update(rawPath=raw_path,sha256=raw_ack['sha256'],rawPublication=raw_ack,parsedPublication=parsed_ack) + json_new(str(Path(directory)/'ready-producer-authentication.json'),{'readyProducerAuthenticated':True,'startupAccepted':False,'snapshots':receipts}) + return receipts + +def authenticate_ready(path,host,config,return_snapshot=False): + raw=read_bytes(str(path),65536);ready=_decode(raw,'ready');exact(ready,{'schema','runId','writer','source','profile','tacticalRevision','queueEvidenceScheme','grpcAddress','gatewayUrl','allowedOrigin','receiverUrl','sessionId','credential','metadataPath','setupPath','hostTracePath'},'ready') + need(ready['schema']==READY_SCHEMA and ready['runId']==config['runId'] and ready['source']==config['source'] and ready['profile']==PROFILE and ready['tacticalRevision']==2 and ready['queueEvidenceScheme']==2 and ready['receiverUrl']==config['bootstrap']['receiverUrl'],'ready binding');_same_process(ready['writer'],host) + need(re.fullmatch(r'127\.0\.0\.1:[1-9][0-9]{0,4}',ready['grpcAddress']),'grpc address');_loopback_url(ready['gatewayUrl'],{'ws','wss'});_loopback_url(ready['allowedOrigin'],{'http','https'});_loopback_url(ready['receiverUrl'],{'http','https'}) + try:need(str(uuid.UUID(ready['sessionId']))==ready['sessionId'],'session UUID') + except (ValueError,AttributeError):raise Refused('session UUID') + need(HEX64.fullmatch(ready['credential']),'credential shape') + for key in ('metadataPath','setupPath','hostTracePath'):beneath(ready[key],config['roots']['attemptRoot']) + need(ready['hostTracePath']==config['paths']['hostTrace'],'host trace path mismatch');_writable_append_fd(host.pid,ready['hostTracePath']);need(start_ticks(host.pid)==ready['writer']['startTicks'],'host changed during ready authentication');return _validated_result(ready,raw,path,return_snapshot) +def _stable_private_json(path,maximum=1048576):return _decode(read_bytes(path,maximum),'private') +def authenticate_metadata(path,ready,return_snapshot=False): + raw=read_bytes(str(path),1048576);value=_decode(raw,'private');exact(value,{'schema','runId','writer','basis','catalogue','actors'},'metadata');need(value['schema']==METADATA_SCHEMA and value['runId']==ready['runId'] and value['writer']==ready['writer'],'metadata identity') + basis=value['basis'];exact(basis,{'token','stateSequence','nativeFrame','matchId','processIncarnation','stateChannelIncarnation'},'basis');need(canonical_b64(basis['token']) and u64(basis['stateSequence'],True) and type(basis['nativeFrame']) is int and 0<=basis['nativeFrame']<2**32 and canonical_b64(basis['matchId'],16),'basis values');need(all(isinstance(basis[k],str) and 0=first.st_size,'live prefix truncated or replaced') + finally:os.close(fd) + raw=b''.join(chunks);need(raw.endswith(b'\n') and b'\x00' not in raw,'incomplete or NUL live prefix');lines=raw.splitlines(keepends=True);need(01 and os.getpgid(pid)==browser.pid and os.getsid(pid)==browser.pid and browser.poll() is None,'capture writer outside retained browser group');selected={'pid':pid,'startTicks':start_ticks(pid),'uid':os.stat(f'/proc/{pid}').st_uid};need(selected['startTicks']==writer['startTicks'],'capture writer changed') + verify_capture_writer(selected,writer,config['artifacts']['captureRuntime'],config['artifacts']['captureSource']);_writable_append_fd(pid,path);_validate_journal(lines,CAPTURE_SCHEMA,config['runId'],writer,config['source']);return selected,writer +def authenticate_host_writer(path,host,ready,config): + _same_process(ready['writer'],host);_writable_append_fd(host.pid,path);_,lines=_bounded_raw(path);_validate_journal(lines,HOST_JOURNAL_SCHEMA,config['runId'],ready['writer'],config['source']);return {'pid':host.pid,'startTicks':ready['writer']['startTicks'],'uid':ready['writer']['uid']} +def _freeze_prefix(path,target,verifier,schema,run_id,writer,source): + before=verifier();raw,lines=_bounded_raw(path);rows=_validate_journal(lines,schema,run_id,writer,source);st=components(path).stat();need(stat.S_ISREG(st.st_mode) and st.st_uid==os.geteuid() and stat.S_IMODE(st.st_mode)==0o600 and st.st_nlink==1,'journal custody');after=verifier();need(before==after,'writer changed during freeze');sha=atomic_bytes_new(target,raw,16*1024*1024);return {'path':target,'sha256':sha,'bytes':len(raw),'records':len(rows),'firstSequence':'1','lastSequence':str(len(rows)),'sourcePath':path,'sourceDevice':st.st_dev,'sourceInode':st.st_ino,'sourceSize':st.st_size,'sourceMtimeNs':st.st_mtime_ns,'writer':after} +def freeze_host_prefix(path,target,host,ready,config):return _freeze_prefix(path,target,lambda:authenticate_host_writer(path,host,ready,config),HOST_JOURNAL_SCHEMA,config['runId'],ready['writer'],config['source']) +def freeze_capture_prefix(path,target,browser,config): + _,writer=authenticate_capture_writer(path,browser,config);return _freeze_prefix(path,target,lambda:authenticate_capture_writer(path,browser,config)[0],CAPTURE_SCHEMA,config['runId'],writer,config['source']) +def freeze_trace_prefix(process,engine,plugin,trace_path,target,run_id): + need(RUN.fullmatch(run_id),'run id');before=verify_process(process,engine,plugin,trace_path);raw,lines,st=_bounded_live_prefix(trace_path);sequence=0 + for line in lines: + row=_decode(line,'trace');exact(row,{'schema','runId','sequence','phase','readFrame','perspectiveTeamId','nativeBasis','context','reader','queue','dispatch'},'trace record');need(row['schema']==TRACE_SCHEMA and row['runId']==run_id and u64(row['sequence'],True) and int(row['sequence'])==sequence+1 and row['phase'] in ('sample','final-read'),'trace sequence');sequence+=1 + after=verify_process(process,engine,plugin,trace_path);need(before==after,'process changed during trace freeze');sha=atomic_bytes_new(target,raw,16*1024*1024);return {'path':target,'sha256':sha,'bytes':len(raw),'records':len(lines),'firstSequence':'1','lastSequence':str(sequence),'sourcePath':trace_path,'sourceDevice':st.st_dev,'sourceInode':st.st_ino,'sourceSize':len(raw),'sourceMtimeNs':st.st_mtime_ns,'process':after} +def revalidate_growing_prefix(receipt,verifier): + need(type(receipt.get('bytes')) is int and 0=first.st_size and after.st_size<=maximum,'prefix append identity') + finally:os.close(fd) + body=b''.join(raw);rows=records(body,role) + for row in rows: + if role=='host': + exact(row,{'schema','runId','sequence','kind','writer','source','value'},'prearm host row');need(row['schema']==HOST_JOURNAL_SCHEMA and row['kind']!='complete','live host cannot claim completion') + else: + exact(row,{'schema','runId','sequence','kind','writer','source','acceptedGeneration','stateSequence','nativeFrame','disposition','value'},'prearm raw row');need(row['schema']=='fsbar.barc-one-unit-raw-state-journal/v1','raw schema') + need(row['runId']==config['runId'] and row['writer']==ready['writer'] and row['source']==config['source'],'prefix exact producer origin') + if role=='host':need(rows[0]['kind']=='header' and rows[0]['value']=={'selectedCase':'stock-smoke-count1'},'host real header') + else:need(len({row['acceptedGeneration']for row in rows})==1 and rows[0]['acceptedGeneration'],'raw owning generation') + live={'path':str(p),'sha256':digest(body),'bytes':len(body),'device':first.st_dev,'inode':first.st_ino,'uid':first.st_uid,'mode':stat.S_IMODE(first.st_mode),'nlink':first.st_nlink} + need(prefix_bytes(live,maximum,deadline)==body,'prefix revalidation');_same_process(ready['writer'],host);_writable_append_fd(host.pid,path) + ack=bytes_new(target,body,maximum);retained={k:ack[k]for k in ['path','sha256','bytes','device','inode','uid','mode','nlink']} + measured=p.stat().st_size + return {'schema':'bar.prearm-live-prefix-observation/v1','role':role,'livePin':live,'retainedPin':retained,'writer':dict(ready['writer']),'terminal':False,'observedBytes':measured,'suffixBytes':measured-len(body)} + +def collect_prearm(p,ready,metadata,setup,host,engine,policy,deadline,started_ns): + """Actual concrete producer joins; called once predicates are observed. + No Node, browser, commands or approval records are generated here. + """ + import time,copy + import mechanics as m + from snapshot_publication import json_new + from loaded_custody import persist_loaded_custody + from qualify_prearm import derive,prefix_bytes,records + A=Path(p['attemptRoot']);root=A/'evidence';cfg=copy.deepcopy(p['seed']['configuration']);need(cfg['source']==p['source'] and cfg['runId']==p['runId'],'seed configuration source') + cfg['commands']['engine']=__import__('startup_runner').commands(p)['engine'] + config_path=root/'seed-effective-launch-config.json';json_new(config_path,cfg);config_pin=m.physical(config_path) + process={'pid':engine.pid,'startTicks':start_ticks(engine.pid),'uid':os.geteuid()} + persist_loaded_custody(process,cfg,A,config_pin['sha256']) + result=policy.consume('browser',cfg,deadline) + json_new(root/'infolog-policy-result.json',result);json_new(root/'infolog-policy-effects.json',policy.settlement_effects) + infolog={'result':m.physical(root/'infolog-policy-result.json'),'effects':m.physical(root/'infolog-policy-effects.json'),'config':cfg,'configPin':config_pin,'producer':m.physical(Path(__file__).parent/'growing_log.py'),'transports':[m.physical(root/('policy-'+str(n)+'.transport.json'))for n in range(1,policy.context.evaluations+1)]} + prefixes={} + for role in ['raw','host']: + source=A/'host'/('raw.jsonl'if role=='raw'else 'host.jsonl');receipt=_freeze_prearm_owned(str(source),str(root/(role+'-prefix.raw')),role,host,ready,cfg,deadline) + json_new(root/(role+'-prefix.json'),receipt);prefixes[role]=receipt + stock=freeze_trace_prefix(process,cfg['artifacts']['engine'],cfg['artifacts']['plugin'],str(A/'engine/stock.jsonl'),str(root/'stock-prefix.raw'),cfg['runId']) + st=Path(stock['sourcePath']).stat();live={k:stock[k]for k in ['sha256','bytes']};live.update(path=stock['sourcePath'],device=stock['sourceDevice'],inode=stock['sourceInode'],uid=st.st_uid,mode=stat.S_IMODE(st.st_mode),nlink=st.st_nlink) + prefixes['stock']={'schema':'bar.prearm-live-prefix-observation/v1','role':'stock','livePin':live,'retainedPin':m.physical(stock['path']),'writer':process,'terminal':False,'observedBytes':st.st_size,'suffixBytes':st.st_size-stock['bytes']};json_new(root/'stock-prefix.json',prefixes['stock']) + horizons={'schema':'bar.prearm-prefix-observations/v2','prefixes':{k:m.physical(root/(k+'-prefix.json'))for k in prefixes},'infolog':infolog};json_new(root/'prefix-evidence.json',horizons) + inputs={k:prefixes[k]['livePin']for k in prefixes} + for label in ['metadata','setup']:inputs[label]=m.physical(root/('authenticated-'+label+'.raw')) + evidence=copy.deepcopy(p['seed']['configurationInputs']);evidence.update(loaded=m.physical(A/'runtime-closure-custody.json'),settings=m.physical(A/'engine-settings-copy.json'),configPin=config_pin,infolog=infolog);evidence['roles']['settings']=m.physical(A/'engine/springsettings.cfg');json_new(root/'native-evidence-inputs.json',evidence);inputs['configuration']=m.physical(root/'native-evidence-inputs.json') + rows=records(prefix_bytes(inputs['raw'],4*1024*1024,deadline),'raw') + contract={'schema':'bar.prearm-sealed-input-contract/v1','runId':cfg['runId'],'source':p['source'],'writer':ready['writer'],'acceptedGeneration':rows[0]['acceptedGeneration'],'factory':metadata['actors']['factory'],'setup':{'seedCount':3,'seedDefinitionId':setup['existingProductionDefinitionId'],'productDefinitionId':setup['productDefinitionId']},'inputs':inputs,'completeRecordEvidence':m.physical(root/'prefix-evidence.json'),'lifecycle':{'startedMonotonicNs':str(started_ns),'transitionDeadlineMonotonicNs':str(int(deadline*1e9)),'totalDeadlineMonotonicNs':str(started_ns+180_000_000_000)},'outputPath':str(root/'seed-qualification.json')} + json_new(root/'seed-input-contract.json',contract);qualification=derive(contract,min(deadline,time.monotonic()+10));ack=json_new(root/'seed-qualification.json',qualification,65536) + for role in prefixes:need(prefix_bytes(inputs[role],4*1024*1024 if role=='raw'else 16*1024*1024,deadline),'final live prefix') + return {'qualification':qualification,'publication':ack,'canonicalPolicyConsumed':True,'contract':m.physical(root/'seed-input-contract.json'),'seedQualificationAccepted':True} diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/stock_config.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/stock_config.py new file mode 100644 index 0000000..44e49f5 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/stock_config.py @@ -0,0 +1,114 @@ +"""Closed stock-smoke config. Templates retain null pins and must fail preflight.""" +import os,re,stat,urllib.parse +from pathlib import Path +from private_io import Refused,SHA,canonical,components,hash_artifact,need,read_bytes,read_json + +SCHEMA='fsbar.barc-stock-native-smoke-supervisor-config/v2' +PROFILE='barc-live-tactical-stock-v1';BRIDGE='barc-stock-queue-reader-v1' +EXPECTED_SOURCE={'fsbarCommit':'fb00d0f77b6c7d0998f85a2039931c0c5eed2472','highbarCommit':'b57f11f290dc3239d894b0285ae272b6b21bc25d'} +ARTIFACT_ROLES=('engine','plugin','stockOverlay','baseGame','map','passiveFixture','nativeHost','receiverArchive','client','guest','codecManifest','helperManifest','fsbarNormalizer','runtimeClosure','normalizerRuntime','captureRuntime','captureSource','runtimeEvidencePolicy','runtimeEvidencePolicyClosure') +NULL_AI_SHA256='cf72efb79a43b8242f0f505915dff5d900cffc721cc8667bb8da0e4aec4eb5ed' +HANDOFF_ROLES=ARTIFACT_ROLES[:12] +COMMAND_ROLES=('host','engine','receiver','browser') +TOP={'schema','runId','packetSha256','source','artifacts','roots','paths','commands','bootstrap','limits','expected'} +RUN=re.compile(r'^[A-Za-z0-9._-]{1,64}$');GIT=re.compile(r'^[0-9a-f]{40}$') + +def exact(value,keys,label):need(isinstance(value,dict) and set(value)==set(keys),f'closed {label}') +def beneath(path,root): + path=canonical(path);root=canonical(root) + try:path.relative_to(root) + except ValueError:raise Refused('path outside admitted root') + return path + +def loopback_endpoint(value,schemes,label): + if schemes is None: + need(isinstance(value,str) and re.fullmatch(r'127\.0\.0\.1:[1-9][0-9]{0,4}',value),f'fixed loopback {label}') + port=int(value.rsplit(':',1)[1]);need(port<=65535,f'fixed loopback {label}');return (None,'127.0.0.1',port) + try: + parsed=urllib.parse.urlsplit(value or '');port=parsed.port + except ValueError as error:raise Refused(f'fixed loopback {label}') from error + need(parsed.scheme in schemes and parsed.hostname=='127.0.0.1' and port and port<=65535 and not parsed.username and not parsed.password and not parsed.fragment,f'fixed loopback {label}') + return (parsed.scheme,parsed.hostname,port) + +def coordinator_endpoint(value): + _,host,port=loopback_endpoint(value['commands']['host'][2],None,'coordinator');return f'{host}:{port}' + +def validate_template(value): + exact(value,TOP,'config');need(value['schema']==SCHEMA and (value['runId'] is None or RUN.fullmatch(value['runId'])),'config identity') + exact(value['source'],{'fsbarCommit','highbarCommit'},'source') + exact(value['artifacts'],ARTIFACT_ROLES,'artifact roles') + for role,item in value['artifacts'].items(): + exact(item,{'path','sha256'},f'artifact {role}');need(item['path'] is None or isinstance(item['path'],str),'artifact path');need(item['sha256'] is None or SHA.fullmatch(item['sha256']),'artifact hash') + exact(value['roots'],{'packetRoot','attemptRoot'},'roots');exact(value['paths'],{'codecDirectory','receiverRoot','hostTrace','stockTrace','capture','output'},'paths') + exact(value['commands'],COMMAND_ROLES,'commands') + for argv in value['commands'].values():need(argv is None or isinstance(argv,list) and 1<=len(argv)<=32 and all(isinstance(x,str) and 0=0) + current=p.read_bytes();self.assertTrue(current.startswith(previous));self.assertTrue(current.endswith(b'\n'));self.assertGreater(len(current),len(previous));previous=current + finally:libc.fclose(stream) + def test_error_default_does_not_flush_info_but_error_flushes(self): + with tempfile.TemporaryDirectory() as td: + p=pathlib.Path(td)/'infolog.txt';stream=libc.fopen(str(p).encode(),b'wb');buffer=ctypes.create_string_buffer(8192) + try: + self.assertEqual(libc.setvbuf(stream,buffer,0,8192),0) + self.write(stream,b'info',30>=50);self.assertEqual(p.read_bytes(),b'') + self.write(stream,b'error',50>=50);self.assertTrue(p.read_bytes().endswith(b'error\n')) + finally:libc.fclose(stream) + +if __name__=='__main__':unittest.main() diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_growing_log.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_growing_log.py new file mode 100644 index 0000000..7c2e39e --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/test_growing_log.py @@ -0,0 +1,1016 @@ +import base64,fnmatch,hashlib,json,os,pathlib,shutil,stat,subprocess,sys,tempfile,threading,time,unittest +from unittest import mock +import growing_log +from growing_log import GrowingLog +from private_io import Refused,digest +from runtime_identity import acquire_data_root_evidence,start_ticks + +POLICY_BUILD=pathlib.Path(os.environ['BAR_RUNTIME_EVIDENCE_BUILD']) +POLICY_SOURCE=pathlib.Path(os.environ['BAR_RUNTIME_EVIDENCE_SOURCE']) +WRITER="""import os,sys +os.umask(0o077) +p=sys.argv[1] +f=open(p,'w+b',buffering=0) +f.write(base64.b64decode(sys.argv[2])) +for command in sys.stdin.buffer: + command=command.rstrip(b'\\n') + if command.startswith(b'append:'):f.seek(0,2);f.write(command[7:]+b'\\n') + elif command.startswith(b'appendraw:'):f.seek(0,2);f.write(base64.b64decode(command[10:])) + elif command==b'rewrite':f.seek(0);f.write(b'X');f.flush() + elif command==b'truncate':f.truncate(1);f.flush() + elif command==b'close':f.close();break +""" + +def _rp2_canonical_trace(source,timeline,name,directory): + """Concrete action parameters from actual framed F# and held-FD effects. + The expected core states are independently checked by the F# reader. The + canonical Quint actions check every projected field at every ordered step. + This fixture code grants no runtime authority and is never imported by the + production helper. + """ + empty_hash=hashlib.sha256(b'').hexdigest() + empty_prefix={'rawBytes':0,'rawSha256':empty_hash,'completeBytes':0,'completeSha256':empty_hash,'tailBytes':0,'tailSha256':empty_hash,'completeRecords':0,'tailClass':'empty'} + no_consumption={'boundary':'none','revision':0,'rawBytes':0,'rawSha256':'','readStartMicroseconds':0,'readEndMicroseconds':0,'linearizedMicroseconds':0,'releasedMicroseconds':0,'deadlineMicroseconds':0} + core={'phase':'empty','identity':None,'observedRevision':0,'validatedRevision':0,'validatedBoundary':None,'consumedRevision':0,'consumedBoundary':None,'bytes':0,'sha256':'','stickyInvalid':False,'reason':None,'prefix':empty_prefix,'candidatePrefix':None,'consumedPrefix':None,'consumption':None,'attemptId':0,'probeCount':0,'evaluationCount':0,'deadlineMicroseconds':0} + mech={'rootsValid':False,'prefixIntact':False,'writerPresent':False,'policyPhase':'none','policyInvocation':'none','policyClosure':'none','settlement':{'phase':'idle','boundary':'none','remaining':32,'deadlineAvailable':True},'producerBytes':0,'producerContradiction':False,'finalPhase':'none','finalSize':0,'readStart':0,'readEnd':0,'linearized':0} + steps=[] + def literal(value): + if type(value) is bool:return 'true' if value else 'false' + if type(value) is int:return str(value) + if type(value) is str:return json.dumps(value) + if type(value) is dict:return '{ '+', '.join(key+': '+literal(item) for key,item in value.items())+' }' + raise AssertionError('closed Quint literal') + def project(): + present=core['identity'] is not None + return {'phase':core['phase'],'generation':1 if present else 0,'observedRevision':core['observedRevision'],'validatedRevision':core['validatedRevision'],'intendedBoundary':core['validatedBoundary'] or 'none','consumedRevision':core['consumedRevision'],'consumedBoundary':core['consumedBoundary'] or 'none','producerMatches':present,'logMatches':present,'sourceMatches':present,'rootsValid':mech['rootsValid'],'prefixIntact':mech['prefixIntact'],'writerPresent':mech['writerPresent'],'authorityActive':False,'stickyInvalid':core['stickyInvalid'],'policyPhase':mech['policyPhase'],'policyInvocation':mech['policyInvocation'],'policyClosure':mech['policyClosure'],'settlement':dict(mech['settlement']),'prefix':core['prefix'],'consumedPrefix':core['consumedPrefix'] or empty_prefix,'consumption':core['consumption'] or no_consumption,'producerBytes':mech['producerBytes'],'producerContradiction':mech['producerContradiction'],'candidateBytes':(core['candidatePrefix'] or {}).get('rawBytes',0),'candidateSha256':(core['candidatePrefix'] or {}).get('rawSha256',''),'finalPhase':mech['finalPhase'],'finalSize':mech['finalSize'],'readStart':mech['readStart'],'readEnd':mech['readEnd'],'linearized':mech['linearized'],'evaluations':core['evaluationCount'],'attemptId':core['attemptId'],'probeCount':core['probeCount'],'deadlineMicroseconds':core['deadlineMicroseconds']} + def emit(action):steps.append({'action':action,'state':json.loads(json.dumps(project()))}) + first=next(row for row in timeline if row['kind']=='policy') + identity={key:first['expected'][key] for key in ('runId','sourceSetSha256','apphostSha256','closureSha256')} + identity.update({key:first['observation'][key] for key in ('pid','startTicks','uid','device','inode','path')}) + core={**core,'phase':'acquired','identity':identity};mech['writerPresent']=True;emit('acquire') + for row in timeline: + kind=row['kind'] + if kind=='boundary': + if mech['settlement']['phase']!='idle':raise AssertionError('boundary without terminal') + mech['settlement']={'phase':'probing','boundary':row['boundary'],'remaining':32,'deadlineAvailable':True};mech.update(policyPhase='none',policyInvocation='none',policyClosure='none',finalPhase='none');emit('beginObservedBoundary('+literal(row['boundary'])+')') + elif kind=='probe': + if mech['settlement']['phase']=='candidate': + mech['settlement']['phase']='probing';mech.update(policyPhase='none',policyInvocation='none',policyClosure='none',finalPhase='none') + core={**core,'phase':'sampled','candidatePrefix':None};emit('retryObserved('+literal(mech['settlement']['boundary'])+')') + mech['settlement']['remaining']=row['remaining'] + if row['event']=='raw':mech['settlement']['phase']='candidate';emit('completeRecordCandidate') + else:emit('incompleteRecordProbe') + elif kind=='policy': + inv=row['invocation'];args=literal(inv['invocationId'])+','+literal(inv['closureSha256']) + mech.update(policyPhase='started',policyInvocation=inv['invocationId'],policyClosure=inv['closureSha256']);emit('beginPolicy('+args+')') + mech['policyPhase']='ready';emit('readyPolicy('+args+')') + after=row['afterState'];before=core;core=json.loads(json.dumps(after)) + if row['status']=='accepted':core.update(phase='sampled',validatedRevision=before['validatedRevision'],validatedBoundary=before['validatedBoundary'],candidatePrefix=None) + mech.update(rootsValid=row['status']=='accepted',prefixIntact=True,producerBytes=max(mech['producerBytes'],after['bytes'])) + extras=str(after['attemptId'])+','+str(after['probeCount'])+','+str(after['evaluationCount'])+','+str(after['deadlineMicroseconds']) + params=literal(after['prefix'])+','+extras + emit(('rejectRaw('+params+')') if row['status']=='refused' else 'observeRaw('+literal(after['prefix'])+','+literal(row['status']=='accepted')+','+extras+')') + if row['status']=='accepted': + core=after;emit('validate('+literal(row['boundary'])+')') + mech['policyPhase']='evaluated';emit('evaluatedPolicy('+args+')') + mech['policyPhase']='completed';emit('completePolicy('+args+')') + elif kind=='final-observation': + mech.update(finalPhase='reading',readStart=row['readStartMicroseconds']);emit('beginFinalRead('+str(mech['readStart'])+')') + mech.update(finalPhase='read',readEnd=row['readEndMicroseconds']);emit('endFinalRead('+str(mech['readEnd'])+')') + if row['rawBytes']>mech['producerBytes']: + mech['producerBytes']=row['rawBytes'];emit('producerSize('+str(row['rawBytes'])+',false)') + mech.update(finalSize=row['rawBytes'],linearized=row['linearizedMicroseconds'],finalPhase='unchanged' if row['rawBytes']==core['bytes'] else 'grown') + emit('observeFinalSize('+str(row['rawBytes'])+','+str(row['linearizedMicroseconds'])+')') + elif kind=='growth': + core=row['afterState'];mech.update(rootsValid=False,policyPhase='none',policyInvocation='none',policyClosure='none',finalPhase='none');emit('observedGrowth') + elif kind=='producer-after-L': + mech.update(producerBytes=row['rawBytes'],producerContradiction=row['contradictory']);emit('producerSize('+str(row['rawBytes'])+','+literal(row['contradictory'])+')') + elif kind=='consume': + core=row['afterState'];mech['finalPhase']='released';obs=row['observation'];emit('consumeAt('+literal(obs['boundary'])+','+str(obs['releasedMicroseconds'])+','+str(obs['deadlineMicroseconds'])+')') + elif kind=='revoke': + core=row['afterState'] + if row['unavailable']:mech['writerPresent']=False + emit('revokeObserved('+literal(row['unavailable'])+')') + elif kind=='terminal': + if row['outcome']=='accepted': + mech['settlement'].update(phase='idle',boundary='none');emit('completeBoundary('+literal(core['consumedBoundary'])+')') + elif row['check']=='infolog-record-settlement-exhausted': + mech['settlement']['phase']='refused';emit('recordSettlementExhausted') + else: + # Policy/root refusal and evaluation exhaustion have already + # produced the concrete sticky revocation edge. + if not core['stickyInvalid']:raise AssertionError('unrevoked terminal') + else:raise AssertionError('closed RP2 effect') + # Dynamic constants contain public fixture values only. Import exactly the + # production canonical model; no alternate transition definitions. + directory.mkdir(parents=True,exist_ok=True) + shutil.copyfile(source/'GrowingLogEvidence.qnt',directory/'GrowingLogEvidence.qnt') + qnt=directory/(name+'.qnt') + body='module RP2_actual_test {\n import GrowingLogEvidence.* from "./GrowingLogEvidence"\n run actual = init' + for step in steps:body+='\n .then('+step['action']+').expect(evidence == '+literal(step['state'])+')' + qnt.write_text(body+'\n}\n') + trace=directory/(name+'.itf.json') + run=subprocess.run(['quint','test',str(qnt),'--backend=typescript','--main','RP2_actual_test','--match','^actual$','--out-itf',str(directory/(name+'_{test}_{seq}.itf.json')),'--seed','424242','--max-samples','1'],stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,timeout=30) + (directory/(name+'.log')).write_text(run.stdout) + if run.returncode:raise AssertionError('actual full-state canonical correspondence: '+name+'\n'+run.stdout) + generated=directory/(name+'_actual_0.itf.json') + itf=json.loads(generated.read_text()) + # Apply the same volatile-metadata normalization as the owning predecessor + # gate. Preserve every generated variable and ordered state unchanged. + itf['#meta'].pop('description',None);itf['#meta'].pop('timestamp',None) + itf['#meta']['status']='ok' + trace.write_text(json.dumps(itf,separators=(',',':'),sort_keys=True)+'\n') + return {'steps':steps,'trace':trace.name} + +class GrowingLogTests(unittest.TestCase): + def test_policy_diagnostic_decoder_is_total_closed_and_never_echoes(self): + valid=b'{"schema":"fsbar.barc-runtime-evidence-failure-observation/v1","checkpoint":"request-evaluation","kind":"refused"}\n' + self.assertEqual(growing_log._policy_observation(valid)['checkpoint'],'request-evaluation') + invalid=(valid.replace(b'"refused"',b'"PRIVATE_SENTINEL"'),valid+valid,valid.replace(b'"checkpoint"',b'"checkpoint":[],"other"'),b'['+b'x'*2049+b']') + for raw in invalid:self.assertIsNone(growing_log._policy_observation(raw)) + def test_policy_transport_retains_only_one_closed_diagnostic_frame(self): + policy=self.root/'diagnostic-policy' + policy.write_text('''#!/usr/bin/python3 +import hashlib,json,os,sys +args=dict(zip(sys.argv[1::2],sys.argv[2::2]));inv=args["--invocation-id"];closure=args["--closure-sha256"] +ticks=open(f"/proc/{os.getpid()}/stat").read().split()[21] +print(json.dumps({"schema":"fsbar.barc-runtime-evidence-policy-ready/v2","invocationId":inv,"closureSha256":closure,"pid":os.getpid(),"startTicks":ticks,"uid":os.geteuid(),"phase":"ready"},separators=(",",":")),flush=True) +sys.stdin.buffer.read() +print(json.dumps({"schema":"fsbar.barc-runtime-evidence-policy-completed/v2","invocationId":inv,"closureSha256":closure,"pid":os.getpid(),"startTicks":ticks,"uid":os.geteuid(),"phase":"completed","result":{}},separators=(",",":")),flush=True) +sys.stderr.write('{"schema":"fsbar.barc-runtime-evidence-failure-observation/v1","checkpoint":"request-evaluation","kind":"refused"}\\n');sys.stderr.flush() +''');os.chmod(policy,0o700) + runner=object.__new__(GrowingLog);runner.last_policy_observation=None + self.assertEqual(runner._run_policy(str(policy),'/unused','a'*64,b'{}',time.monotonic()+2),b'{}\n') + self.assertEqual(runner.last_policy_observation['checkpoint'],'request-evaluation') + policy.write_text(policy.read_text().replace("sys.stderr.write('{\"schema\"", "sys.stderr.write('PRIVATE_SENTINEL\\n'+ '{\"schema\""));os.chmod(policy,0o700) + runner.last_policy_observation=None + self.assertEqual(runner._run_policy(str(policy),'/unused','a'*64,b'{}',time.monotonic()+2),b'{}\n') + self.assertIsNone(runner.last_policy_observation) + def test_actual_compiled_failure_projection_uses_production_transport(self): + config=self.config();expected={'configSha256':'d'*64,'sourceSetSha256':growing_log._source_set(config),'policySha256':config['artifacts']['runtimeEvidencePolicy']['sha256'],'closureSha256':self.closure_sha} + observation={'schema':'fsbar.barc-stock-failure-observation/v1',**expected,'scope':'post-handoff-pre-browser','check':'policy-result-join','outcome':'policy-nonaccepted','policyObservation':{'schema':'fsbar.barc-runtime-evidence-failure-observation/v1','checkpoint':'request-evaluation','kind':'refused'}} + observation_raw=(json.dumps(observation,separators=(',',':'))+'\n').encode() + operation_raw=b'{"schema":"fsbar.barc-stock-operation-result/v1","status":"failed","category":"refused"}\n' + request={'schema':'fsbar.barc-stock-failure-projection/v1','expected':expected,'observation':observation,'observationSha256':hashlib.sha256(observation_raw).hexdigest(),'operationResultBase64':base64.b64encode(operation_raw).decode(),'operationResultSha256':hashlib.sha256(operation_raw).hexdigest()} + runner=object.__new__(GrowingLog);runner.last_policy_observation=None + encoded=json.dumps(request,separators=(',',':')).encode();projected=json.loads(runner._run_policy(str(self.policy),str(self.closure),self.closure_sha,encoded,time.monotonic()+5)) + self.assertEqual((projected['status'],projected['check'],projected['nativeAcceptance']),('observed-failure','policy-result-join',False)) + request['expected']['sourceSetSha256']='f'*64 + unavailable=json.loads(runner._run_policy(str(self.policy),str(self.closure),self.closure_sha,json.dumps(request,separators=(',',':')).encode(),time.monotonic()+5)) + self.assertEqual(unavailable['status'],'diagnostic-unavailable');self.assertNotIn('PRIVATE_SENTINEL',json.dumps(unavailable)) + request['expected']['sourceSetSha256']=expected['sourceSetSha256'];request['operationResultSha256']='0'*64 + false_digest=json.loads(runner._run_policy(str(self.policy),str(self.closure),self.closure_sha,json.dumps(request,separators=(',',':')).encode(),time.monotonic()+5)) + self.assertEqual(false_digest['status'],'diagnostic-unavailable') + def test_later_final_refresh_failure_drops_noncausal_pending_observation(self): + self.spawn(self.text().replace(b'[DataDirLocater::Check] Isolation Mode!\n',b''));original_run=self.handle._run_policy;original_custody=self.handle._custody;after=[False] + def policy(*args): + output=original_run(*args);after[0]=True;return output + def custody(*args,**kwargs): + if after[0]:raise OSError('PRIVATE_SENTINEL_FINAL_REFRESH') + return original_custody(*args,**kwargs) + self.handle._run_policy=policy;self.handle._custody=custody + with self.assertRaises(OSError) as caught:self.consume('browser') + fact=caught.exception._barc_failure_observation + self.assertEqual((fact['check'],fact['outcome'],fact['policyObservation']),('infolog-final-refresh','os-unavailable',None)) + def test_acquisition_close_failure_preserves_writer_refusal(self): + self.spawn();real_close=os.close;closed=[] + def close_failure(fd):closed.append(fd);raise OSError('PRIVATE_SENTINEL_CLOSE') + with mock.patch.object(GrowingLog,'_writer',side_effect=growing_log.mark_failure(Refused('PRIVATE_SENTINEL_WRITER'),'infolog-writer-match')),mock.patch.object(growing_log.os,'close',side_effect=close_failure): + with self.assertRaises(Refused) as caught:GrowingLog.acquire(self.log,self.handle.process) + for fd in closed:real_close(fd) + fact=caught.exception._barc_failure_observation + self.assertEqual((fact['check'],fact['outcome']),('infolog-writer-match','refused'));self.assertTrue(caught.exception._barc_cleanup_unknown) + def test_three_accepted_growth_samples_report_mechanical_exhaustion(self): + self.spawn();original=self.handle._run_policy;calls=[0] + def grow(*args): + output=original(*args);calls[0]+=1;self.command('append:benign complete record');return output + self.handle._run_policy=grow + with self.assertRaises(Refused) as caught:self.consume('browser') + fact=caught.exception._barc_failure_observation + self.assertEqual(calls[0],3);self.assertEqual((fact['check'],fact['outcome'],fact['policyObservation']),('policy-sample-exhausted','refused',None)) + counters=caught.exception._barc_infolog_mechanical_observation + self.assertEqual((counters['terminalCause'],counters['evaluationsBegun'],counters['evaluationsCompleted'],counters['growthAfterEvaluation']),('policy-evaluation-growth-cap',3,3,3)) + self.assertEqual(counters['probesCompleted'],3) + def test_full_buffered_writer_growth_after_real_policy_exhausts_three_evaluations(self): + self.child=subprocess.Popen([sys.executable,str(pathlib.Path(__file__).with_name('buffered_writer.py')),str(self.log),base64.b64encode(self.text()).decode()],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,start_new_session=True) + def ack(expected): + need=__import__('select').select([self.child.stdout],[],[],2)[0] + self.assertTrue(need,'public buffered writer ack deadline') + self.assertEqual(self.child.stdout.readline().strip(),expected) + ack('ready') + self.handle=acquire_data_root_evidence(self.log,{'pid':self.child.pid,'startTicks':start_ticks(self.child.pid),'uid':os.geteuid()}) + original=self.handle._run_policy + def grow(*args): + result=original(*args) + self.child.stdin.write('growflush\n');self.child.stdin.flush();ack('done') + return result + self.handle._run_policy=grow + try: + with self.assertRaises(Refused) as caught:self.consume('browser') + counters=caught.exception._barc_infolog_mechanical_observation + self.assertEqual((counters['terminalCause'],counters['evaluationsBegun'],counters['evaluationsCompleted'],counters['growthAfterEvaluation']),('policy-evaluation-growth-cap',3,3,3)) + self.assertEqual(counters['probesCompleted'],3) + self.assertEqual(counters['terminalCheck'],'policy-sample-exhausted') + self.assertEqual(counters['tailBytes'],0) + self.assertTrue(caught.exception._barc_infolog_mechanical_retained) + finally: + self.child.stdin.write('close\n');self.child.stdin.flush();self.child.communicate(timeout=2) + def test_genuine_pending_exhaustion_retains_policy_observation(self): + self.spawn(self.text().replace(b'[DataDirLocater::Check] Isolation Mode!\n',b'')) + with self.assertRaises(Refused) as caught:self.consume('browser') + fact=caught.exception._barc_failure_observation + self.assertEqual((fact['check'],fact['outcome']),('policy-sample-exhausted','policy-nonaccepted'));self.assertEqual(fact['policyObservation']['kind'],'pending') + def test_partial_record_settles_before_first_policy_revision(self): + initial=self.text()[:-1];self.spawn(initial);requests=[];actual=self.handle._run_policy;actual_sample=self.handle._sample;probes=[0] + def inspect(*args): + requests.append(json.loads(args[3])['observation']['revision']);return actual(*args) + self.handle._run_policy=inspect + def sample(deadline): + value=actual_sample(deadline);probes[0]+=1 + if probes[0]==1:self.command('appendraw:'+base64.b64encode(b'\n').decode()) + return value + self.handle._sample=sample;result=self.consume('browser') + self.assertEqual((requests,result['revision']),([1],1)) + self.assertGreaterEqual(len(self.handle.settlement_transcript),2) + self.assertEqual(self.handle.settlement_transcript[-1]['event'],'complete') + output=os.environ.get('BAR_SETTLEMENT_TRANSCRIPT') + if output:pathlib.Path(output).write_text(json.dumps(self.handle.settlement_transcript,separators=(',',':'))+'\n') + def test_continuous_partial_tail_exhausts_without_policy_evaluation(self): + self.spawn(self.text()[:-1]);calls=[0] + self.handle._run_policy=lambda *args:(calls.__setitem__(0,calls[0]+1),b'')[1] + with mock.patch.object(growing_log,'MAX_SETTLEMENT_PROBES',3): + with self.assertRaises(Refused) as caught:self.consume('browser') + fact=caught.exception._barc_failure_observation + self.assertEqual((calls[0],self.handle.revision,fact['check'],fact['outcome'],fact['policyObservation']),(0,0,'infolog-record-settlement-exhausted','refused',None)) + def test_settlement_deadline_prevents_policy_launch(self): + self.spawn(self.text()[:-1]);calls=[0];self.handle._run_policy=lambda *args:calls.__setitem__(0,calls[0]+1) + with self.assertRaises(Refused) as caught:self.handle._settle(time.monotonic(),0) + fact=caught.exception._barc_failure_observation + self.assertEqual((calls[0],self.handle.revision,fact['check'],fact['outcome'],fact['policyObservation']),(0,0,'infolog-record-settlement-deadline','deadline',None)) + def test_partial_prefix_rewrite_is_sticky_across_probes(self): + self.spawn(self.text()[:-1]);real_sleep=time.sleep;changed=[False] + def rewrite(_): + if not changed[0]: + changed[0]=True;self.child.stdin.write(b'rewrite\n');self.child.stdin.flush();real_sleep(.03) + else:real_sleep(0) + with mock.patch.object(growing_log.time,'sleep',side_effect=rewrite): + with self.assertRaises(Refused):self.consume('browser') + self.assertTrue(self.handle.revoked);self.assertEqual(self.handle.revision,0) + def test_exact_read_refuses_after_finite_short_read_budget(self): + self.spawn(self.text());actual=os.pread + def short(fd,count,offset):return actual(fd,min(count,1),offset) + with mock.patch.object(growing_log,'MAX_PREAD_CALLS',2),mock.patch.object(growing_log.os,'pread',side_effect=short): + with self.assertRaises(Refused) as caught:self.consume('browser') + self.assertEqual(caught.exception._barc_failure_observation['check'],'infolog-record-settlement-exhausted') + def test_deadline_expiry_inside_writer_census_stops_before_fd_walk_and_policy(self): + self.spawn(self.text());base=time.monotonic();expired=[False];fd_stats=[0];actual_listdir=os.listdir;actual_stat=os.stat;policy=[0] + def listdir(path): + value=actual_listdir(path) + if path==f'/proc/{self.child.pid}/fd':expired[0]=True + return value + def guarded_stat(path,*args,**kwargs): + if expired[0] and str(path).startswith(f'/proc/{self.child.pid}/fd/'): + fd_stats[0]+=1 + return actual_stat(path,*args,**kwargs) + self.handle._run_policy=lambda *args:policy.__setitem__(0,policy[0]+1) + with mock.patch.object(growing_log.time,'monotonic',side_effect=lambda:base+2 if expired[0] else base),mock.patch.object(growing_log.os,'listdir',side_effect=listdir),mock.patch.object(growing_log.os,'stat',side_effect=guarded_stat): + with self.assertRaises(Refused) as caught:self.handle._settle(base+1,0) + self.assertEqual((fd_stats[0],policy[0],self.handle.revision,caught.exception._barc_failure_observation['check']),(0,0,0,'infolog-record-settlement-deadline')) + def test_deadline_at_candidate_promotion_does_not_advance_revision_or_launch_policy(self): + self.spawn(self.text());base=time.monotonic();expired=[False];actual=self.handle._sample;policy=[0] + def expire_after_sample(deadline): + value=actual(deadline);expired[0]=True;return value + self.handle._sample=expire_after_sample;self.handle._run_policy=lambda *args:policy.__setitem__(0,policy[0]+1) + with mock.patch.object(growing_log.time,'monotonic',side_effect=lambda:base+2 if expired[0] else base): + with self.assertRaises(Refused) as caught:self.handle._settle(base+1,0) + self.assertEqual((policy[0],self.handle.revision,caught.exception._barc_failure_observation['check']),(0,0,'infolog-record-settlement-deadline')) + def test_settlement_failure_after_pending_drops_policy_observation(self): + self.spawn(self.text().replace(b'[DataDirLocater::Check] Isolation Mode!\n',b''));actual=self.handle._run_policy;calls=[0] + def append_partial(*args): + output=actual(*args);calls[0]+=1 + if calls[0]==1:self.command('appendraw:'+base64.b64encode(b'partial').decode()) + return output + self.handle._run_policy=append_partial + with mock.patch.object(growing_log,'MAX_SETTLEMENT_PROBES',4): + with self.assertRaises(Refused) as caught:self.consume('browser') + fact=caught.exception._barc_failure_observation + self.assertEqual((calls[0],fact['check'],fact['outcome'],fact['policyObservation']),(1,'infolog-record-settlement-exhausted','refused',None)) + @classmethod + def setUpClass(cls): + cls.fixture=tempfile.TemporaryDirectory(prefix='bar-policy-closure-fixture-');root=pathlib.Path(cls.fixture.name) + managed=root/'managed';provenance=root/'provenance';managed.mkdir();provenance.mkdir() + for name in ('RuntimeEvidence','RuntimeEvidence.dll','RuntimeEvidence.deps.json','RuntimeEvidence.runtimeconfig.json','FSharp.Core.dll'): + shutil.copyfile(POLICY_BUILD/name,managed/name);os.chmod(managed/name,0o555 if name=='RuntimeEvidence' else 0o444) + shutil.copyfile(POLICY_BUILD/'RuntimeEvidence.pdb',provenance/'RuntimeEvidence.pdb') + system_dotnet=pathlib.Path(os.environ.get('DOTNET_ROOT','/usr/share/dotnet')).resolve() + version=subprocess.check_output(['dotnet','--list-runtimes'],text=True).splitlines()[-1].split()[1] + system_framework=system_dotnet/'shared/Microsoft.NETCore.App'/version + system_fxr=max((system_dotnet/'host/fxr').iterdir(),key=lambda p:tuple(map(int,p.name.split('.')))) + cls.dotnet_root=root/'dotnet';framework=cls.dotnet_root/'shared/Microsoft.NETCore.App'/version;fxr=cls.dotnet_root/'host/fxr'/system_fxr.name + framework.parent.mkdir(parents=True);fxr.parent.mkdir(parents=True) + shutil.copytree(system_framework,framework);shutil.copytree(system_fxr,fxr) + shutil.copyfile(system_dotnet/'dotnet',cls.dotnet_root/'dotnet') + for base,directories,files in os.walk(cls.dotnet_root): + for name in directories:os.chmod(pathlib.Path(base)/name,0o555) + for name in files:os.chmod(pathlib.Path(base)/name,0o555 if '.so' in name else 0o444) + os.chmod(cls.dotnet_root/'dotnet',0o555);os.chmod(cls.dotnet_root,0o555) + if any(path.is_symlink() for path in cls.dotnet_root.rglob('*')):raise AssertionError('private runtime staging retained a link') + def row(path,role=None): + info=path.stat();value={'path':str(path),'bytes':info.st_size,'sha256':hashlib.sha256(path.read_bytes()).hexdigest(),'device':f'{os.major(info.st_dev):x}:{os.minor(info.st_dev):x}','inode':info.st_ino,'ownerUid':info.st_uid,'mode':stat.S_IMODE(info.st_mode),'links':info.st_nlink} + if role:value={'role':role,**value} + return value + runtime_paths={path.resolve() for directory in (framework,fxr) for path in directory.rglob('*') if path.is_file()} + candidates=[managed/'RuntimeEvidence',framework/'libcoreclr.so',framework/'libhostpolicy.so',fxr/'libhostfxr.so',*framework.glob('*.so')] + for candidate in candidates: + output=subprocess.run(['ldd',str(candidate)],text=True,stdout=subprocess.PIPE,check=True).stdout + for line in output.splitlines(): + fields=line.replace('=>',' ').split() + runtime_paths.update(pathlib.Path(x).resolve() for x in fields if x.startswith('/') and pathlib.Path(x).is_file()) + dynamic_patterns={'icu':'libicu*.so*','crypto':'libcrypto.so*','ssl':'libssl.so*','brotli':'libbrotli*.so*','zlib':'libz.so*','zstd':'libzstd.so*'} + ldconfig=subprocess.run(['ldconfig','-p'],stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=True) + if len(ldconfig.stdout)>1024*1024 or len(ldconfig.stderr)>4096:raise AssertionError('bounded loader cache census') + dynamic={name:set() for name in dynamic_patterns} + for line in ldconfig.stdout.decode('utf-8','strict').splitlines(): + if '=>' not in line:continue + raw=line.rsplit('=>',1)[1].strip();path=pathlib.Path(raw) + for name,pattern in dynamic_patterns.items(): + if fnmatch.fnmatchcase(path.name,pattern): + resolved=path.resolve(strict=True) + if resolved.is_file():dynamic[name].add(resolved) + if sum(map(len,dynamic.values()))>128:raise AssertionError('bounded dynamic runtime census') + cls.dynamic_libraries={name:sorted(paths) for name,paths in dynamic.items()} + runtime_paths.update(path for paths in cls.dynamic_libraries.values() for path in paths) + runtime_paths.add(pathlib.Path('/etc/ld.so.cache')) + runtime=[row(path) for path in sorted(runtime_paths)] + roles={'hostfxr':str(fxr/'libhostfxr.so'),'hostpolicy':str(framework/'libhostpolicy.so'),'coreLib':str(framework/'System.Private.CoreLib.dll'),'coreClr':str(framework/'libcoreclr.so'),'jit':str(framework/'libclrjit.so')} + policy_commit=subprocess.check_output(['git','-C',str(POLICY_SOURCE),'rev-parse','HEAD'],text=True).strip();policy_tree=subprocess.check_output(['git','-C',str(POLICY_SOURCE),'rev-parse','HEAD^{tree}'],text=True).strip() + product_commit='2cd9f47dd120d43b6781b6edc7dbb1d571cc51a1';product_tree='4fee295bf14f6b2fe2369085c1e743610709297e' + highbar_commit='54e17084c35b90584f8f84efc468bcc8943e4ec0' + product_roles={'fsbarCommit':product_commit,'highbarCommit':highbar_commit} + product_source_set=hashlib.sha256(json.dumps(product_roles,sort_keys=True,separators=(',',':')).encode()).hexdigest() + source_link=json.dumps({'documents':{'/_/*':f'https://raw.githubusercontent.com/FS-GG/FSBarV2/{policy_commit}/*'}},separators=(',',':')).encode() + (provenance/'RuntimeEvidence.sourcelink.json').write_bytes(source_link) + (provenance/'policy-source.json').write_text(json.dumps({'schema':'fsbar.barc-source-identity/v1','role':'policySourceManifest','commit':policy_commit,'tree':policy_tree},separators=(',',':'))) + (provenance/'product-source.json').write_text(json.dumps({'schema':'fsbar.barc-source-identity/v1','role':'productSourceManifest','commit':product_commit,'tree':product_tree},separators=(',',':'))) + (provenance/'product-role-graph.json').write_text(json.dumps({'schema':'fsbar.barc-product-source-role-graph/v1','roles':product_roles},separators=(',',':'))) + (provenance/'locked-build-inputs.json').write_text(json.dumps({'schema':'fsbar.barc-locked-build-inputs/v1','policyProjectSha256':hashlib.sha256((POLICY_SOURCE/'tests/Broker.NativeProof/RuntimeEvidence/RuntimeEvidence.fsproj').read_bytes()).hexdigest(),'policyLockSha256':hashlib.sha256((POLICY_SOURCE/'tests/Broker.NativeProof/RuntimeEvidence/RuntimeEvidence.packages.lock.json').read_bytes()).hexdigest(),'policyTree':policy_tree},separators=(',',':'))) + (provenance/'toolchain-receipt.json').write_text(json.dumps({'schema':'fsbar.barc-toolchain-receipt/v1','dotnetSdk':subprocess.check_output(['dotnet','--version'],text=True).strip(),'targetFramework':'net10.0'},separators=(',',':'))) + shutil.copyfile(pathlib.Path(__file__).with_name('source-manifest.json'),provenance/'helper-source-manifest.json') + helper_identity=json.loads((provenance/'helper-source-manifest.json').read_bytes()) + managed_rows=[row(managed/name,role) for role,name in [('apphost','RuntimeEvidence'),('managedDll','RuntimeEvidence.dll'),('depsJson','RuntimeEvidence.deps.json'),('runtimeConfigJson','RuntimeEvidence.runtimeconfig.json'),('fsharpCore','FSharp.Core.dll')]] + managed_hashes={item['role']:item['sha256'] for item in managed_rows} + receipt={'schema':'fsbar.barc-runtime-evidence-build-receipt/v2','policyCommit':policy_commit,'policyTree':policy_tree,'productCommit':product_commit,'productTree':product_tree,'helperCommit':helper_identity['publicHead'],'helperTree':helper_identity['publicTree'],'productSourceSetSha256':product_source_set,'productRoleGraphSha256':hashlib.sha256((provenance/'product-role-graph.json').read_bytes()).hexdigest(),'lockedBuildInputsSha256':hashlib.sha256((provenance/'locked-build-inputs.json').read_bytes()).hexdigest(),'toolchainReceiptSha256':hashlib.sha256((provenance/'toolchain-receipt.json').read_bytes()).hexdigest(),'managed':managed_hashes,'pdbSha256':hashlib.sha256((provenance/'RuntimeEvidence.pdb').read_bytes()).hexdigest(),'sourceLinkSha256':hashlib.sha256(source_link).hexdigest(),'helperManifestSha256':hashlib.sha256((provenance/'helper-source-manifest.json').read_bytes()).hexdigest()} + (provenance/'build-receipt.json').write_text(json.dumps(receipt,separators=(',',':'))) + for path in provenance.iterdir():os.chmod(path,0o444) + provenance_rows=[row(provenance/name,role) for role,name in [('pdb','RuntimeEvidence.pdb'),('sourceLink','RuntimeEvidence.sourcelink.json'),('buildReceipt','build-receipt.json'),('helperManifest','helper-source-manifest.json'),('policySourceManifest','policy-source.json'),('productSourceManifest','product-source.json'),('productRoleGraph','product-role-graph.json'),('lockedBuildInputs','locked-build-inputs.json'),('toolchainReceipt','toolchain-receipt.json')]] + source={'policyCommit':policy_commit,'policyTree':policy_tree,'productCommit':product_commit,'productTree':product_tree,'productSourceSetSha256':product_source_set,**{role+'Sha256':next(item['sha256'] for item in provenance_rows if item['role']==role) for role in ('policySourceManifest','productSourceManifest','productRoleGraph','lockedBuildInputs','toolchainReceipt','buildReceipt','pdb','sourceLink','helperManifest')}} + def directory(path): + info=path.stat();names=sorted(p.name for p in path.iterdir());return {'path':str(path),'ownerUid':info.st_uid,'mode':stat.S_IMODE(info.st_mode),'entriesSha256':hashlib.sha256('\n'.join(names).encode()).hexdigest()} + os.chmod(managed,0o555);os.chmod(provenance,0o555) + search_paths={managed,provenance,framework,fxr,framework.parent,fxr.parent,*[path.parent for path in runtime_paths]} + search=[directory(path) for path in sorted(search_paths)] + value={'schema':'fsbar.barc-runtime-evidence-policy-closure/v3','custodyRoot':str(root),'managedRoot':str(managed),'provenanceRoot':str(provenance),'runtimeRoots':[str(framework),str(fxr)],'searchLayout':search,'managed':managed_rows,'provenance':provenance_rows,'runtime':runtime,'runtimeRoles':roles,'source':source,'custody':{'ownerUid':os.geteuid(),'executablePath':str(managed/'RuntimeEvidence'),'fixedArgv':[str(managed/'RuntimeEvidence'),'--closure-manifest','--closure-sha256','--invocation-id']}} + cls.closure=root/'closure.json';cls.closure.write_text(json.dumps(value,separators=(',',':')));os.chmod(cls.closure,0o600) + cls.policy=managed/'RuntimeEvidence';cls.closure_sha=hashlib.sha256(cls.closure.read_bytes()).hexdigest() + @classmethod + def tearDownClass(cls): + for path in pathlib.Path(cls.fixture.name).rglob('*'): + try:os.chmod(path,0o700 if path.is_dir() else 0o600) + except FileNotFoundError:pass + cls.fixture.cleanup() + def setUp(self): + self.temp=tempfile.TemporaryDirectory(prefix='bar-growing-log-');self.root=pathlib.Path(self.temp.name);os.chmod(self.root,0o700) + self.write=self.root/'engine';self.data=self.root/'runtime-data';self.write.mkdir(mode=0o700);self.data.mkdir(mode=0o700);self.log=self.write/'infolog.txt';self.child=None;self.handle=None + real_popen=subprocess.Popen + def private_runtime_popen(argv,*args,**kwargs): + if argv and pathlib.Path(argv[0])==self.policy: + env=dict(kwargs.get('env') or {});env['DOTNET_ROOT']=str(self.dotnet_root);env['DOTNET_ROOT_X64']=str(self.dotnet_root);env['DOTNET_MULTILEVEL_LOOKUP']='0';kwargs['env']=env + return real_popen(argv,*args,**kwargs) + self.popen_patch=mock.patch.object(growing_log.subprocess,'Popen',side_effect=private_runtime_popen);self.popen_patch.start() + def tearDown(self): + self.popen_patch.stop() + if self.handle: + try:self.handle.close() + except OSError:pass + if self.child and self.child.poll() is None: + self.child.kill();self.child.wait() + if self.child and self.child.stdin: + self.child.stdin.close() + self.temp.cleanup() + def text(self): + return (f'[DataDirLocater::Check] Isolation Mode!\n[DataDirLocater::FindWriteableDataDir] using writeable data-directory "{self.write}"\n[DataDirLocater::FilterUsableDataDirs] using read-write data directory: {self.write}/\n[DataDirLocater::FilterUsableDataDirs] using read-only data directory: {self.data}/\n').encode() + def spawn(self,initial=None): + initial=self.text() if initial is None else initial + self.child=subprocess.Popen([sys.executable,'-c','import base64\n'+WRITER,str(self.log),base64.b64encode(initial).decode()],stdin=subprocess.PIPE,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,start_new_session=True) + end=time.time()+3 + while not self.log.exists() and time.time()=phase_end:break + sleep(min(.02,max(0,phase_end-now()))) + if not held_live(owner,errors):break + # Discovery errors survive even if every held PID settled. Never signal unseen IDs. + remaining_held=held_live(owner,errors) + if remaining_held:errors.add('held processes remain after finite cleanup') + return {'status':'Unknown'if errors else 'settled','signals':counts,'ownedLive':len(remaining_held),'remainingHeld':remaining_held,'errors':sorted(errors),'discoveryComplete':not bool(errors),'heldIdentities':list(owner.records.values())} + +def custody_observation(path,root,reason,phase,known_owned,live_owned,lstat=None,error=None): + p=pathlib.Path(path);observation={'schema':'bar.stage-custody-refusal/v1','reason':reason,'absolutePath':str(p),'relativePath':str(p.relative_to(root)),'phase':phase,'observerIdentity':identity(os.getpid()),'knownOwnedProcessIdentities':known_owned,'observedLiveOwnedProcessIdentities':live_owned,'processCausedObjectClaim':False,'observedMonotonicSeconds':clock()} + if lstat is None:observation['lstat']={'status':'Unknown','errorType':type(error).__name__,'errno':getattr(error,'errno',None)} + else: + kind=stat.S_IFMT(lstat.st_mode);types={stat.S_IFREG:'regular',stat.S_IFDIR:'directory',stat.S_IFLNK:'symlink',stat.S_IFSOCK:'socket',stat.S_IFIFO:'fifo',stat.S_IFCHR:'character-device',stat.S_IFBLK:'block-device'} + observation['lstat']={'status':'Observed','type':types.get(kind,'unknown'),'typeBits':kind,'mode':stat.S_IMODE(lstat.st_mode),'rawMode':lstat.st_mode,'device':lstat.st_dev,'inode':lstat.st_ino,'uid':lstat.st_uid,'gid':lstat.st_gid,'nlink':lstat.st_nlink,'bytes':lstat.st_size,'mtimeNs':lstat.st_mtime_ns,'ctimeNs':lstat.st_ctime_ns} + return observation + +def quota(root,maximum,phase=None,known_owned=(),live_owned=(),runtime_socket=None): + total=0;files=0 + for base,dirs,names in os.walk(root,followlinks=False): + for name in dirs+names: + p=pathlib.Path(base)/name + try:st=p.lstat() + except OSError as e:raise CustodyRefused('stage lstat unavailable',custody_observation(p,root,'stage lstat unavailable',phase,known_owned,live_owned,error=e)) from e + reason=None + if name in dirs: + if stat.S_ISLNK(st.st_mode):reason='stage symlink directory' + elif stat.S_ISSOCK(st.st_mode)and runtime_socket is not None: + runtime_socket.inspect_for_quota(p) + elif not (stat.S_ISREG(st.st_mode) and st.st_uid==os.geteuid() and st.st_nlink==1):reason='stage file custody' + if reason:raise CustodyRefused(reason,custody_observation(p,root,reason,phase,known_owned,live_owned,lstat=st)) + if name in names:files+=1;total+=st.st_size;need(files<=1024 and total<=maximum,'stage output census bound') + return {'files':files,'bytes':total} +def census(root,runtime_socket=None): + result=[] + if pathlib.Path(root).exists(): + for p in sorted(pathlib.Path(root).rglob('*')): + s=p.lstat() + if stat.S_ISSOCK(s.st_mode):need(runtime_socket is not None,'unknown census socket');result.append(runtime_socket.inspect_for_quota(p)) + elif p.is_file():need(not p.is_symlink(),'census symlink');result.append(physical(p)) + return result + diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/native_evidence.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/native_evidence.py new file mode 100644 index 0000000..cb362e6 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/native_evidence.py @@ -0,0 +1,135 @@ +"""Read-only joins over actual custody outputs. No authority or command producer. +New tagged Lua observations are parsed from the policy-authenticated Recoil infolog. +Source tests may supply controlled records; those are never native acceptance. +""" +import hashlib,json,re,base64,os,stat +from qualify_prearm import need,exact,held_bytes,prefix_bytes,parse,sha,dec,live_process,mapped_roles +PREFIX=b'BARC_PREARM_V1 ' +FIELDS={ + 'content':{'path','bytes','sha512'}, + 'archives':{'map','game','map_sha512','game_sha512'}, + 'ai':{'team','id','name','host','short','version','options'}, + 'config':{'key','type','value'}, + 'vfs':{'path','archive','absolute'}, + 'actors':{'team','enemy','factory','builder','target','seed','product'}, + 'empty':{'factory','observed_frame'}, + 'order':{'factory','ordinal','definition','count','options'}, + 'queue':{'factory','ordinal','definition','tag','options'}, + 'complete':{'factory','count','empty_frame'}, + 'refused':{'reason'}, +} +def text(value): + if value=='-':return '' + need(type(value)is str and re.fullmatch(r'(?:[0-9a-f]{2}){1,4096}',value),'lua-hex-text');return bytes.fromhex(value).decode('utf8') +def observations(raw,nonce): + need(type(raw)is bytes and len(raw)<=10*1024*1024,'infolog-bound');out=[] + for line in raw.splitlines(): + at=line.find(PREFIX) + if at<0:continue + line=line[at+len(PREFIX):];need(len(line)<=16384 and b'\0'not in line,'lua-line-bound') + fields={} + for item in line.decode('ascii').split(' '): + need(item.count('=')==1,'lua-token');key,value=item.split('=');need(key not in fields and value,'lua-duplicate-or-empty');fields[key]=value + kind=fields.get('kind');need(kind in FIELDS,'lua-kind');exact(fields,{'kind','nonce','frame'}|FIELDS[kind],'lua-'+kind) + need(fields['nonce']==nonce and re.fullmatch('[A-Za-z0-9._-]{1,64}',nonce),'lua-nonce');need(dec(fields['frame'])<=2**32-1,'lua-frame') + need(kind!='refused','lua-runtime-refusal');out.append(fields) + need(0=len(raw) and receipt['suffixBytes']==receipt['observedBytes']-len(raw),'prefix-byte-observation') + # Authenticate an actual writable O_APPEND FD in the exact live writer generation. + live_process(receipt['writer']);writable=False + names=os.listdir('/proc/'+str(receipt['writer']['pid'])+'/fd');need(len(names)<=256,'writer-fd-bound') + for name in names: + fdpath='/proc/'+str(receipt['writer']['pid'])+'/fd/'+name + try: + st=os.stat(fdpath);info=open('/proc/'+str(receipt['writer']['pid'])+'/fdinfo/'+name).read(4096);m=re.search(r'^flags:\s+([0-7]+)$',info,re.M) + except FileNotFoundError:continue + if (st.st_dev,st.st_ino)==(receipt['livePin']['device'],receipt['livePin']['inode']) and m: + flags=int(m[1],8);writable=writable or bool(flags&os.O_APPEND and flags&os.O_ACCMODE in [os.O_WRONLY,os.O_RDWR]) + need(writable,'actual-prefix-writable-append-fd');live_process(receipt['writer']) + infolog(value['infolog'],contract,deadline) + +def configuration(value,contract,deadline): + exact(value,['schema','source','roles','loaded','settings','configPin','infolog','review','archiveInventory','producerSource'],'native-evidence') + need(value['schema']=='bar.prearm-native-evidence-inputs/v2' and value['source']==contract['source'],'native-evidence-source') + roles=value['roles'];exact(roles,['engine','plugin','passivePlugin','fixture','observer','gameInventory','mapInventory','settings','setupScript','nullAiSource'],'native-roles') + for pin in roles.values():held_bytes(pin,128*1024*1024,deadline) + raw,config=infolog(value['infolog'],contract,deadline);need(value['configPin']==value['infolog']['configPin'],'configuration-policy-join') + loaded=parse(held_bytes(value['loaded'],1024*1024,deadline));exact(loaded,['schema','status','nativeAcceptance','beforeBrowserEffect','configSha256','sourceSetSha256','process','runtimeClosureSha256','rawMapsSha256','mapsBytes','executableMapCount','uniqueExecutableFiles','required','mappings','observationBeginMonotonicNs','observationEndMonotonicNs'],'loaded') + need(loaded['schema']=='fsbar.barc-stock-loaded-custody/v1' and loaded['status']=='verified-snapshot' and loaded['nativeAcceptance']is False and loaded['beforeBrowserEffect']is True and loaded['configSha256']==value['configPin']['sha256'] and loaded['sourceSetSha256']==sha(json.dumps(contract['source'],sort_keys=True,separators=(',',':')).encode()) and loaded['runtimeClosureSha256']==config['artifacts']['runtimeClosure']['sha256'],'actual-loaded-inputs') + policy_result=parse(held_bytes(value['infolog']['result'],65536,deadline));need(all(policy_result['state']['identity'][k]==loaded['process'][k]for k in ['pid','startTicks','uid']),'actual-infolog-engine-generation') + for role in ['engine','plugin','passivePlugin']: + need(any(x['path']==roles[role]['path'] and x['sha256']==roles[role]['sha256'] and 'x'in x['permissions'] and int(x['mappedInode'])==roles[role]['inode'] and os.makedev(*[int(part,16) for part in x['mappedDevice'].split(':')])==roles[role]['device'] for x in loaded['mappings']),'actual-mapped-'+role) + if role!='passivePlugin':need(loaded['required'][role]=={k:roles[role][k]for k in ['path','sha256']},'required-map-join') + settings=parse(held_bytes(value['settings'],65536,deadline));exact(settings,['schema','seedPath','seedSha256','outputPath','copiedSha256','actualEngineArgv','originalEngineArgv','packetSha256','seedDevice','seedInode','outputDevice','outputInode','nativeAcceptance'],'settings') + need(settings['schema']=='fsbar.barc-stock-engine-settings-copy/v1' and settings['nativeAcceptance']is False and settings['actualEngineArgv']==config['commands']['engine'] and settings['outputPath']==roles['settings']['path'] and settings['copiedSha256']==settings['seedSha256']==roles['settings']['sha256'] and (settings['outputDevice'],settings['outputInode'])==(roles['settings']['device'],roles['settings']['inode']),'actual-settings-copy') + review=parse(held_bytes(value['review'],65536,deadline));exact(review,['schema','source','rolesSha256','setupOnlyOrderSites','postSetupOrderSites','competingControllers','aiSlots','effectiveConfig','vfs','archives','sourceReceiptPins'],'static-source-review') + need(review['schema']=='bar.prearm-static-source-review/v2' and review['source']==contract['source'] and review['rolesSha256']==sha(json.dumps({role:{k:pin[k] for k in ['path','sha256']} for role,pin in roles.items()},sort_keys=True,separators=(',',':')).encode()) and review['postSetupOrderSites']==[] and review['competingControllers']==[],'static-order-closure') + need(review['setupOnlyOrderSites']==[{'fixtureSha256':roles['fixture']['sha256'],'count':contract['setup']['seedCount'],'definition':contract['setup']['seedDefinitionId'],'options':0}],'static-finite-order-site') + need(type(review['sourceReceiptPins'])is list and 1<=len(review['sourceReceiptPins'])<=32,'source-receipt-closure') + for pin in review['sourceReceiptPins']:held_bytes(pin,16*1024*1024,deadline) + sources=value['producerSource'];exact(sources,['CircuitAI.h','CircuitAI.cpp'],'source');h=held_bytes(sources['CircuitAI.h'],2*1024*1024,deadline);cpp=held_bytes(sources['CircuitAI.cpp'],2*1024*1024,deadline);need(b'bool enableBuiltin = false;'in h and b'if (enableBuiltin)'in cpp and b'modules.push_back(grpcGateway);'in cpp and b'built-in Circuit decision modules cannot be re-enabled'in cpp,'external-control-source') + null=held_bytes(roles['nullAiSource'],2*1024*1024,deadline);need(re.search(rb'EXPORT\(int\) handleEvent\([^)]*\)\s*\{\s*// TODO: do something\s*// signal: ok\s*return 0;\s*\}',null) is not None,'passive-null-source') + obs=observations(raw,contract['runId']);contents=[r for r in obs if r['kind']=='content'];need(len(contents)==2,'two-vfs-contents') + for role,path in [('fixture','LuaRules/Gadgets/barc_stock_queue_fixture.lua'),('observer','LuaRules/Gadgets/barc_stock_queue_reader.lua')]: + matches=[r for r in contents if text(r['path'])==path];need(len(matches)==1,'vfs-path');body=held_bytes(roles[role],131072,deadline);need(dec(matches[0]['bytes'])==len(body) and matches[0]['sha512']==hashlib.sha512(body).hexdigest(),'vfs-actual-content-sha512') + actors=[r for r in obs if r['kind']=='actors'];need(len(actors)==1 and actors[0]['factory']==contract['factory']['id'] and dec(actors[0]['seed'])==contract['setup']['seedDefinitionId'] and dec(actors[0]['product'])==contract['setup']['productDefinitionId'],'actual-setup-actors') + actual_ai=[r for r in obs if r['kind']=='ai'];need(len(actual_ai)==len(review['aiSlots'])==2,'all-two-ai-slots') + for row in actual_ai: + expected=next((x for x in review['aiSlots'] if x['team']==int(row['team'])),None);need(expected is not None and text(row['short'])==expected['shortName'] and text(row['version'])==expected['version'] and text(row['options'])==expected['options'] and expected['mode']in ['external-control','passive-null'],'actual-ai-identity') + need({x['shortName']for x in review['aiSlots']}=={'highBar','NullAI'} and len({x['team']for x in review['aiSlots']})==2,'known-ai-slots-only') + actual_config={text(r['key']):{'type':r['type'],'value':text(r['value'])}for r in obs if r['kind']=='config'};need(len([r for r in obs if r['kind']=='config'])==len(actual_config)==len(review['effectiveConfig']) and actual_config==review['effectiveConfig'],'actual-effective-settings') + actual_vfs=[{k:text(r[k])for k in ['path','archive','absolute']}for r in obs if r['kind']=='vfs'];need(actual_vfs==review['vfs'],'actual-vfs-search-selection') + archives=[r for r in obs if r['kind']=='archives'];need(len(archives)==1,'archive-observation');archive={k:(archives[0][k] if k.endswith('sha512')else text(archives[0][k]))for k in ['map','game','map_sha512','game_sha512']};need(archive==review['archives'] and all(re.fullmatch('[0-9a-f]{128}',archive[k])for k in ['map_sha512','game_sha512']),'native-complete-archive-sha512') + inventory=parse(held_bytes(value['archiveInventory'],16*1024*1024,deadline));exact(inventory,['schema','archives','gameInventory','mapInventory','searchClosure'],'archive-inventory');need(inventory['schema']=='bar.prearm-selected-archive-inventory/v1' and inventory['archives']==archive and inventory['gameInventory']==roles['gameInventory'] and inventory['mapInventory']==roles['mapInventory'],'complete-package-inventory');need(type(inventory['searchClosure'])is list and inventory['searchClosure'],'archive-search-closure') + for pin in inventory['searchClosure']:held_bytes(pin,128*1024*1024,deadline) + complete=[r for r in obs if r['kind']=='complete'];empty=[r for r in obs if r['kind']=='empty'];orders=[r for r in obs if r['kind']=='order'];queues=[r for r in obs if r['kind']=='queue'];need(len(complete)==len(empty)==1 and len(orders)==len(queues)==contract['setup']['seedCount'],'finite-observed-setup') + c=complete[0];need(c['factory']==contract['factory']['id'] and dec(c['count'])==contract['setup']['seedCount'] and dec(c['empty_frame'])==dec(empty[0]['observed_frame']) and dec(empty[0]['frame'])=pin['bytes'] and s.st_size<=limit and s.st_uid==pin['uid'] and stat.S_IMODE(s.st_mode)==pin['mode'] and (s.st_dev,s.st_ino)==(pin['device'],pin['inode']) and (named.st_dev,named.st_ino)==(s.st_dev,s.st_ino),'prefix-custody-drift');return s + check();parts=[];offset=0 + while offset0 and dec(v['lifetime'])>0,'reference-positive');return v +def b64(v,lo,hi): + need(type(v)is str,'base64-type') + try:raw=base64.b64decode(v,validate=True) + except Exception:raise Refused('base64') + need(lo<=len(raw)<=hi and base64.b64encode(raw).decode()==v,'base64-canonical');return raw +def basis(v): + exact(v,['token','stateSequence','frame','matchIncarnation','processIncarnation','stateChannelIncarnation','snapshotSendMonotonicNs','effectiveCadenceFrames'],'native-basis') + b64(v['token'],1,64);b64(v['matchIncarnation'],16,16);need(dec(v['stateSequence'])>0 and dec(v['snapshotSendMonotonicNs'])>0,'basis-positive') + need(type(v['frame'])is int and 0<=v['frame']<=2**32-1 and type(v['effectiveCadenceFrames'])is int and 019 and tail[0] not in ['Z','X']and m and tail[19]==process['startTicks']and int(m[1])==process['uid'],'live-pid-generation') + read();read() + finally:os.close(fd) +def mapped_roles(processes,roles): + engines=[p for p in processes if p['role']=='engine'];need(len(engines)==1,'engine-process-custody') + with open('/proc/'+str(engines[0]['pid'])+'/maps','rb')as stream:body=stream.read(1024*1024+1) + need(len(body)<=1024*1024,'loaded-maps-bound');lines=body.decode().splitlines() + for role in ['engine','plugin']: + pin=roles[role];found=False + for line in lines: + fields=line.split(None,5) + if len(fields)==6 and fields[5]==pin['path']: + dev=fields[3].split(':');found=found or (int(fields[4])==pin['inode']and os.makedev(int(dev[0],16),int(dev[1],16))==pin['device']) + need(found,'current-engine-plugin-mapping') +def framed(value,magic): + need(type(value)is str and value.isascii()and '\0'not in value and len(value)<=8192 and value.endswith('\n'),'stock-frame') + lines=value[:-1].split('\n');need(len(lines)>=3 and lines[0]==magic and lines[-1]=='end'and all(len(x)<=512 for x in lines),'stock-framing');need(re.fullmatch(r'length=[0-9]{8}',lines[1])and int(lines[1][7:])==len(value),'stock-frame-length');return lines +def field(value,name):need(value.startswith(name+'='),'stock-field');return value[len(name)+1:] +def row_text(row):return row['domain']+'|'+str(row['id'])+'|'+str(row['codedOptions'])+'|'+str(row['tag'])+'|'+','.join(row['float32params']) +def stock_revision(c,rows): + parts=[b'barc-stock-queue-revision/1\0'] + def add(tag,v):parts.append(bytes([tag])+struct.pack('>I',len(v))+v) + u32=lambda v:struct.pack('>I',v);u64=lambda v:struct.pack('>Q',int(v)) + values=[u32(2),c['profile'].encode(),u32(2),b64(c['catalogueId'],16,16),u64(c['catalogueRevision']),c['engineVersion'].encode(),c['gameName'].encode(),c['gameVersion'].encode(),bytes.fromhex(c['contentSha256']),u32(int(c['actor']['id'])),u64(c['actor']['lifetime']),c['domain'].encode(),u32(len(rows))] + for n,v in enumerate(values,1):add(n,v) + for row in rows:add(14,row_text(row).encode('ascii')) + return str(int.from_bytes(hashlib.sha256(b''.join(parts)).digest()[:8],'big')or 1) +def stock_record(r): + exact(r,['schema','runId','sequence','phase','readFrame','perspectiveTeamId','nativeBasis','context','reader','queue','dispatch'],'stock-row');basis(r['nativeBasis']);need(type(r['readFrame'])is int and 0<=r['readFrame']<=2**32-1 and type(r['perspectiveTeamId'])is int and 0<=r['perspectiveTeamId']<=255,'stock-envelope') + c=r['context'];exact(c,['profile','tacticalRevision','queueEvidenceScheme','actor','catalogueId','catalogueRevision','engineVersion','gameName','gameVersion','contentSha256','domain'],'stock-context');reference(c['actor']);b64(c['catalogueId'],16,16) + need(c['profile']=='barc-live-tactical-stock-v1'and c['tacticalRevision']==2 and c['queueEvidenceScheme']==2 and c['engineVersion']=='2026.07.04'and c['domain']in ['production','rally']and int(c['actor']['id'])<=31999 and dec(c['catalogueRevision'])>0 and re.fullmatch('[0-9a-f]{64}',c['contentSha256'])and all(type(c[k])is str and 0=9 and response[2]==req[2]and field(response[3],'request-sha256')==sha(reader['request'].encode())and field(response[4],'status')=='ok'and response[5]=='domain='+c['domain']and response[6]=='unit='+c['actor']['id'],'stock-response-echo') + count=dec(field(response[7],'count'));need(count<=64 and len(response)==count+9,'stock-response-count');q=r['queue'];exact(q,['revision','entries'],'stock-queue');need(type(q['entries'])is list and len(q['entries'])==count and dec(q['revision'])>0,'stock-queue-count');expanded=[];params=0 + for row,line in zip(q['entries'],response[8:-1]): + exact(row,['id','codedOptions','tag','float32params'],'stock-entry');need(all(type(row[k])is int and -2**31<=row[k]<2**31 for k in ['id','tag'])and type(row['codedOptions'])is int and 0<=row['codedOptions']<=65535 and type(row['float32params'])is list and len(row['float32params'])<=16,'stock-entry-bound') + for bits in row['float32params']:need(type(bits)is str and re.fullmatch('[0-9a-f]{8}',bits)and ((int(bits,16)>>23)&255)!=255,'stock-float32') + params+=len(row['float32params']);x={'domain':c['domain'],**row};need(field(line,'row')==row_text(x),'stock-response-row');expanded.append(x) + need(params<=256 and q['revision']==stock_revision(c,expanded),'stock-revision') + +def configuration_evidence(cfg,contract,deadline): + from native_evidence import configuration + return configuration(cfg,contract,deadline) + +def derive(contract,deadline): + exact(contract,['schema','runId','source','writer','acceptedGeneration','factory','setup','inputs','completeRecordEvidence','lifecycle','outputPath'],'contract') + need(contract['schema']=='bar.prearm-sealed-input-contract/v1','contract-schema');lifecycle=contract['lifecycle'];exact(lifecycle,['startedMonotonicNs','transitionDeadlineMonotonicNs','totalDeadlineMonotonicNs'],'lifecycle');start,transition,total=(dec(lifecycle[k])for k in ['startedMonotonicNs','transitionDeadlineMonotonicNs','totalDeadlineMonotonicNs']);need(start<=time.monotonic_ns()0 and type(setup['productDefinitionId'])is int and setup['productDefinitionId']>0 and setup['seedDefinitionId']!=setup['productDefinitionId'],'finite-distinct-seeds') + exact(contract['inputs'],['raw','host','stock','metadata','setup','configuration'],'inputs');limits={'raw':MAX_RAW,'host':16*1024*1024,'stock':16*1024*1024,'metadata':65536,'setup':65536,'configuration':65536};bodies={k:(prefix_bytes(v,limits[k],deadline) if k in ['raw','host','stock'] else held_bytes(v,limits[k],deadline)) for k,v in contract['inputs'].items()} + from native_evidence import horizons + horizons(parse(held_bytes(contract['completeRecordEvidence'],65536,deadline)),contract,bodies,deadline) + raw=records(bodies['raw'],'raw');host=records(bodies['host'],'host');stock=records(bodies['stock'],'stock');metadata=parse(bodies['metadata']);setup_record=parse(bodies['setup']);loaded=configuration_evidence(parse(bodies['configuration']),contract,deadline) + need(metadata.get('schema')=='fsbar.barc-stock-native-smoke-metadata/v1'and metadata.get('runId')==contract['runId']and metadata.get('writer')==contract['writer']and metadata.get('actors',{}).get('factory')==contract['factory'],'actual-metadata-origin') + need(setup_record.get('schema')=='fsbar.barc-stock-native-smoke-setup/v1'and setup_record.get('runId')==contract['runId']and setup_record.get('writer')==contract['writer']and setup_record.get('existingProductionDefinitionId')==setup['seedDefinitionId']and setup_record.get('productDefinitionId')==setup['productDefinitionId'],'actual-setup-origin') + origin={k:contract[k]for k in ['runId','source','writer','acceptedGeneration']};previous_state=0;previous_frame=0 + for row in raw: + exact(row,['schema','runId','sequence','kind','writer','source','acceptedGeneration','stateSequence','nativeFrame','disposition','value'],'raw-row');need(row['schema']=='fsbar.barc-one-unit-raw-state-journal/v1'and all(row[k]==v for k,v in origin.items()),'raw-origin');seq=dec(row['stateSequence']);need(seq>=previous_state and type(row['nativeFrame'])is int and row['nativeFrame']>=previous_frame,'raw-state-frame-order');previous_state=seq;previous_frame=row['nativeFrame'];need(row['disposition']in ['materialized','invalidated','liveness-only']and row['kind']in ['snapshot','unit-created','unit-finished','command-dispatch'],'gap-or-raw-kind');need(row['kind']!='command-dispatch','prearm-dispatch') + for row in host: + exact(row,['schema','runId','sequence','kind','writer','source','value'],'host-row');need(row.get('schema')=='fsbar.barc-stock-host-journal/v1'and all(row.get(k)==contract[k]for k in ['runId','writer','source']),'host-origin');need(row.get('kind')!='result','prearm-broker-result');need(row.get('kind') not in ['gap','detach','identity-conflict'],'host-generation-refusal');need(not (row.get('kind')=='stale' and any(x in str(row['value'].get('detail','')).lower() for x in ['gap','detach','identity','incarnation'])),'host-generation-refusal') + process=metadata['basis']['processIncarnation'];channel=metadata['basis']['stateChannelIncarnation'];match=metadata['basis']['matchId'];need(process and channel and match,'metadata-incarnations') + snapshots={};metadata_rows=[] + for row in host: + if row['kind']=='live-metadata'and type(row['value'].get('tactical'))is dict: + t=row['value']['tactical'];b=basis(t['basis']);need(b['processIncarnation']==process and b['stateChannelIncarnation']==channel and b['matchIncarnation']==match,'metadata-generation-basis');need(type(t.get('actors'))is list and len(t['actors'])<=4096,'metadata-actor-bound');refs=[reference(a['actor'])for a in t['actors']if a.get('actor')];need(len({a['id']for a in refs})==len(refs),'metadata-actor-lifetime-ambiguity');metadata_rows.append(t) + def joined(row): + found=[t for t in metadata_rows if t['basis']['stateSequence']==row['stateSequence']and t['basis']['frame']==row['nativeFrame']];need(len(found)>=1,'same-basis-native-metadata-missing');need(all(t==found[0]for t in found),'contradictory-metadata');return found[0] + for row in raw: + if row['kind']=='snapshot': + need(row['disposition']=='materialized','snapshot-not-materialized');t=joined(row);units=row['value']['units'];need(type(units)is list and len(units)<=4096 and len({str(u['unitId'])for u in units})==len(units),'snapshot-unit-set');snapshots[dec(row['sequence'])]=(row,t) + seed_created=[r for r in raw if r['kind']=='unit-created'and str(r['value'].get('builderId'))==contract['factory']['id']];need(len(seed_created)==setup['seedCount'],'seed-creation-count');ids=[str(r['value']['unitId'])for r in seed_created];need(len(set(ids))==len(ids)and '0'not in ids,'seed-creation-identities') + seed_finished=[r for r in raw if r['kind']=='unit-finished'and str(r['value'].get('unitId'))in ids];need(len(seed_finished)==len(ids)and len({str(r['value']['unitId'])for r in seed_finished})==len(ids),'seed-finish-count') + completion=loaded['setupCompletion'];need(completion['completionNativeFrame']last_finish];need(len(terminals)>=1,'later-complete-replacement-missing');terminal,t=terminals[-1];need(dec(terminal['stateSequence'])>max(dec(r['stateSequence'])for r in seed_finished)and terminal['nativeFrame']>=max(r['nativeFrame']for r in seed_finished),'replacement-after-invalidation') + units=terminal['value']['units'];need(all(u.get('underConstruction')is False and u.get('buildProgress')==1 for u in units),'unfinished-terminal');need(known.issubset({str(u['unitId'])for u in units}),'setup-actor-disappeared');fresh={str(u['unitId'])for u in units}-known;need(fresh==set(ids),'unexplained-extra-products');actors={a['actor']['id']:reference(a['actor'])for a in t['actors']if a.get('actor')};products=[] + for created in seed_created: + ident=str(created['value']['unitId']);finished=next(r for r in seed_finished if str(r['value']['unitId'])==ident);need(created['disposition']=='invalidated'and finished['disposition']=='invalidated'and dec(finished['sequence'])>dec(created['sequence'])and finished['nativeFrame']>=created['nativeFrame'],'seed-lifecycle-invalidation-order') + for event in [created,finished]: + need(any(h['kind']=='stale'and str(h['value'].get('receivedSequence'))==event['stateSequence']for h in host),'actual-stale-evidence-missing') + need(any(h['kind']=='observation'and str(h['value'].get('stateSequence'))==terminal['stateSequence']for h in host),'actual-replacement-projection-missing') + u=next(u for u in units if str(u['unitId'])==ident);need(u.get('definitionId')==setup['seedDefinitionId']and ident in actors,'seed-definition-lifetime-missing');need(all(a['actor']==actors[ident]for mt in metadata_rows for a in mt['actors']if a.get('actor',{}).get('id')==ident),'seed-lifetime-changed');products.append({'reference':actors[ident],'createdSequence':created['sequence'],'finishedSequence':finished['sequence']}) + for row in raw: + if dec(row['sequence'])=dec(initial['sequence']) and row['kind']=='unit-created':need(str(row['value'].get('unitId'))in ids and str(row['value'].get('builderId'))==contract['factory']['id'],'extra-or-direct-spawn') + if dec(row['sequence'])>=first_created and row['kind']=='unit-finished':need(str(row['value'].get('unitId'))in ids,'extra-finish') + samples=[] + for r in stock: + stock_record(r);need(r.get('schema')=='highbar.barc-stock-queue-trace/v1'and r.get('runId')==contract['runId'],'stock-origin');b=basis(r['nativeBasis']);need(b['processIncarnation']==process and b['stateChannelIncarnation']==channel and b['matchIncarnation']==match,'stock-basis-incarnation');need(r['phase']=='sample'and r['dispatch']is None,'prearm-stock-dispatch');need(r['context']['actor']==contract['factory'],'stock-factory'); + if r['context']['domain']=='production'and r['reader']['status']=='complete': + fake={'stateSequence':b['stateSequence'],'nativeFrame':b['frame']};tm=joined(fake);actor=next((a for a in tm['actors']if a.get('actor')==contract['factory']),None);need(actor,'stock-host-actor');queues=[z for z in actor.get('queue',[])if z.get('domain')in ['NATIVE_QUEUE_DOMAIN_FACTORY_PRODUCTION','FactoryProduction',2]];need(len(queues)==1,'production-projection');queue=queues[0];need(all(str(z.get('definitionId'))==str(-e['id'])and z.get('nativeTag',0)==e['tag'] for z,e in zip(queue.get('entries',[]),r['queue']['entries'])),'stock-host-entry-projection');need(queue.get('complete')is True and queue.get('repeat')is False and queue.get('evidenceScheme')in [2,'NATIVE_QUEUE_EVIDENCE_SCHEME_STOCK_LUA_SUPPORTED_FIELDS_V1']and str(queue.get('revision'))==r['queue']['revision']and len(queue.get('entries',[]))==len(r['queue']['entries']),'same-basis-complete-stock-queue');samples.append(r) + nonempty=[r for r in samples if len(r['queue']['entries'])==setup['seedCount']and all(e['id']==-setup['seedDefinitionId']and e['codedOptions']==0 and e['float32params']==[] for e in r['queue']['entries'])];need(nonempty,'actual-finite-nonempty-seed-queue-missing') + empty=[r for r in samples if not r['queue']['entries']and dec(r['nativeBasis']['stateSequence'])>=dec(terminal['stateSequence'])and r['readFrame']>=terminal['nativeFrame']];need(empty,'actual-terminal-empty-queue-missing');need(empty[-1]['readFrame']>nonempty[0]['readFrame']and dec(empty[-1]['sequence'])>dec(nonempty[0]['sequence'])and dec(nonempty[0]['nativeBasis']['stateSequence'])0 and fields[4]=='0001','literal Unix stream kernel row');matches.append(fields) + need(len(matches)==1,'one exact pathname net/unix row');kernel=matches[0][6] + names=os.listdir(f"/proc/{ident['pid']}/fd");need(len(names)<=256,'engine FD bound');fds=[] + for name in names: + need(name.isdigit(),'numeric engine FD') + try:target=os.readlink(f"/proc/{ident['pid']}/fd/{name}") + except FileNotFoundError:continue + if target=='socket:['+kernel+']':fds.append(int(name)) + need(bool(fds),'actual engine held socket FD');need(m.same(ident,m.identity(ident['pid'])),'engine generation drift during socket join');self.socket_stat() + self.owner=dict(schema='bar.typed-stock-runtime-socket/v1',filesystem=row,kernelSocketInode=kernel,engine=ident,engineFDs=sorted(fds),netUnixRawSHA256=m.hashlib.sha256(raw).hexdigest(),directory=self.pin,ownership='Observed',inodeDomains='kernel socket inode and filesystem dentry inode are distinct; never equated');self.pending=None + return dict(row,ownership='Observed'if self.owner else 'Pending',firstObservedMonotonic=self.first['firstObservedMonotonic'],lastObservedMonotonic=self.last['lastObservedMonotonic']) + def inspect_for_quota(self,path): + need(pathlib.Path(path)==self.path,'only literal admitted socket path');row=self.observe(False) + if not self.owner: + try:self.observe(True) + except BaseException as e:self.pending=type(e).__name__+':'+str(e) + return dict(row,ownership='Observed'if self.owner else 'Pending',ownerJoinUnknown=self.pending) + def cleanup(self,settled): + self.directory_check() + try:self.socket_stat() + except FileNotFoundError:return dict(status='ObservedAbsent',removed=False,owner=self.owner,first=self.first,last=self.last) + need(settled and self.owner is not None,'socket cleanup requires observed owner and settled engine generations');engine=self.owner['engine'] + try:current=m.identity(engine['pid']) + except (FileNotFoundError,ProcessLookupError):current=None + need(current is None,'engine numeric PID must be absent before leftover unlink');self.socket_stat();os.unlink('highbar-0.sock',dir_fd=self.fd) + try:os.stat('highbar-0.sock',dir_fd=self.fd,follow_symlinks=False) + except FileNotFoundError:return dict(status='ObservedAbsent',removed=True,owner=self.owner,first=self.first,last=self.last) + raise m.CleanupUnknown('socket remains after exact held-FD unlink') + def close(self):os.close(self.fd) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/startup_runner.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/startup_runner.py new file mode 100644 index 0000000..83c907f --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/startup_runner.py @@ -0,0 +1,416 @@ +"""Literal host+engine seed-only startup and one sequential direct policy child. Inert without fresh exact root admission.""" +import sys +sys.dont_write_bytecode=True +import os,json,pathlib,time,datetime,argparse,subprocess,selectors,ctypes,hashlib,stat,re +HERE=pathlib.Path(__file__).resolve().parent;sys.path[:0]=[str(HERE),str(HERE/'helper')] +import mechanics as m,outer_custody as outer +from runtime_socket import RuntimeSocket +from stock_bootstrap import authenticate_ready,authenticate_metadata,authenticate_setup,persist_authenticated_snapshots +from runtime_identity import proc_bytes,start_ticks,verify_executable_map_lines,verify_host_dynamic_map_lines +ROOT='/tmp/bar-stock-seed-attribution-20261004';PYTHON='/usr/bin/python3.14' +need=m.need;clock=m.clock +RUNTIME_SOCKET=None + +def profile(): + p=m.load(HERE/'startup-profile.json');need(p['schema']=='bar.seed-attribution-profile/v2' and p['preparationReady'] is True and p['attemptRoot']==ROOT and p['roles']==['host','engine'],'closed startup profile') + need(p['source']=={'fsbarCommit':'361f5060aea0e340fb8a33da96a91fd234e7fc39','highbarCommit':'b57f11f290dc3239d894b0285ae272b6b21bc25d'} and p['engineSource']=='de69361239d8c8b1012dba3f5aa3122954ea4da3','literal source roles') + need(p['limits']==dict(wholeSeconds=180,hostReadinessSeconds=60,cleanupReserve=8,termSeconds=3,killSeconds=3,streamBytes=33554432,attemptBytes=134217728,attemptFiles=1024,mapsBytes=4194304,ownedIdentities=128),'literal diagnostic caps') + need(p['runtimeEnvironment']['DOTNET_EnableDiagnostics']=='0' and p['diagnosticConfigRootSourceDecision']=='Root approved SOURCE profile only; runtime diagnostic admission must bind this exact environment','explicit preparation-independent runtime configuration') + need(p['runtimeObjects']==dict(directory=ROOT+'/engine/run',socket=ROOT+'/engine/run/highbar-0.sock',role='engine',aiId=0,mode=448)and p['engineRuntimeEnvironment']=={'XDG_RUNTIME_DIR':ROOT+'/engine/run'},'literal reviewed runtime object/environment') + need(len(os.fsencode(p['runtimeObjects']['socket']))+1<108,'no UDS fallback') + from dynamic_mapping import PROFILE + need(p['dynamicMappingProfile']==PROFILE,'closed host-only dynamic metadata profile') + required=[p['nativeHost']]+[path for path in p['allowedExecutableMappings'] if pathlib.Path(path).name in ('libcoreclr.so','libclrjit.so')] + from mapping_profile import validate_schema + validate_schema(p) + need(p['requiredHostMappings']==required and len(required)==3,'literal pinned apphost/coreclr/clrjit') + need(p['policy']['environment']=={'PATH':'/usr/bin:/bin'} and p['policy']['maximumSimultaneousChildren']==1 and p['policy']['maximumEvaluations']==3 and p['policy']['deadlineSeconds']==5 and p['policy']['inputBytes']==16777216 and p['policy']['outputBytes']==131072,'literal closed policy caps') + need(set(p['sourceHelperPins'])=={'helper/runtime_identity.py','helper/stock_bootstrap.py','helper/dynamic_mapping.py','helper/snapshot_publication.py','helper/mapping_profile.py','helper/seed_policy.py','helper/growing_log.py','helper/loaded_custody.py','helper/settings_custody.py','qualify_prearm.py','native_evidence.py'},'closed successor source helpers') + for path,sha in p['sourceHelperPins'].items():need(m.digest(HERE/path)==sha,'exact profile helper byte pin') + return p + +def reviewed_source(): + s=m.load(HERE/'review-packet.json');need(s['schema']=='bar.seed-attribution-source-review/v2','source schema') + need(len(s['files'])==len({x['path']for x in s['files']}),'unique sealed source rows') + need(set(x['path']for x in s['files'])=={str(f.relative_to(HERE))for f in HERE.rglob('*')if f.is_file()and f.name!='review-packet.json'},'complete source leaf roster') + for x in s['files']: + need(set(x)=={'path','bytes','sha256','mode'}and not pathlib.PurePosixPath(x['path']).is_absolute()and '..'not in pathlib.PurePosixPath(x['path']).parts,'closed safe source row') + f=HERE/x['path'];need(f.is_file()and not f.is_symlink()and f.stat().st_size==x['bytes']and stat.S_IMODE(f.stat().st_mode)==x['mode']and m.digest(f)==x['sha256'],'all source leaf pins') + return m.digest(HERE/'review-packet.json') + +def admission(a,p,source,operator,now,root_absent,affinity): + need(set(a)=={'schema','operation','sourceSHA256','profileSHA256','operator','cpu','expiresUTC','root','oneUse','runtimeEnvironment','engineRuntimeEnvironment','runtimeObjects','source','ports','policy','capacity','seedInputsSHA256'},'closed seed admission') + need(a['schema']=='bar.seed-attribution-admission/v2'and a['operation']=='seed-attribution-startup-only'and a['root']==ROOT and a['oneUse']is True,'startup-only one-use operation') + need(a['sourceSHA256']==source and a['profileSHA256']==m.digest(HERE/'startup-profile.json'),'exact admitted source/profile') + need(m.same(a['operator'],operator)and a['operator']['pid']==operator['pid'],'actual admitted operator') + need(type(a['cpu'])is int and a['cpu']in affinity and root_absent,'fresh absent root and one allowed CPU') + expires=datetime.datetime.fromisoformat(a['expiresUTC']);need(expires.tzinfo is not None and 0<(expires-now).total_seconds()<=600,'fresh finite admission') + need(a['engineRuntimeEnvironment']==p['engineRuntimeEnvironment']and a['runtimeObjects']==p['runtimeObjects'],'exact UDS and engine environment admission') + need(a['runtimeEnvironment']==p['runtimeEnvironment']and a['source']==p['source']and a['ports']==p['proposedPorts'],'closed semantic role/environment/port pins') + need(a['policy']==p['policy'] and a['seedInputsSHA256']==hashlib.sha256(json.dumps(p['seed'],sort_keys=True,separators=(',',':')).encode()).hexdigest(),'admitted closed seed/policy inputs') + need(a['capacity']['maximumTaskCLR']==2 and a['capacity']['maximumInfrastructureCLR']==2 and a['capacity']['cpu']==a['cpu'] and a['capacity']['reservationSource'] and a['capacity']['currentCensusPin'],'actual capacity reservation binding required') + m.checked_pin(a['capacity']['currentCensusPin']) + return a + +def originals(p,deadline): + rows=p['originalDataPins']+p['hostPins']+p['clrPins']+p['elfPins']+p['pythonPins']+p['authorityPins'] + for x in rows:m.remaining(deadline,1);m.checked_pin(x) + from mapping_profile import revalidate + mapping=revalidate(p,lambda:m.remaining(deadline,1)) + return {'mappingIdentities':mapping,'observedPins':len(rows),'originalDataFiles':len(p['originalDataPins']),'acceptedHostMembers':len(p['hostPins'])} + +def environment(p,role): + if role=='policy':return {'PATH':'/usr/bin:/bin'} + A=pathlib.Path(ROOT);env={'PATH':'/usr/bin:/bin','HOME':str(A/role),'TMPDIR':str(A/role/'tmp'),'LANG':'C.UTF-8','LC_ALL':'C.UTF-8','PYTHONDONTWRITEBYTECODE':'1'} + if role=='host': + env.update(p['runtimeEnvironment']);env.update(BARC_ONE_UNIT_RAW_EVENTS=str(A/'host/raw.jsonl'),BARC_STOCK_SMOKE_RUN_ID=p['runId'],BARC_STOCK_SMOKE_READY=str(A/'host/ready.json'),BARC_STOCK_SMOKE_METADATA=str(A/'host/metadata.json'),BARC_STOCK_SMOKE_SETUP=str(A/'host/setup.json'),BARC_STOCK_SMOKE_HOST_TRACE=str(A/'host/host.jsonl'),BARC_STOCK_SMOKE_SOURCE_JSON=json.dumps(p['source'],sort_keys=True,separators=(',',':')),BARC_STOCK_SMOKE_RECEIVER_URL=f"http://127.0.0.1:{p['proposedPorts']['unusedReceiverOrigin']}/barc/?barc-profile=barc-live-tactical-stock-v1") + if role=='engine':env.update(p['engineRuntimeEnvironment']);env.update(HIGHBAR_COORDINATOR=f"127.0.0.1:{p['proposedPorts']['grpc']}",HIGHBAR_STOCK_QUEUE_TRACE=str(A/'engine/stock.jsonl'),HIGHBAR_STOCK_QUEUE_TRACE_RUN_ID=p['runId']) + return env + +def commands(p,invocation=None): + ports=p['proposedPorts'];A=pathlib.Path(ROOT) + result={'host':[p['nativeHost'],'--stock-tactical-live-host',f"127.0.0.1:{ports['grpc']}",f"http://127.0.0.1:{ports['gateway']}",f"http://127.0.0.1:{ports['unusedReceiverOrigin']}",str(A/'host'),p['source']['fsbarCommit']], 'engine':[p['engine'],'--isolation','--isolation-dir',p['dataRoot'],'--write-dir',str(A/'engine'),'--config',str(A/'engine/springsettings.cfg'),p['startscript']]} + if invocation is not None: + need(re.fullmatch('[0-9a-f]{64}',invocation),'exact policy invocation');result['policy']=[p['policy']['apphost'],'--closure-manifest',p['policy']['closurePath'],'--closure-sha256',p['policy']['closureSha256'],'--invocation-id',invocation] + return result + +def runtime_socket(): + global RUNTIME_SOCKET + if RUNTIME_SOCKET is None: + p=profile();pin=m.load(pathlib.Path(ROOT)/'evidence/runtime-directory.json') + def engine_identity(): + f=pathlib.Path(ROOT)/'evidence/engine.process.json' + if not f.exists():return None + row=m.load(f);need(row['argv']==commands(p)['engine']and row['environment']==environment(p,'engine'),'source-bound observed engine receipt');return row['identity'] + RUNTIME_SOCKET=RuntimeSocket(p['runtimeObjects'],pin,engine_identity) + return RUNTIME_SOCKET + +def validate_startscript(raw): + text=raw.decode('ascii');slots=re.findall(r'\[ai([0-9]+)\]\s*\{([^{}]*)\}',text,re.I);highbar=[int(i)for i,body in slots if re.search(r'\bshortname\s*=\s*highBar\s*;',body,re.I)] + need(highbar==[0]and len(slots)==2 and any(re.search(r'\bshortname\s*=\s*NullAI\s*;',body,re.I)for _,body in slots)and len({i for i,body in slots})==len(slots),'exact single HighBar AI ID0 startscript') + +def quota(owner=None): + q=m.quota(pathlib.Path(ROOT),134217728,phase='startup',known_owned=list(owner.records.values())if owner else[],live_owned=owner.observe()if owner else[],runtime_socket=runtime_socket());need(q['files']<=1024,'startup leaf bound');return q + +def ready_config(p):return {'runId':p['runId'],'source':p['source'],'bootstrap':{'receiverUrl':environment(p,'host')['BARC_STOCK_SMOKE_RECEIVER_URL']},'roots':{'attemptRoot':ROOT},'paths':{'hostTrace':ROOT+'/host/host.jsonl'}} + +def maps(role,child,p,phase,require_complete=False): + held=m.load(pathlib.Path(ROOT)/'evidence'/(role+'.process.json'))['identity'];ident=m.identity(child.pid) + need(child.poll() is None and child.pid==held['pid'] and m.same(held,ident),'held map snapshot process drift') + from dynamic_mapping import held_process + executable=p['nativeHost'] if role=='host' else p['engine'];before=held_process(held,executable) + raw=proc_bytes(child.pid,'maps',4194304);target=pathlib.Path(ROOT)/'evidence'/(role+'.'+phase+'.maps.raw') + from snapshot_publication import bytes_new + raw_ack=bytes_new(str(target),raw,4194304) + receipt={'role':role,'phase':phase,'process':ident,'rawSHA256':hashlib.sha256(raw).hexdigest(),'rawBytes':len(raw),'rawPublication':raw_ack,'completeMapsReadThroughEOF':True,'notExhaustiveManagedModuleOrIOProof':True,'status':'Unknown','dynamicMappingMetadata':'Unknown' if role=='host' else 'NotApplicable','dynamicContentSHA256':None,'dynamicContentProvenance':'Unknown','creatorAttribution':'Inference','exhaustiveMemoryOrIOProof':False} + try: + allowed={path:{'sha256':sha}for path,sha in p['allowedExecutableMappings'].items()};lines=raw.decode().splitlines() + if role=='host': + seen,dynamic=verify_host_dynamic_map_lines(lines,allowed,held,executable,p['dynamicMappingProfile']);receipt['dynamicMapping']=dynamic;receipt['dynamicMappingMetadata']=dynamic['dynamicMappingMetadata'] + else: + for line in lines: + fields=line.split(None,5);name=fields[5] if len(fields)==6 else '';need(not name.startswith('/memfd:') and not name.endswith(' (deleted)'),'engine-dynamic-map-refused') + seen=verify_executable_map_lines(lines,allowed) + required=[x['path']for x in p['requiredEngineMappings'].values()]if role=='engine'else p['requiredHostMappings'];missing=[x for x in required if x not in seen] + need(m.same(held,m.identity(child.pid)) and before==held_process(held,executable),'map snapshot process drift');receipt.update(executableFiles=sorted(seen),missingRequired=missing,fileBackedExecutableIdentity='Observed',status='Pending'if missing else 'Observed') + if require_complete:need(not missing,'actual required loaded role identities') + except BaseException as e:receipt.update(status='Unknown',error=str(e));m.write_new(target.with_suffix('.json'),receipt);raise + m.write_new(target.with_suffix('.json'),receipt);return receipt + +def content_checkpoint(path): + path=pathlib.Path(path) + if not path.is_file()or path.is_symlink():return {'status':'Unknown','reason':'no emitted engine log'} + need(path.stat().st_size<=33554432,'content log bound');raw=path.read_bytes();text=raw.decode('utf-8') + patterns={'map':r'\[PreGame::AddMapArchivesToVFS\]\[server=[^\]\r\n]+\] using map "Avalanche 3\.4" \(loaded=1 cached=[01]\)', 'game':r'\[PreGame::AddModArchivesToVFS\]\[server=[^\]\r\n]+\] using game "HighBar BARC Stock Queue Fixture 2" \(loaded=[01] cached=[01]\)'} + matches={k:re.search(v,text)for k,v in patterns.items()} + if not all(matches.values()):return {'status':'Unknown','reason':'literal selected map/game emitted joins absent','logSHA256':hashlib.sha256(raw).hexdigest()} + return {'status':'Observed','scope':'loaded-map emission plus selected-game emission only; game Lua/fixture and authenticated native readiness are separate joins','map':'Avalanche 3.4','game':'HighBar BARC Stock Queue Fixture 2','logSHA256':hashlib.sha256(raw).hexdigest(),'matchedRecords':{k:{'record':v.group(0),'byteOffset':len(text[:v.start()].encode('utf8'))}for k,v in matches.items()}} + +def checkpoint_report(observed): + order=['launch','engine-loader','content/map','game-Lua/stock-reader','fixture-ready','local-service-bind','native-channel','catalogue/basis','stock-selection','authenticated-ready'];known=[x for x in order if x in observed] + return {'order':order,'observed':known,'lastPositive':known[-1]if known else None,'firstUnmet':next((x for x in order if x not in observed),None),'silenceMeans':'Unknown, not guessed map failure','nativeUsefulPlay':'0/6','ArmAuthority':False} + +def record_tail(path,label): + path=pathlib.Path(path) + if not path.is_file()or path.is_symlink():return {'label':label,'status':'Unknown','reason':'absent'} + s=path.stat();need(stat.S_ISREG(s.st_mode)and s.st_uid==os.geteuid()and s.st_nlink==1,'failure log custody');need(s.st_size<=33554432,'individual runtime log bound') + with path.open('rb')as f:f.seek(max(0,s.st_size-65536));raw=f.read(65536) + target=pathlib.Path(ROOT)/'evidence'/(label+'.tail.raw');target.write_bytes(raw);target.chmod(0o600) + return {'label':label,'status':'Observed','sourceBytes':s.st_size,'offset':max(0,s.st_size-len(raw)),'bytes':len(raw),'sha256':hashlib.sha256(raw).hexdigest()} + +def launch(role,p,a_path,owner,deadline,registered,invocation=None,evaluation=None,cleanup_reserve=8,policy_context=None): + need(role in ['host','engine','policy'],'literal launch role') + need((role!='policy' and invocation is None and evaluation is None) or (role=='policy' and re.fullmatch('[0-9a-f]{64}',invocation or '') and evaluation in [1,2,3]),'literal invocation/evaluation') + name=role if role!='policy'else 'policy-'+str(evaluation) + end=min(deadline-cleanup_reserve,clock()+5);rd,rw=os.pipe();ar,aw=os.pipe();child=None + try: + runtime_socket().directory_check() + shim_argv=[PYTHON,'-I','-B',str(HERE/'startup_runner.py'),'--shim',role,'--admission',str(a_path),'--ready',str(rw),'--ack',str(ar)] + if role=='policy':shim_argv+=['--invocation',invocation] + child=subprocess.Popen(shim_argv,env=environment(p,role),pass_fds=(rw,ar),start_new_session=True,stdin=subprocess.PIPE if role=='policy'else None,stdout=subprocess.PIPE,stderr=subprocess.PIPE,umask=0o077) + registered[name]=child;owner.leaders.add(child.pid);os.close(rw);rw=-1;os.close(ar);ar=-1 + with selectors.DefaultSelector()as sel: + sel.register(rd,selectors.EVENT_READ) + while True: + if role=='policy': + from seed_policy import SeedPolicyContext + need(type(policy_context)is SeedPolicyContext,'concrete policy preexec supervision context');policy_context.pump(end) + if sel.select(m.remaining(end,.02 if role=='policy'else 5)):break + need(os.read(rd,64)==b'BAR-STARTUP-READY\n','actor preexec handshake') + ident=m.identity(child.pid);need(ident['pgid']==ident['session']==child.pid and ident['ppid']==os.getpid(),'actual owned actor identity');owner.acquire(ident);need(os.sched_getaffinity(child.pid)=={m.load(a_path)['cpu']},'actor serial affinity') + m.write_new(pathlib.Path(ROOT)/'evidence'/(name+'.process.json'),dict(identity=ident,argv=commands(p,invocation)[role],environment=environment(p,role)));os.write(aw,b'GO\n');return child + finally: + for fd in [rd,rw,ar,aw]: + if fd>=0: + try:os.close(fd) + except OSError:pass + +def diagnostic_checkpoints(A,observed): + reports=[] + for label,path in [('infolog',A/'engine/infolog.txt'),('stderr',A/'logs/engine.stderr.raw')]: + if not path.is_file():continue + need(path.stat().st_size<=33554432,'checkpoint log bound');raw=path.read_bytes();text=raw.decode('utf8') + patterns={'game-Lua/stock-reader':r'^.*Loaded synced gadget: BARC Stock Queue Reader .*$', 'fixture-ready':r'^.*highbar_barc_stock_fixture kind=ready frame=([0-9]+) team=0 enemy_team=1 .*$', 'local-service-fault':r'^.*(?:Failed to add port to server:.*unix:|\[hb-gateway\] fault subsystem=callback reason=rpc_internal detail="frame_tick_threw").*$'} + for key,pattern in patterns.items(): + found=re.search(pattern,text,re.MULTILINE) + if found: + record=dict(status='Observed',log=label,logSHA256=hashlib.sha256(raw).hexdigest(),byteOffset=len(text[:found.start()].encode('utf8')),matchedRecord=found.group(0),frame=(re.search(r'(?:frame[=: ]+)([0-9]+)',found.group(0),re.I).group(1)if re.search(r'(?:frame[=: ]+)([0-9]+)',found.group(0),re.I)else None)) + observed.setdefault(key,record);reports.append(record) + content=content_checkpoint(A/'engine/infolog.txt') + if content['status']=='Observed':observed.setdefault('content/map',content) + return reports + +def seed_predicates(A,ready): + from qualify_prearm import records + import native_evidence + raw_path=A/'host/raw.jsonl';stock_path=A/'engine/stock.jsonl';log_path=A/'engine/infolog.txt' + if not all(x.is_file() for x in [raw_path,stock_path,log_path]):return False + for path,cap in [(raw_path,4194304),(stock_path,16777216),(log_path,10485760)]:need(path.stat().st_size<=cap,'seed predicate input cap') + raw=records(raw_path.read_bytes(),'raw');stock=records(stock_path.read_bytes(),'stock') + completed=[r for r in raw if r['kind']=='unit-finished'];snapshots=[r for r in raw if r['kind']=='snapshot'] + if len(completed)<3 or not snapshots:return False + later=snapshots[-1] + if int(later['sequence'])<=max(int(r['sequence']) for r in completed):return False + observed=native_evidence.observations(log_path.read_bytes(),ready['runId']) + return any(r['kind']=='complete'for r in observed) and any(r.get('queue') is not None and r.get('reader',{}).get('status')=='complete' and r.get('context',{}).get('domain')=='production' and not r['queue']['entries'] and int(r['nativeBasis']['stateSequence'])>=int(later['stateSequence'])for r in stock) + +def supervise(p,a_path,deadline): + owner=m.LinuxOwner();children={};streams={};counts={};observed={};failure=None;secondary=[];result={'readyProducerAuthenticated':False,'startupAccepted':False,'seedQualificationAccepted':False,'canonicalPolicyConsumed':False};first_join=None;A=pathlib.Path(ROOT);captured=set();socket=runtime_socket();next_scan=0 + def capture_maps(role,phase,required=False): + key=(role,phase) + if key in captured:return None + captured.add(key) + try: + receipt=maps(role,children[role],p,phase,required) + if role=='engine'and receipt['status']=='Observed':observed.setdefault('engine-loader',{'basis':'actual full engine/plugin/NullAI/interface executable mappings','phase':phase,'rawSHA256':receipt['rawSHA256']}) + return receipt + except BaseException as e: + secondary.append(dict(kind='maps',role=role,phase=phase,error=str(e))) + if required:raise + return {'status':'Unknown','error':str(e)} + def drain(sel,seconds): + end=min(deadline-8,clock()+seconds) + while sel.get_map()and clock()=next_scan: + diagnostic_checkpoints(A,observed);next_scan=clock()+.2 + if 'game-Lua/stock-reader'in observed or 'fixture-ready'in observed or 'local-service-fault'in observed: + for role in ['host','engine']:capture_maps(role,'early') + if (A/'host/ready.json').is_file(): + first_join='ready-producer-authentication' + ready,ready_snapshot=authenticate_ready(A/'host/ready.json',children['host'],ready_config(p),return_snapshot=True);metadata,metadata_snapshot=authenticate_metadata(ready['metadataPath'],ready,return_snapshot=True);setup,setup_snapshot=authenticate_setup(ready['setupPath'],ready,return_snapshot=True);need(ready['grpcAddress']==f"127.0.0.1:{p['proposedPorts']['grpc']}"and ready['allowedOrigin']==f"http://127.0.0.1:{p['proposedPorts']['unusedReceiverOrigin']}",'actual ready ports') + result.update(readyProducerAuthenticated=True,readySnapshotsPublicationAcknowledged=False) + first_join='authenticated-snapshot-publication' + authenticated=persist_authenticated_snapshots(A/'evidence',{'ready':(ready,ready_snapshot),'metadata':(metadata,metadata_snapshot),'setup':(setup,setup_snapshot)});result.update(readyProducerAuthenticated=True,readySnapshotsPublicationAcknowledged=True,authenticatedSnapshots=authenticated);observed['authenticated-ready']={'basis':'authenticated producer exact retained snapshots','readySHA256':ready_snapshot['sha256']} + first_join='owned-socket' + owned_socket=socket.observe(True);need(owned_socket['ownership']=='Observed','actual owned stock UDS');m.write_new(A/'evidence/runtime-socket-owner.json',socket.owner);observed['local-service-bind']={'basis':'actual owned UDS kernel/fd receipt plus authenticated stock readiness','socket':socket.owner} + joined=[] + for role in ['engine','host']: + first_join=role+'-maps' + receipt=capture_maps(role,'terminal',True);need(receipt and receipt['status']=='Observed','complete actual loaded role joins at ready');joined.append(receipt) + for label in ['engine-loader','Lua/AI-init','native-channel','catalogue/basis','stock-selection','authenticated-ready']:observed[label]={'basis':'authenticated producer + full actual role maps + owned UDS','readySHA256':ready_snapshot['sha256']} + first_join='stock-reader-and-fixture' + diagnostic_checkpoints(A,observed);need('game-Lua/stock-reader'in observed and 'fixture-ready'in observed,'actual emitted stock reader and fixture readiness checkpoints');content=content_checkpoint(A/'engine/infolog.txt') + first_join='selected-content' + result.update(authenticatedReady=True,mapLoadCheckpoint=content,hostSourceUnchanged=p['source'],maps=joined,ownedSocket=socket.owner);need(content['status']=='Observed','authenticated ready but loaded selected map/game log join Unknown') + first_join='finite-seed-observation' + # Ready is a prerequisite, never the success predicate. Continue draining + # the actual actor selector until real retained lifecycle predicates exist. + from seed_policy import SeedGrowingLog,SeedPolicyContext + from stock_bootstrap import collect_prearm + import native_evidence,qualify_prearm + policy_context=SeedPolicyContext(p,a_path,owner,children,sel,streams,counts,deadline) + while not seed_predicates(A,ready): + m.remaining(deadline,1,8);policy_context.pump(deadline-8);time.sleep(min(.02,m.remaining(deadline,.02,8))) + first_join='canonical-infolog-and-native-seed-qualification' + policy=SeedGrowingLog.acquire(str(A/'engine/infolog.txt'),{'pid':children['engine'].pid,'startTicks':start_ticks(children['engine'].pid),'uid':os.geteuid()}).bind(policy_context) + try:seed=collect_prearm(p,ready,metadata,setup,children['host'],children['engine'],policy,deadline-8,int((deadline-180)*1e9)) + finally:policy.close() + need(seed['seedQualificationAccepted'] and seed['canonicalPolicyConsumed'] and seed['publication']['fullWriteFsyncAcknowledged'],'real seed/policy/publication joins') + result.update(seedQualificationAccepted=True,canonicalPolicyConsumed=True,qualificationPublicationAcknowledged=True,seedQualification=seed) + break + for role,child in children.items():need(child.poll()is None,role+' exited before authenticated readiness') + finally: + try:drain(sel,.1) + except BaseException as e:secondary.append(dict(kind='pipe-drain',error=str(e))) + except BaseException as e: + failure=e;result['firstFailedJoin']=str(e) if str(e).startswith('host-dynamic-') else first_join + finally: + snapshots={} + for role,child in children.items(): + try:snapshots[role]={'identity':m.identity(child.pid),'state':'Observed'} + except BaseException as e:snapshots[role]={'state':'Unknown','reason':type(e).__name__} + if role in ['host','engine']:capture_maps(role,'terminal') + try:diagnostic_checkpoints(A,observed) + except BaseException as e:secondary.append(dict(kind='checkpoint',error=str(e))) + try: + if socket.path.exists():socket.observe(True) + except BaseException as e:secondary.append(dict(kind='socket-owner',error=str(e))) + try: + tails=[record_tail(A/'engine/infolog.txt','engine-infolog')] + for key in streams:tails.append(record_tail(A/'logs'/(key+'.raw'),key)) + m.write_new(A/'evidence/precleanup-observation.json',{'processes':snapshots,'tails':tails,'checkpoint':checkpoint_report(observed),'firstFailure':str(failure)if failure else None,'secondaryFailures':secondary}) + except BaseException as e:secondary.append(dict(kind='precleanup-capture',error=str(e))) + try:result['cleanup']=m.settle(owner,deadline);need(result['cleanup']['status']=='settled','discovery/cleanup Unknown') + except BaseException as e:secondary.append(dict(kind='cleanup',error=str(e)));result.setdefault('cleanup',{'status':'Unknown','reason':str(e)}) + for role,child in children.items(): + try:child.wait(timeout=max(.01,min(1,deadline-clock()))) + except BaseException as e:secondary.append(dict(kind='actor-reap',error=str(e))) + owner.leaders.discard(child.pid) + for stream in [child.stdin,child.stdout,child.stderr]: + if stream is not None and not stream.closed:stream.close() + try:result['socketLifecycle']=socket.cleanup(result.get('cleanup',{}).get('status')=='settled') + except BaseException as e:secondary.append(dict(kind='socket-cleanup',error=str(e)));result['socketLifecycle']={'status':'Unknown','error':str(e)} + for stream in streams.values():stream.close() + result['precleanupSeedQualificationAccepted']=result.get('seedQualificationAccepted',False) + result['seedQualificationAccepted']=bool(result.get('seedQualificationAccepted') and failure is None and not secondary and result.get('cleanup',{}).get('status')=='settled') + result.update(actorEligible=False,nativeUsefulPlay='0/6',dynamicContentProvenance='Unknown',creatorAttribution='Inference',exhaustiveMemoryOrIOProof=False,seedProofScope='Bounded through last captured complete snapshot; post-snapshot disconnect coverage unavailable',failure=str(failure)if failure else None,firstObservedNativeFault=observed.get('local-service-fault'),secondaryFailures=secondary,checkpoint=checkpoint_report(observed),checkpointRecords=observed,ownedIdentities=list(owner.records.values()),browserLoaded=False,Arm=False,commandsSubmitted=0);owner.close();m.write_new(A/'evidence/diagnostic-result.json',result);socket.close() + return 3 if failure or secondary else 0 + +def source_binding(a,p): + need(a['sourceSHA256']==reviewed_source()and a['profileSHA256']==m.digest(HERE/'startup-profile.json'),'shim/worker exact source') + +def shim(args): + p=profile();a=m.load(args.admission);source_binding(a,p);need(args.shim in ['host','engine','policy'],'literal roles');need((args.shim=='policy' and re.fullmatch('[0-9a-f]{64}',args.invocation or ''))or(args.shim!='policy' and args.invocation is None),'shim literal invocation');need(os.sched_getaffinity(0)=={a['cpu']},'shim affinity') + parent=m.load(pathlib.Path(ROOT)/'evidence/worker-start.json');need(m.same(parent['worker'],m.identity(os.getppid()))and parent['admissionSHA256']==m.digest(args.admission),'owned source-bound parent') + os.write(args.ready,b'BAR-STARTUP-READY\n');os.close(args.ready);need(os.read(args.ack,16)==b'GO\n','actual preexec observer release');os.close(args.ack);runtime_socket().directory_check();os.chdir(ROOT);c=commands(p,args.invocation)[args.shim];os.execve(c[0],c,environment(p,args.shim)) + +def worker(args): + p=profile();a=m.load(args.admission);source_binding(a,p);os.sched_setaffinity(0,{a['cpu']});start=m.load(pathlib.Path(ROOT)/'evidence/outer-start.json');need(m.same(start['outer'],m.identity(os.getppid()))and start['admissionSHA256']==m.digest(args.admission),'independent actual outer') + os.write(args.ready,b'BAR-STARTUP-WORKER\n');os.close(args.ready);need(os.read(args.ack,16)==b'GO\n','independent release');os.close(args.ack);m.write_new(pathlib.Path(ROOT)/'evidence/worker-start.json',dict(worker=m.identity(os.getpid()),admissionSHA256=m.digest(args.admission))) + return supervise(p,pathlib.Path(args.admission),start['deadline']) + +def finish_outer(A,result,buffers,started,failure): + """Retain an exclusive failure receipt even if final census/quota refuses.""" + from snapshot_publication import bytes_new,json_new + def failed(check,error): + nonlocal failure + value={'check':check,'errorType':type(error).__name__,'error':str(error),'custodyObservation':getattr(error,'observation',None)} + if failure is None:failure=error;result['firstFailure']=value + else:result.setdefault('secondaryFailures',[]).append(value) + for name,raw in buffers.items(): + try:bytes_new(A/'logs'/('outer-'+name+'.raw'),bytes(raw),524288) + except BaseException as e:failed('outer-output-publication',e) + try:json_new(A/'evidence/full-output-census.json',{'files':m.census(A,runtime_socket=runtime_socket()),'scope':'stage leaves before census and outer-result; independent root final census required'}) + except BaseException as e:result['finalCensus']={'status':'Unknown','reason':str(e)};failed('final-census',e) + try:result['finalQuota']=quota() + except BaseException as e:result['finalQuota']={'status':'Unknown','reason':str(e)};failed('final-quota',e) + try:runtime_socket().close() + except BaseException as e:failed('runtime-socket-close',e) + result.update(failure=str(failure)if failure else None,elapsedSeconds=clock()-started,operationCount=1,noRetry=True,actorEligible=False,dynamicContentProvenance='Unknown',creatorAttribution='Inference',exhaustiveMemoryOrIOProof=False,seedProofScope='Bounded through last captured complete snapshot; post-snapshot disconnect coverage unavailable',rootVerdict='Pending independent root full actual receipt review',nativeUsefulPlay='0/6',noBrowserGuestArmOrCommands=True) + settled=bool(result.get('independentCustody',{}).get('leaderReaped') and not result.get('independentCustody',{}).get('remaining') and not result.get('independentCustody',{}).get('errors') and result.get('innerCleanup',{}).get('status')=='settled') + result['cleanupStatus']='settled' if settled else 'Unknown' + result['seedQualificationAccepted']=bool(failure is None and result.get('innerSeedAccepted') and settled and not result.get('secondaryFailures')) + result['startupAccepted']=bool(result['seedQualificationAccepted'] and failure is None and settled and result.get('innerJoinsAccepted') and not result.get('secondaryFailures')) + # Reserve exactly this final receipt's bytes/leaf against the unchanged quota. + encoded=(json.dumps(result,sort_keys=True,separators=(',',':'),allow_nan=False)+'\n').encode();q=result.get('finalQuota',{}) + if 'files' in q and (q['files']+1>1024 or q['bytes']+len(encoded)>134217728): + failed('outer-receipt-reserve',m.Refused('stage output census bound'));result.update(startupAccepted=False,seedQualificationAccepted=False,failure=str(failure)) + # Failure recording deliberately follows the failing checks, without replacing them. + json_new(A/'evidence/outer-result.json',result,1048576) + return failure + +def execute(a_path): + started=clock();deadline=started+180;p=profile();source=reviewed_source();a=m.load(a_path);admission(a,p,source,m.identity(os.getppid()),datetime.datetime.now(datetime.timezone.utc),not os.path.lexists(ROOT),os.sched_getaffinity(0));os.sched_setaffinity(0,{a['cpu']});pre=originals(p,deadline-8) + A=pathlib.Path(ROOT);A.mkdir(mode=0o700) + for n in ['host','engine','logs','evidence']:(A/n).mkdir(mode=0o700) + for role in ['host','engine']:(A/role/'tmp').mkdir(mode=0o700) + validate_startscript(pathlib.Path(p['startscript']).read_bytes());run=A/'engine/run';run.mkdir(mode=0o700);need(not os.path.lexists(p['runtimeObjects']['socket']),'new socket must be absent');ds=run.lstat();m.write_new(A/'evidence/runtime-directory.json',dict(path=str(run),device=ds.st_dev,inode=ds.st_ino,uid=ds.st_uid,mode=stat.S_IMODE(ds.st_mode)));runtime_socket().directory_check() + from settings_custody import prepare_engine_settings + seed_argv,settings_receipt=prepare_engine_settings(p['seed']['configuration'],A);need(seed_argv==commands(p)['engine'],'actual isolated settings argv') + m.write_new(A/'evidence/outer-start.json',dict(outer=m.identity(os.getpid()),admissionSHA256=m.digest(a_path),deadline=deadline,preOriginals=pre));need(ctypes.CDLL(None,use_errno=True).prctl(36,1,0,0,0)==0,'outer subreaper') + rd,rw=os.pipe();ar,aw=os.pipe();proc=None;owned=None;failure=None;result={'startupAccepted':False,'firstFailure':None,'secondaryFailures':[]};buffers={'stdout':bytearray(),'stderr':bytearray()} + def fail(check,error): + nonlocal failure + observation={'check':check,'errorType':type(error).__name__,'error':str(error),'custodyObservation':getattr(error,'observation',None)} + if failure is None:failure=error;result['firstFailure']=observation + else:result['secondaryFailures'].append(observation) + try: + env={'PATH':'/usr/bin:/bin','HOME':str(A/'host'),'PYTHONDONTWRITEBYTECODE':'1'} + proc=subprocess.Popen([PYTHON,'-I','-B',str(HERE/'startup_runner.py'),'--worker','--admission',str(a_path),'--ready',str(rw),'--ack',str(ar)],env=env,pass_fds=(rw,ar),start_new_session=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,umask=0o077);os.close(rw);rw=-1;os.close(ar);ar=-1;owned=outer.OwnedChild(proc,adopted=True) + with selectors.DefaultSelector()as sel:sel.register(rd,selectors.EVENT_READ);need(sel.select(m.remaining(deadline,5,8))and os.read(rd,64)==b'BAR-STARTUP-WORKER\n','independent actual worker handshake') + need(os.sched_getaffinity(proc.pid)=={a['cpu']},'independent actual affinity');m.write_new(A/'evidence/worker-independent-identity.json',dict(leader=owned.leader,cpu=a['cpu']));os.write(aw,b'GO\n');os.close(aw);aw=-1 + with selectors.DefaultSelector()as sel: + for name in buffers:sel.register(getattr(proc,name),selectors.EVENT_READ,name) + while sel.get_map()or owned.exited()is None: + m.remaining(deadline,1,8);owned.observe();quota() + for key,_ in sel.select(.02): + raw=os.read(key.fileobj.fileno(),65536) + if not raw:sel.unregister(key.fileobj);continue + buffers[key.data].extend(raw);need(len(buffers[key.data])<=524288,'outer output bound') + status=owned.exited();result['workerExitCode']=status.si_status if status.si_code==os.CLD_EXITED else-status.si_status + except BaseException as e:fail('outer-supervision',e) + finally: + for fd in [rd,rw,ar,aw]: + if fd>=0: + try:os.close(fd) + except OSError:pass + if owned: + try: + if runtime_socket().path.exists():runtime_socket().observe(True) + except BaseException as e:fail('outer-socket-owner',e) + try:result['independentCustody']=owned.settle(deadline);need(result['independentCustody']['leaderReaped']and not result['independentCustody']['remaining']and not result['independentCustody']['errors'],'independent settled cleanup') + except BaseException as e:result.setdefault('independentCustody',{'status':'Unknown','reason':str(e)});fail('independent-cleanup',e) + else:fail('independent-cleanup',m.CleanupUnknown('independent observer absent'));result['independentCustody']={'status':'Unknown','reason':'independent observer absent'} + try:result['socketLifecycle']=runtime_socket().cleanup(bool(result.get('independentCustody',{}).get('leaderReaped')and not result.get('independentCustody',{}).get('remaining')and not result.get('independentCustody',{}).get('errors'))) + except BaseException as e:result['socketLifecycle']={'status':'Unknown','reason':str(e)};fail('outer-socket-cleanup',e) + try:result['postOriginals']=originals(p,deadline) + except BaseException as e:fail('post-originals',e) + try: + inner=m.load(A/'evidence/diagnostic-result.json');result['readyProducerAuthenticated']=inner.get('readyProducerAuthenticated',False);result['firstFailedJoin']=inner.get('firstFailedJoin');result['innerCleanup']=inner.get('cleanup',{'status':'Unknown'});result['innerSecondaryFailures']=inner.get('secondaryFailures',[]) + result['innerSeedAccepted']=bool(inner.get('seedQualificationAccepted') and inner.get('canonicalPolicyConsumed') and inner.get('qualificationPublicationAcknowledged')) + result['innerJoinsAccepted']=bool(result['innerSeedAccepted'] and result.get('workerExitCode')==0 and inner.get('authenticatedReady') and inner.get('readySnapshotsPublicationAcknowledged') and inner.get('cleanup',{}).get('status')=='settled' and not inner.get('secondaryFailures')) + if inner.get('failure'):fail(inner.get('firstFailedJoin')or 'inner-operation',m.Refused(inner['failure'])) + elif result.get('workerExitCode')!=0:fail('inner-operation',m.Refused('worker failed; retained secondary failures' if inner.get('secondaryFailures') else 'worker failed')) + except BaseException as e:result.update(innerCleanup={'status':'Unknown','reason':'inner result unavailable'},innerJoinsAccepted=False,readyProducerAuthenticated=False,readyProducerAuthentication='Unknown');fail('inner-result',e) + failure=finish_outer(A,result,buffers,started,failure) + + return 0 if failure is None and result.get('workerExitCode')==0 else 3 + +def main(): + parser=argparse.ArgumentParser();parser.add_argument('--admission',required=True);parser.add_argument('--worker',action='store_true');parser.add_argument('--shim',choices=['host','engine','policy']);parser.add_argument('--ready',type=int);parser.add_argument('--ack',type=int);parser.add_argument('--invocation');a=parser.parse_args() + if a.shim:return shim(a) + if a.worker:return worker(a) + return execute(pathlib.Path(a.admission)) +if __name__=='__main__':raise SystemExit(main()) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_native_evidence.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_native_evidence.py new file mode 100644 index 0000000..8713610 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_native_evidence.py @@ -0,0 +1,28 @@ +"""Controlled bytes/process mocks only. Not native evidence.""" +import unittest,time,os,pathlib,tempfile,json +import native_evidence as n +import qualify_prearm as q +class Evidence(unittest.TestCase): + def test_observation_closed_and_unsynced_identity(self): + line=b'prefix BARC_PREARM_V1 kind=ai nonce=controlled frame=1 team=0 id=0 name=78 host=0 short=68696768426172 version=31 options=-\n' + row=n.observations(line,'controlled')[0];self.assertEqual(n.text(row['short']),'highBar');self.assertEqual(n.text(row['options']),'') + for body in [line.replace(b'nonce=controlled',b'nonce=foreign'),line.replace(b' id=0',b' id=0 id=0'),line+b'BARC_PREARM_V1 kind=refused nonce=controlled frame=1 reason=vfs\n',line.replace(b' options=-',b' options=- passed=true')]: + with self.assertRaises(q.Refused):n.observations(body,'controlled') + def test_prefix_append_is_not_completion(self): + with tempfile.TemporaryDirectory()as d: + p=pathlib.Path(d)/'live';body=b'{"sequence":"1"}\n';p.write_bytes(body);p.chmod(0o600);s=p.stat();pin={'path':str(p),'sha256':q.sha(body),'bytes':len(body),'device':s.st_dev,'inode':s.st_ino,'uid':s.st_uid,'mode':0o600,'nlink':1} + with p.open('ab')as f:f.write(b'{"sequence":"2"}\n') + self.assertEqual(q.prefix_bytes(pin,1024,time.monotonic()+1),body) + with self.assertRaises(q.Refused):q.held_bytes(pin,1024,time.monotonic()+1) + p.write_bytes(body[:-1]); + with self.assertRaises(q.Refused):q.prefix_bytes(pin,1024,time.monotonic()+1) + p.write_bytes(body.replace(b'1',b'3')) + with self.assertRaises(q.Refused):q.prefix_bytes(pin,1024,time.monotonic()+1) + p.unlink();p.write_bytes(body) + with self.assertRaises(q.Refused):q.prefix_bytes(pin,1024,time.monotonic()+1) + def test_policy_is_distinct_and_mandatory(self): + with self.assertRaises(q.Refused):n.infolog({'canonicalReceipt':'fabricated'}, {},time.monotonic()+1) + with self.assertRaises(q.Refused):n.horizons({'schema':'bar.root-accepted-complete-record-horizons/v1'}, {},{},time.monotonic()+1) + def test_schema_does_not_assert_open_lua_files(self): + with self.assertRaises(q.Refused):n.configuration({'schema':'bar.root-accepted-loaded-configuration-custody/v1'}, {},time.monotonic()+1) +if __name__=='__main__':unittest.main() diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_qualify_prearm.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_qualify_prearm.py new file mode 100644 index 0000000..fd885c7 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_qualify_prearm.py @@ -0,0 +1,96 @@ +"""Controlled source fixtures only; never actual/native qualification evidence.""" +import unittest,tempfile,pathlib,json,os,time,copy +from unittest.mock import patch +import qualify_prearm as q +import prepare_policy_closure as policy +class Controlled(unittest.TestCase): + def setUp(self):self.tmp=tempfile.TemporaryDirectory();self.root=pathlib.Path(self.tmp.name);self.count=0 + def tearDown(self):self.tmp.cleanup() + def pin(self,v,raw=False): + self.count+=1;p=self.root/str(self.count);b=v if raw else json.dumps(v,separators=(',',':')).encode();p.write_bytes(b);p.chmod(0o600);s=p.stat();return {'path':str(p),'sha256':q.sha(b),'bytes':len(b),'device':s.st_dev,'inode':s.st_ino,'uid':s.st_uid,'mode':0o600,'nlink':1} + def framed(self,magic,rows): + text='\n'.join([magic,'length=00000000',*rows,'end'])+'\n';return text.replace('length=00000000','length='+str(len(text)).zfill(8)) + def fixture(self): + source={'fsbarCommit':'a'*40,'highbarCommit':'b'*40};writer={'pid':99,'startTicks':'123','uid':os.getuid()};factory={'id':'10','lifetime':'5'};products=[{'id':'11','lifetime':'6'}];base={'token':'eA==','matchIncarnation':'AAAAAAAAAAAAAAAAAAAAAA==','processIncarnation':'P','stateChannelIncarnation':'C','snapshotSendMonotonicNs':'1','effectiveCadenceFrames':1} + def basis(n):return {**base,'stateSequence':str(n*2),'frame':n*10} + context={'profile':'barc-live-tactical-stock-v1','tacticalRevision':2,'queueEvidenceScheme':2,'actor':factory,'catalogueId':base['matchIncarnation'],'catalogueRevision':'1','engineVersion':'2026.07.04','gameName':'controlled','gameVersion':'1','contentSha256':'c'*64,'domain':'production'} + raw=[];host=[];stock=[] + def hr(kind,value):host.append({'schema':'fsbar.barc-stock-host-journal/v1','runId':'controlled','source':source,'writer':writer,'sequence':str(len(host)+1),'kind':kind,'value':value}) + for n,kind,value in [(1,'snapshot',{'units':[{'unitId':10,'definitionId':1,'underConstruction':False,'buildProgress':1}]}),(2,'unit-created',{'unitId':11,'builderId':10}),(3,'snapshot',{'units':[{'unitId':10,'definitionId':1,'underConstruction':False,'buildProgress':1},{'unitId':11,'definitionId':2,'underConstruction':True,'buildProgress':0.5}]}),(4,'unit-finished',{'unitId':11}),(5,'snapshot',{'units':[{'unitId':10,'definitionId':1,'underConstruction':False,'buildProgress':1},{'unitId':11,'definitionId':2,'underConstruction':False,'buildProgress':1}]})]: + entries=[{'id':-2,'codedOptions':0,'tag':8,'float32params':[]}]if n==3 else [] + revision=q.stock_revision(context,[{'domain':'production',**x}for x in entries]);actors=[{'actor':factory,'queue':[{'domain':2,'revision':revision,'entries':[{'definitionId':2,'nativeTag':8}]if entries else [],'repeat':False,'complete':True,'evidenceScheme':2}]}]+([{'actor':products[0],'queue':[]}]if n in [3,5] else []) + if n in [1,3,5]:hr('live-metadata',{'tactical':{'basis':basis(n),'actors':actors}}) + raw.append({'schema':'fsbar.barc-one-unit-raw-state-journal/v1','runId':'controlled','source':source,'writer':writer,'acceptedGeneration':'G','sequence':str(n),'kind':kind,'stateSequence':str(n*2),'nativeFrame':n*10,'disposition':'materialized'if kind=='snapshot'else 'invalidated','value':value}) + if n in [2,4]:hr('stale',{'receivedSequence':str(n*2)}) + if n==5:hr('observation',{'stateSequence':'10'}) + if n in [1,3,5]: + req=self.framed('BARC_QUEUE_REQUEST/1',['bridge=barc-stock-queue-reader-v1','domain=production','unit=10']);response=self.framed('BARC_QUEUE_RESPONSE/1',['bridge=barc-stock-queue-reader-v1','request-sha256='+q.sha(req.encode()),'status=ok','domain=production','unit=10','count='+str(len(entries)),*['row='+q.row_text({'domain':'production',**x})for x in entries]]) + stock.append({'schema':'highbar.barc-stock-queue-trace/v1','runId':'controlled','sequence':str(len(stock)+1),'phase':'sample','readFrame':n*10,'perspectiveTeamId':0,'nativeBasis':basis(n),'context':context,'reader':{'kind':'CallRules','status':'complete','request':req,'response':response},'queue':{'revision':revision,'entries':entries},'dispatch':None}) + inputs={k:self.pin(b''.join(json.dumps(r,separators=(',',':')).encode()+b'\n'for r in rows),True)for k,rows in [('raw',raw),('host',host),('stock',stock)]} + inputs['metadata']=self.pin({'schema':'fsbar.barc-stock-native-smoke-metadata/v1','runId':'controlled','writer':writer,'actors':{'factory':factory},'basis':{'processIncarnation':'P','stateChannelIncarnation':'C','matchId':base['matchIncarnation']}});inputs['setup']=self.pin({'schema':'fsbar.barc-stock-native-smoke-setup/v1','runId':'controlled','writer':writer,'existingProductionDefinitionId':2,'productDefinitionId':3}) + inputs['configuration']=self.pin({'CONTROLLED':'native adapter tested separately'}) + record=self.pin({'CONTROLLED':'prefix custody tested separately'});now=time.monotonic_ns() + return {'schema':'bar.prearm-sealed-input-contract/v1','runId':'controlled','source':source,'writer':writer,'acceptedGeneration':'G','factory':factory,'setup':{'seedCount':1,'seedDefinitionId':2,'productDefinitionId':3},'inputs':inputs,'completeRecordEvidence':record,'lifecycle':{'startedMonotonicNs':str(now),'transitionDeadlineMonotonicNs':str(now+60_000_000_000),'totalDeadlineMonotonicNs':str(now+120_000_000_000)},'outputPath':str(self.root/'qualification')} + def derive(self,c): + with patch.object(q,'configuration_evidence',return_value={'processes':[{**c['writer'],'role':'host'}],'setupCompletion':{'completionNativeFrame':15}}),patch('native_evidence.horizons'):return q.derive(c,time.monotonic()+10) + def mutate_raw(self,c,change): + rows=[json.loads(x)for x in pathlib.Path(c['inputs']['raw']['path']).read_bytes().splitlines()];change(rows);pin=self.pin(b''.join(json.dumps(x,separators=(',',':')).encode()+b'\n'for x in rows),True);c['inputs']['raw']=pin + def test_controlled_positive_nonconsecutive_native_sequences(self): + c=self.fixture();r=self.derive(c);self.assertEqual(r['seedEvidence']['seedCount'],1);q.publish(r,c['outputPath']);self.assertEqual(pathlib.Path(c['outputPath']).stat().st_mode&0o777,0o600) + with self.assertRaises(FileExistsError):q.publish(r,c['outputPath']) + def test_lifecycle_mutants_refuse(self): + changes=[lambda r:r[1]['value'].update(builderId=0),lambda r:r[3].update(kind='unit-created'),lambda r:r[3].update(disposition='materialized'),lambda r:r[2].update(acceptedGeneration='foreign'),lambda r:r[2].update(kind='command-dispatch'),lambda r:r[4]['value']['units'][1].update(underConstruction=True),lambda r:r[4]['value']['units'].append({'unitId':12,'definitionId':2,'underConstruction':False,'buildProgress':1}),lambda r:r[1].update(sequence='3'),lambda r:r[1].update(disposition='gap'),lambda r:r[3].update(stateSequence='6')] + for change in changes: + with self.subTest(change=change):c=self.fixture();self.mutate_raw(c,change) + with self.assertRaises((q.Refused,KeyError,StopIteration)):self.derive(c) + def test_json_and_record_bounds(self): + for body in [b'{"a":1,"a":2}',b'{"a":NaN}',b'{"a":1}\0']: + with self.assertRaises(q.Refused):q.parse(body) + for body in [b'{"sequence":"1"}',b'{"sequence":"2"}\n',b'{"sequence":"1","x":"'+b'x'*32768+b'"}\n']: + with self.assertRaises(q.Refused):q.records(body,'controlled') + def test_physical_mutants(self): + p=self.pin(b'controlled',True);path=pathlib.Path(p['path']);path.write_bytes(b'tampering') + with self.assertRaises(q.Refused):q.held_bytes(p,100,time.monotonic()+1) + p=self.pin(b'controlled',True);os.link(p['path'],self.root/'link') + with self.assertRaises(q.Refused):q.held_bytes(p,100,time.monotonic()+1) + p=self.pin(b'controlled',True);path=pathlib.Path(p['path']);path.unlink();path.symlink_to(self.root/'link') + with self.assertRaises(OSError):q.held_bytes(p,100,time.monotonic()+1) + def test_expiry_and_missing_config(self): + c=self.fixture();c['lifecycle']['transitionDeadlineMonotonicNs']='1' + with self.assertRaises(q.Refused):self.derive(c) + c=self.fixture();del c['inputs']['configuration'] + with self.assertRaises(q.Refused):self.derive(c) + def test_stock_response_revision_not_boolean(self): + c=self.fixture();rows=[json.loads(x)for x in pathlib.Path(c['inputs']['stock']['path']).read_bytes().splitlines()];rows[1]['queue']['revision']='1' + with self.assertRaises(q.Refused):q.stock_record(rows[1]) + def test_actual_pid_generation_inspector(self): + text=pathlib.Path('/proc/self/stat').read_text();ticks=text[text.rfind(')')+2:].split()[19];q.live_process({'pid':os.getpid(),'startTicks':ticks,'uid':os.getuid()}) + with self.assertRaises(q.Refused):q.live_process({'pid':os.getpid(),'startTicks':str(int(ticks)+1),'uid':os.getuid()}) + def test_exact_record_limits_and_partial_tail(self): + base=b'{"sequence":"1","padding":""}';padding=q.MAX_ROW-len(base)-1;exact=base.replace(b'"padding":""',b'"padding":"'+b'x'*padding+b'"')+b'\n';self.assertEqual(len(exact),q.MAX_ROW);q.records(exact,'raw') + with self.assertRaises(q.Refused):q.records(exact[:-2]+b'x"\n','raw') + with self.assertRaises(q.Refused):q.records(exact[:-1],'raw') + body=b''.join(json.dumps({'sequence':str(i+1)}).encode()+b'\n'for i in range(q.MAX_ROWS));q.records(body,'raw') + with self.assertRaises(q.Refused):q.records(body+b'{"sequence":"8193"}\n','raw') + def test_snapshot_join_and_generation_refusal(self): + c=self.fixture();self.mutate_raw(c,lambda rows:rows[0].update(nativeFrame=11)) + with self.assertRaises(q.Refused):self.derive(c) + c=self.fixture();self.mutate_raw(c,lambda rows:rows[4].update(stateSequence='8')) + with self.assertRaises(q.Refused):self.derive(c) + def test_reported_host_gap_and_lifetime_refusals(self): + for mutant in ['gap','detach','lifetime']: + c=self.fixture();rows=[json.loads(x)for x in pathlib.Path(c['inputs']['host']['path']).read_bytes().splitlines()] + if mutant in ['gap','detach']:next(x for x in rows if x['kind']=='stale')['value']['detail']='state sequence gap'if mutant=='gap'else 'coordinator detached' + else: + tactical=[x['value']['tactical']for x in rows if x['kind']=='live-metadata'];tactical[1]['actors'][1]['actor']['lifetime']='99' + c['inputs']['host']=self.pin(b''.join(json.dumps(x,separators=(',',':')).encode()+b'\n'for x in rows),True) + with self.assertRaises(q.Refused):self.derive(c) + def test_no_fabricated_boolean_contract(self): + with self.assertRaises(q.Refused):q.derive({'actualPassed':True,'noAutonomousOrders':True},time.monotonic()+1) + c=self.fixture();pin=c['inputs']['raw'];body=pathlib.Path(pin['path']).read_bytes();pathlib.Path(pin['path']).write_bytes(body+b'{') + self.derive(c) # Later append is legal; the captured complete extent stays unchanged. + def test_policy_preparation_is_obstructed(self): + roles=lambda names:[{'role':x,'path':'/controlled/'+x,'sha256':'a'*64}for x in names] + original={'schema':'fsbar.barc-runtime-evidence-policy-closure/v3','custodyRoot':'/controlled','managedRoot':'/controlled/m','provenanceRoot':'/controlled/p','runtimeRoots':['/controlled/r'],'searchLayout':[],'managed':roles(policy.MANAGED),'provenance':roles(policy.PROVENANCE),'runtime':[{'path':'/controlled/lib'}],'runtimeRoles':{x:'/controlled/'+x for x in policy.RUNTIME},'source':{'productCommit':'a'*40,'productTree':'b'*40},'custody':{}} + result=policy.prepare(self.pin(original),{'productCommit':'c'*40,'productTree':'d'*40,'highbarCommit':'e'*40},str(self.root/'fresh'),time.monotonic()+5);self.assertFalse(result['ready']);self.assertIn('productCommit-mismatch',result['obstructions']);self.assertFalse((self.root/'fresh').exists()) +if __name__=='__main__':unittest.main() From 37e66d74daf8d38c703b22b21086aac65b17523a Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 22:34:11 +0200 Subject: [PATCH 06/10] Make seed helper controls portable and wire native source gates --- .github/workflows/complete-record-source.yml | 4 + .github/workflows/prearm-source-controls.yml | 7 + .../barc_stock_queue_reader.baseline.lua | 310 ++++++++++++++++++ .../fixture/tests/test_prearm_observation.py | 2 +- .../prepare_policy_closure.py | 26 ++ .../run-source-controls.py | 24 ++ 6 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/fixtures/barc_stock_queue_reader.baseline.lua create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/prepare_policy_closure.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py diff --git a/.github/workflows/complete-record-source.yml b/.github/workflows/complete-record-source.yml index db3b291..b1d6b98 100644 --- a/.github/workflows/complete-record-source.yml +++ b/.github/workflows/complete-record-source.yml @@ -36,6 +36,10 @@ jobs: test "$(git rev-parse HEAD)" = "$EXPECTED_SHA" test -z "$(git status --porcelain --untracked-files=all)" + - name: Run the portable seed helper source controls + shell: bash + run: python3 -I -B tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py + - name: Set up the exact .NET SDK uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6 with: diff --git a/.github/workflows/prearm-source-controls.yml b/.github/workflows/prearm-source-controls.yml index ec15285..176892c 100644 --- a/.github/workflows/prearm-source-controls.yml +++ b/.github/workflows/prearm-source-controls.yml @@ -17,6 +17,9 @@ on: - tests/Broker.Browser.Live.Tests/** - tests/Broker.NativeProof/tactical-native-journey.spec.js - tests/Broker.NativeProof/tactical-native-readiness.test.mjs + - tests/Broker.NativeProof/one-unit-prearm.test.mjs + - tests/Broker.NativeProof/Policy/build-stock-selection.mjs + - tests/Broker.NativeProof/Policy/build-stock-selection.test.mjs workflow_dispatch: permissions: @@ -64,6 +67,10 @@ jobs: shell: bash run: node --test tests/Broker.NativeProof/tactical-native-readiness.test.mjs + - name: Run the one-unit and compiler-capture source controls + shell: bash + run: node --test tests/Broker.NativeProof/one-unit-prearm.test.mjs tests/Broker.NativeProof/Policy/build-stock-selection.test.mjs + - name: Restore and build the live boundary source shell: bash env: diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/fixtures/barc_stock_queue_reader.baseline.lua b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/fixtures/barc_stock_queue_reader.baseline.lua new file mode 100644 index 0000000..3c86241 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/fixtures/barc_stock_queue_reader.baseline.lua @@ -0,0 +1,310 @@ +function gadget:GetInfo() + return { + name = "BARC Stock Queue Reader", + desc = "Bounded team-scoped stock production and rally observation", + author = "FS-GG", + version = "1", + date = "2026-10-01", + license = "MIT", + layer = 0, + enabled = true, + } +end + +-- Stock AICallback::CallRules selects LuaRules' synced handle. The generic +-- RecvSkirmishAIMessage comment does not change that explicit dispatch route. +if not gadgetHandler:IsSyncedCode() then + return +end + +local REQUEST_ROUTE = "BARC_QUEUE_REQUEST/" +local REQUEST_MAGIC = "BARC_QUEUE_REQUEST/1" +local RESPONSE_MAGIC = "BARC_QUEUE_RESPONSE/1" +local BRIDGE = "barc-stock-queue-reader-v1" +local MAX_BYTES = 8192 +local MAX_LINE_BYTES = 512 +local MAX_ENTRIES = 64 +local PROBE_ENTRIES = 65 +local MAX_PARAMS = 16 +local MAX_TOTAL_PARAMS = 256 +local HEX = "0123456789abcdef" + +local function byteBand(a, b) + local result, place = 0, 1 + for _ = 1, 8 do + if a % 2 == 1 and b % 2 == 1 then result = result + place end + a, b, place = math.floor(a / 2), math.floor(b / 2), place * 2 + end + return result +end + +local function byteBxor(a, b) + local result, place = 0, 1 + for _ = 1, 8 do + if a % 2 ~= b % 2 then result = result + place end + a, b, place = math.floor(a / 2), math.floor(b / 2), place * 2 + end + return result +end + +local function wordFromHex(value) + local word = {} + for index = 1, 8, 2 do word[#word + 1] = assert(tonumber(value:sub(index, index + 1), 16)) end + return word +end + +local function wordXor(a, b, c) + local result = {} + for index = 1, 4 do + local value = byteBxor(a[index], b[index]) + result[index] = c and byteBxor(value, c[index]) or value + end + return result +end + +local function wordAnd(a, b) + return {byteBand(a[1], b[1]), byteBand(a[2], b[2]), byteBand(a[3], b[3]), byteBand(a[4], b[4])} +end + +local function wordNot(a) + return {255 - a[1], 255 - a[2], 255 - a[3], 255 - a[4]} +end + +local function wordShiftRight(a, count) + local result, whole, bits = {}, math.floor(count / 8), count % 8 + for index = 1, 4 do + local source = index - whole + local value = source >= 1 and math.floor(a[source] / (2 ^ bits)) or 0 + if bits > 0 and source > 1 then value = value + (a[source - 1] % (2 ^ bits)) * (2 ^ (8 - bits)) end + result[index] = value + end + return result +end + +local function wordShiftLeft(a, count) + local result, whole, bits = {}, math.floor(count / 8), count % 8 + for index = 1, 4 do + local source = index + whole + local value = source <= 4 and (a[source] * (2 ^ bits)) % 256 or 0 + if bits > 0 and source < 4 then value = value + math.floor(a[source + 1] / (2 ^ (8 - bits))) end + result[index] = value + end + return result +end + +local function wordRotateRight(a, count) + return wordXor(wordShiftRight(a, count), wordShiftLeft(a, 32 - count)) +end + +local function wordAdd(...) + local values, result, carry = {...}, {}, 0 + for index = 4, 1, -1 do + local total = carry + for valueIndex = 1, #values do total = total + values[valueIndex][index] end + result[index], carry = total % 256, math.floor(total / 256) + end + return result +end + +local function wordHex(value) + local chars = {} + for index = 1, 4 do + local byte = value[index] + chars[#chars + 1] = HEX:sub(math.floor(byte / 16) + 1, math.floor(byte / 16) + 1) + chars[#chars + 1] = HEX:sub((byte % 16) + 1, (byte % 16) + 1) + end + return table.concat(chars) +end + +local SHA_K_HEX = { + "428a2f98","71374491","b5c0fbcf","e9b5dba5","3956c25b","59f111f1","923f82a4","ab1c5ed5", + "d807aa98","12835b01","243185be","550c7dc3","72be5d74","80deb1fe","9bdc06a7","c19bf174", + "e49b69c1","efbe4786","0fc19dc6","240ca1cc","2de92c6f","4a7484aa","5cb0a9dc","76f988da", + "983e5152","a831c66d","b00327c8","bf597fc7","c6e00bf3","d5a79147","06ca6351","14292967", + "27b70a85","2e1b2138","4d2c6dfc","53380d13","650a7354","766a0abb","81c2c92e","92722c85", + "a2bfe8a1","a81a664b","c24b8b70","c76c51a3","d192e819","d6990624","f40e3585","106aa070", + "19a4c116","1e376c08","2748774c","34b0bcb5","391c0cb3","4ed8aa4a","5b9cca4f","682e6ff3", + "748f82ee","78a5636f","84c87814","8cc70208","90befffa","a4506ceb","bef9a3f7","c67178f2", +} +local SHA_K = {} +for index = 1, #SHA_K_HEX do SHA_K[index] = wordFromHex(SHA_K_HEX[index]) end + +local function sha256(message) + local bytes = {} + for index = 1, #message do bytes[index] = message:byte(index) end + local bitLength = #bytes * 8 + bytes[#bytes + 1] = 0x80 + while (#bytes % 64) ~= 56 do bytes[#bytes + 1] = 0 end + local lengthBytes = {} + for index = 8, 1, -1 do + lengthBytes[index] = bitLength % 256 + bitLength = math.floor(bitLength / 256) + end + for index = 1, 8 do bytes[#bytes + 1] = lengthBytes[index] end + + local h = {} + for index, value in ipairs({"6a09e667","bb67ae85","3c6ef372","a54ff53a","510e527f","9b05688c","1f83d9ab","5be0cd19"}) do + h[index] = wordFromHex(value) + end + for offset = 1, #bytes, 64 do + local w = {} + for index = 0, 15 do + local base = offset + index * 4 + w[index] = {bytes[base], bytes[base + 1], bytes[base + 2], bytes[base + 3]} + end + for index = 16, 63 do + local x, y = w[index - 15], w[index - 2] + local s0 = wordXor(wordRotateRight(x, 7), wordRotateRight(x, 18), wordShiftRight(x, 3)) + local s1 = wordXor(wordRotateRight(y, 17), wordRotateRight(y, 19), wordShiftRight(y, 10)) + w[index] = wordAdd(w[index - 16], s0, w[index - 7], s1) + end + local a,b,c,d,e,f,g,hh = h[1],h[2],h[3],h[4],h[5],h[6],h[7],h[8] + for index = 0, 63 do + local s1 = wordXor(wordRotateRight(e, 6), wordRotateRight(e, 11), wordRotateRight(e, 25)) + local choice = wordXor(wordAnd(e, f), wordAnd(wordNot(e), g)) + local t1 = wordAdd(hh, s1, choice, SHA_K[index + 1], w[index]) + local s0 = wordXor(wordRotateRight(a, 2), wordRotateRight(a, 13), wordRotateRight(a, 22)) + local majority = wordXor(wordAnd(a, b), wordAnd(a, c), wordAnd(b, c)) + local t2 = wordAdd(s0, majority) + hh,g,f,e,d,c,b,a = g,f,e,wordAdd(d, t1),c,b,a,wordAdd(t1, t2) + end + h[1],h[2],h[3],h[4] = wordAdd(h[1],a),wordAdd(h[2],b),wordAdd(h[3],c),wordAdd(h[4],d) + h[5],h[6],h[7],h[8] = wordAdd(h[5],e),wordAdd(h[6],f),wordAdd(h[7],g),wordAdd(h[8],hh) + end + local parts = {} + for index = 1, 8 do parts[index] = wordHex(h[index]) end + return table.concat(parts) +end + +local function packedHex(bytes, littleEndian) + if type(bytes) ~= "string" or #bytes ~= 4 then return nil end + local chars = {} + if littleEndian then + for index = 4, 1, -1 do chars[#chars + 1] = wordHex({0, 0, 0, bytes:byte(index)}):sub(7, 8) end + else + for index = 1, 4 do chars[#chars + 1] = wordHex({0, 0, 0, bytes:byte(index)}):sub(7, 8) end + end + return table.concat(chars) +end + +local onePacked = VFS.PackF32(1.0) +local oneNativeHex = packedHex(onePacked, false) +local PACK_LITTLE_ENDIAN = oneNativeHex == "0000803f" +local PACK_ENDIAN_VALID = PACK_LITTLE_ENDIAN or oneNativeHex == "3f800000" +local PACK_ZERO_VALID = PACK_ENDIAN_VALID + and packedHex(VFS.PackF32(0.0), PACK_LITTLE_ENDIAN) == "00000000" + and packedHex(VFS.PackF32(-1 / math.huge), PACK_LITTLE_ENDIAN) == "80000000" + +local function float32Hex(value) + if type(value) ~= "number" or value ~= value or value == math.huge or value == -math.huge then + return nil, "nonfinite-float" + end + if not PACK_ENDIAN_VALID then return nil, "float-pack-unsupported" end + local encoded = packedHex(VFS.PackF32(value), PACK_LITTLE_ENDIAN) + if not encoded then return nil, "float-pack-unsupported" end + if value == 0 and not PACK_ZERO_VALID then return nil, "negative-zero-unsupported" end + return encoded +end + +local function integerInRange(value, minimum, maximum) + return type(value) == "number" and value == math.floor(value) and value >= minimum and value <= maximum +end + +local function splitLines(message) + if message:sub(-1) ~= "\n" then return nil end + local lines, position = {}, 1 + while position <= #message do + local newline = message:find("\n", position, true) + if not newline then return nil end + if newline - position + 1 > MAX_LINE_BYTES then return nil end + lines[#lines + 1] = message:sub(position, newline - 1) + position = newline + 1 + end + return lines +end + +local function parseRequest(message) + if type(message) ~= "string" or #message > MAX_BYTES or message:find("\0", 1, true) then return nil end + for index = 1, #message do if message:byte(index) > 127 then return nil end end + local lines = splitLines(message) + if not lines or #lines ~= 6 or lines[6] ~= "end" then return nil end + local length = lines[2]:match("^length=(%d%d%d%d%d%d%d%d)$") + local bridge = lines[3]:match("^bridge=([%w%-]+)$") + local domain = lines[4]:match("^domain=([%l]+)$") + local unitText = lines[5]:match("^unit=(%-?%d+)$") + local unit = unitText and tonumber(unitText) or nil + if not length or tonumber(length) ~= #message or not bridge or not domain or not integerInRange(unit, 0, 2147483647) then return nil end + if domain ~= "production" and domain ~= "rally" then return nil end + local reason + if lines[1] ~= REQUEST_MAGIC then reason = lines[1]:sub(1, #REQUEST_ROUTE) == REQUEST_ROUTE and "wrong-version" or "malformed" + elseif bridge ~= BRIDGE then reason = "missing-bridge" end + return { bridge=bridge, domain=domain, unit=unit, reason=reason } +end + +local function response(requestHash, domain, unit, status, reason, rows) + local lines = { RESPONSE_MAGIC, "length=00000000", "bridge=" .. BRIDGE, "request-sha256=" .. requestHash, + "status=" .. status, "domain=" .. domain, "unit=" .. tostring(unit) } + if reason then lines[#lines + 1] = "reason=" .. reason end + lines[#lines + 1] = "count=" .. tostring(#rows) + for _, row in ipairs(rows) do lines[#lines + 1] = "row=" .. row end + lines[#lines + 1] = "end" + local document = table.concat(lines, "\n") .. "\n" + document = document:gsub("length=00000000", string.format("length=%08d", #document), 1) + if #document > MAX_BYTES then return nil end + for _, line in ipairs(lines) do if #line + 1 > MAX_LINE_BYTES then return nil end end + return document +end + +local function unavailable(message, request, reason) + return response(sha256(message), request.domain, request.unit, "unavailable", reason, {}) +end + +local function encodeRows(domain, commands) + if type(commands) ~= "table" then return nil, "malformed" end + if #commands > MAX_ENTRIES then return nil, "overflow" end + local rows, totalParams = {}, 0 + for index = 1, #commands do + local command = commands[index] + local options = type(command) == "table" and command.options or nil + local params = type(command) == "table" and command.params or nil + if type(options) ~= "table" or type(params) ~= "table" + or not integerInRange(command.id, -2147483648, 2147483647) + or not integerInRange(options.coded, 0, 65535) + or not integerInRange(command.tag, -2147483648, 2147483647) then return nil, "malformed" end + if #params > MAX_PARAMS then return nil, "overflow" end + totalParams = totalParams + #params + if totalParams > MAX_TOTAL_PARAMS then return nil, "overflow" end + local encoded = {} + for paramIndex = 1, #params do + local bits, reason = float32Hex(params[paramIndex]) + if not bits then return nil, reason end + encoded[paramIndex] = bits + end + local row = table.concat({domain, tostring(command.id), tostring(options.coded), tostring(command.tag), table.concat(encoded, ",")}, "|") + if #row + 5 > MAX_LINE_BYTES then return nil, "oversize" end + rows[index] = row + end + return rows +end + +function gadget:RecvSkirmishAIMessage(aiTeam, message) + if type(message) ~= "string" or message:sub(1, #REQUEST_ROUTE) ~= REQUEST_ROUTE then return nil end + local request = parseRequest(message) + if not request then return nil end + if request.reason then return unavailable(message, request, request.reason) end + if not Spring.ValidUnitID(request.unit) or Spring.GetUnitTeam(request.unit) ~= aiTeam then + return unavailable(message, request, "wrong-team") + end + local unitDefID = Spring.GetUnitDefID(request.unit) + if not unitDefID or not UnitDefs[unitDefID] or UnitDefs[unitDefID].isFactory ~= true then + return unavailable(message, request, "not-factory") + end + local commands + if request.domain == "production" then commands = Spring.GetFactoryCommands(request.unit, PROBE_ENTRIES) + else commands = Spring.GetUnitCommands(request.unit, PROBE_ENTRIES) end + local rows, reason = encodeRows(request.domain, commands) + if not rows then return unavailable(message, request, reason) end + local result = response(sha256(message), request.domain, request.unit, "ok", nil, rows) + if result then return result end + return unavailable(message, request, "oversize") +end diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/test_prearm_observation.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/test_prearm_observation.py index 572ac0f..d8c58a7 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/test_prearm_observation.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/fixture/tests/test_prearm_observation.py @@ -15,7 +15,7 @@ def test_empty_before_one_shot_plain_seed(self): def test_actual_source_gate_and_passive_transition(self): text=(ROOT/'barc_stock_queue_fixture.lua').read_text();self.assertIn('frame > observed.observedFrame',text);self.assertLess(text.index('phase = "passive"'),text.index('Spring.GiveOrderToUnit'));self.assertEqual(text.count('Spring.GiveOrderToUnit('),1);self.assertIn('VFS.CalculateHash(bytes, 1)',text) def test_observer_hook_preserves_wire_source(self): - old=pathlib.Path('/home/developer/.local/share/fs-gg-private/bar-stock-policy968-plain-fixture-successor-20261003/packet/runtime-data/AI/Skirmish/highBar/stable/barc-stock-observer/LuaRules/Gadgets/barc_stock_queue_reader.lua').read_text();new=(ROOT/'barc_stock_queue_reader.lua').read_text();a=new.index(' if result then\n');z=new.index(' return unavailable(message, request, "oversize")',a);self.assertEqual(new[:a]+' if result then return result end\n'+new[z:],old) + baseline=pathlib.Path(__file__).with_name('fixtures')/'barc_stock_queue_reader.baseline.lua';self.assertEqual(hashlib.sha256(baseline.read_bytes()).hexdigest(),'78f6be637bf9f062a4e9591c34038acb20bf3b726b63d29f18a3e1880a1c6cb8');old=baseline.read_text();new=(ROOT/'barc_stock_queue_reader.lua').read_text();a=new.index(' if result then\n');z=new.index(' return unavailable(message, request, "oversize")',a);self.assertEqual(new[:a]+' if result then return result end\n'+new[z:],old) hook=new[a:z];self.assertNotIn('GiveOrder',hook);self.assertIn('#rows == 0',hook);self.assertIn('observed.observedFrame == nil',hook) def test_unsynced_only_configuration(self): text=(ROOT/'barc_stock_queue_fixture.lua').read_text();start=text.index('if not gadgetHandler:IsSyncedCode() then');end=text.index('local OPT_ENABLE',start);self.assertIn('Spring.GetAIInfo(team)',text[start:end]);self.assertNotIn('Spring.GetAIInfo(',text[end:]);self.assertNotIn('Spring.GetConfigInt(',text[end:]);self.assertNotIn('SYNCED_NOSHORTNAME',text[end:]);self.assertNotEqual(hashlib.sha512(b'controlled').hexdigest(),hashlib.sha256(b'controlled').hexdigest()) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/prepare_policy_closure.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/prepare_policy_closure.py new file mode 100644 index 0000000..1f21ed0 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/prepare_policy_closure.py @@ -0,0 +1,26 @@ +"""SOURCE placement plan only. Never emits a canonical successful closure/receipt.""" +import pathlib, time +from qualify_prearm import exact, need, held_bytes, parse, sha +MANAGED=('apphost','managedDll','depsJson','runtimeConfigJson','fsharpCore') +PROVENANCE=('pdb','sourceLink','buildReceipt','helperManifest','policySourceManifest','productSourceManifest','productRoleGraph','lockedBuildInputs','toolchainReceipt') +RUNTIME=('hostfxr','hostpolicy','coreLib','coreClr','jit') +def prepare(original_pin,candidate,new_root,deadline): + exact(candidate,['productCommit','productTree','highbarCommit'],'candidate') + import re + need(all(type(v)is str and re.fullmatch('[0-9a-f]{40}',v)for v in candidate.values()),'candidate-identity') + root=pathlib.Path(new_root);need(root.is_absolute()and not root.exists(),'fresh-placement-root') + original=parse(held_bytes(original_pin,1024*1024,deadline));exact(original,['schema','custodyRoot','managedRoot','provenanceRoot','runtimeRoots','searchLayout','managed','provenance','runtime','runtimeRoles','source','custody'],'original-closure') + need({x['role']for x in original['managed']}==set(MANAGED)and len(original['managed'])==5 and {x['role']for x in original['provenance']}==set(PROVENANCE)and len(original['provenance'])==9 and set(original['runtimeRoles'])==set(RUNTIME),'nineteen-canonical-roles') + need(original['schema']=='fsbar.barc-runtime-evidence-policy-closure/v3','original-closure-schema');source=original['source'];obstructions=['fresh-final-policy-build-not-executed','fresh-final-placement-not-executed','root-selected-SDK-physical-inventory-unavailable'] + if source['productCommit']!=candidate['productCommit']:obstructions.append('productCommit-mismatch') + if source['productTree']!=candidate['productTree']:obstructions.append('productTree-mismatch') + placements=[] + for group in ['managed','provenance','runtime']: + for row in original[group]: + # Original pins remain historical. New inode/path identities do not exist yet. + placements.append({'group':group,'original':row,'proposedPath':str(root/group/pathlib.Path(row['path']).name),'newPhysicalPin':None}) + need(len({x['proposedPath']for x in placements})==len(placements),'placement-name-collision') + graph={'schema':'fsbar.barc-product-source-role-graph/v1','roles':{'fsbarCommit':candidate['productCommit'],'highbarCommit':candidate['highbarCommit']}} + import json + graph_body=json.dumps(graph,separators=(',',':')).encode() + return {'schema':'bar.policy-closure-source-placement-proposal/v1','ready':False,'originalManifest':original_pin,'candidate':candidate,'custodyRoot':str(root),'canonicalNineteenRoles':list(MANAGED+PROVENANCE+RUNTIME),'placements':placements,'runtimeCensus':original['runtime'],'searchLayoutObligation':original['searchLayout'],'productRoleGraphProposal':graph,'productRoleGraphSha256':sha(graph_body),'sourceGraphHistorical':source,'obstructions':obstructions,'historicalRoute':{'publicEvidenceAvailable':False,'privateSelectionRequired':True},'freshBuildProposal':{'project':'tests/Broker.NativeProof/RuntimeEvidence/RuntimeEvidence.Tests.fsproj','selectedSDK':None,'restoreArgs':['restore','tests/Broker.NativeProof/RuntimeEvidence/RuntimeEvidence.Tests.fsproj','--locked-mode','--source','','-p:DisableImplicitLibraryPacksFolder=true','-m:1','-nr:false','-p:NuGetAudit=false'],'buildArgs':['build','tests/Broker.NativeProof/RuntimeEvidence/RuntimeEvidence.Tests.fsproj','-c','Release','--no-restore','-p:DisableImplicitLibraryPacksFolder=true','-m:1','-nr:false','-p:NuGetAudit=false','-p:UseSharedCompilation=false','-p:SourceLink='],'environment':{'DOTNET_PROCESSOR_COUNT':'1'},'actualOutputs':None,'actualPDBSourceProof':None,'componentReuse':'Retained v5/v6 compile/proof outputs require explicit unchanged input-equivalence joins to candidate361. No prior receipt rewritten.'},'requiredProducerSemantics':['genuine policy/helper/product source identities','locked existing SDK/project/lock inputs','PE/PDB embedded SourceLink and original output correspondence','exact final product graph','complete runtime/dependency census and loaded map/assembly custody'],'receiptRewritePermitted':False,'effectsPerformed':False} diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py new file mode 100644 index 0000000..55185b3 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py @@ -0,0 +1,24 @@ +"""Finite portable SOURCE controls; no subprocess/compiler/Node/Lua/game effects. +Controlled in-process pipes/threads and temporary files are allowed. Tests may mock +Popen explicitly; any unmocked process launch fails the gate. +Actual growing_log/buffered writer controls require separately selected compiled +policy inputs and are deliberately absent from this portable roster. +""" +import pathlib,sys,unittest,importlib.util,subprocess,os +ROOT=pathlib.Path(__file__).resolve().parent +sys.dont_write_bytecode=True +sys.path[:0]=[str(ROOT),str(ROOT/'helper')] +def forbidden(*args,**kwargs):raise RuntimeError('portable source controls prohibit real process effects') +subprocess.Popen=forbidden +os.system=forbidden +for name in ['fork','forkpty','posix_spawn','posix_spawnp','execv','execve','execvp','execvpe']: + if hasattr(os,name):setattr(os,name,forbidden) +FILES=('test_qualify_prearm.py','test_native_evidence.py','helper/test_prearm_startup.py','helper/test_seed_policy.py','helper/test_capture_custody.py','helper/test_loaded_custody.py','helper/test_settings_custody.py','helper/test_proc_bytes.py','fixture/tests/test_prearm_observation.py') +suite=unittest.TestSuite() +for index,name in enumerate(FILES): + spec=importlib.util.spec_from_file_location('portable_seed_controls_'+str(index),ROOT/name);module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module);suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(module)) +expected=71 +if suite.countTestCases()!=expected:raise SystemExit('portable source control roster changed: '+str(suite.countTestCases())) +result=unittest.TextTestRunner(verbosity=2).run(suite) +if not result.wasSuccessful()or result.testsRun!=expected or result.skipped:raise SystemExit(1) +print('PASS: exact '+str(expected)+' portable seed source controls; no actual policy/game/producer evidence') From a4a29d20e065435191e03bcf1d761dc7bf00e097 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 23:07:34 +0200 Subject: [PATCH 07/10] Await exact live parent results across validated observations --- .../LiveIntegrationTests.fs | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/tests/Broker.Browser.Live.Tests/LiveIntegrationTests.fs b/tests/Broker.Browser.Live.Tests/LiveIntegrationTests.fs index 68c86d8..0e36d1c 100644 --- a/tests/Broker.Browser.Live.Tests/LiveIntegrationTests.fs +++ b/tests/Broker.Browser.Live.Tests/LiveIntegrationTests.fs @@ -551,13 +551,69 @@ let tests = testList "production live boundary" [ Expect.equal nativeDispatch.Result.Status LiveResultStatus.Applied "the one terminal dispatch remains applied" Expect.equal nativeDispatch.Result.ParentId brokerResult.Result.ParentId "both native stages retain the reserved parent" + // Results and observations share the production output channel. Await a + // precise result without assuming it is the next WebSocket message. + let awaitParentResult (request: SubmitLiveIntent) stage = task { + use deadline = new CancellationTokenSource(TimeSpan.FromSeconds 3.0) + let seen = ResizeArray() + let describe () = String.concat ", " seen + let rec next remaining = task { + if remaining = 0 then + return failtestf "awaiting %A exhausted eight envelopes; received [%s]" stage (describe()) + else + let bytes = Array.zeroCreate 65536 + let mutable count = 0 + let mutable complete = false + while not complete do + if count = bytes.Length then + failtest "result wait exceeded the bounded WebSocket frame capacity" + let! frame = socket.ReceiveAsync(Memory(bytes, count, bytes.Length-count), deadline.Token).AsTask() + Expect.equal frame.MessageType WebSocketMessageType.Binary "result wait accepts only binary protocol frames" + count <- count + frame.Count + complete <- frame.EndOfMessage + let envelope = LiveServerEnvelope.Parser.ParseFrom(bytes, 0, count) + match envelope.BodyCase with + | LiveServerEnvelope.BodyOneofCase.Result -> + let result = envelope.Result + seen.Add(sprintf "Result(%A)" result.Stage) + Expect.equal result.ParentId request.ParentId (sprintf "exact pending parent; received [%s]" (describe())) + Expect.equal result.InputId request.InputId "result preserves the exact pending input" + Expect.equal result.Stage stage (sprintf "required result order; received [%s]" (describe())) + Expect.equal result.Controller request.Controller "result preserves the complete controller identity" + Expect.equal result.Module request.Module "result preserves the complete module identity" + Expect.equal result.Basis request.Basis "result preserves the exact submitted basis" + return envelope + | LiveServerEnvelope.BodyOneofCase.Observation -> + let observed = envelope.Observation + Expect.isNotNull observed.Preview "interleaved observation includes its typed preview" + Expect.isNotNull observed.Basis "interleaved observation includes its typed basis" + seen.Add(sprintf "Observation(%d)" observed.Preview.Sequence) + // No new metadata is published during these parents. Only + // the already paired current observation can interleave. + Expect.equal observed.Basis raceObservation.Observation.Basis "interleaved observation has the exact paired owning-generation basis" + Expect.equal observed.Preview.SessionId controller.SessionId "interleaved observation belongs to this authenticated session" + Expect.equal observed.Preview.Sequence observed.Basis.StateSequence "interleaved preview and basis have the same sequence" + Expect.equal observed.Preview.Validity raceObservation.Observation.Preview.Validity "interleaved observation retains current validity without a new gap" + Expect.sequenceEqual observed.Units raceObservation.Observation.Units "interleaved observation retains exact unit lifetimes" + Expect.equal observed.Tactical raceObservation.Observation.Tactical "interleaved tactical evidence retains the exact paired basis" + return! next (remaining-1) + | unexpected -> + return failtestf "awaiting %A received unexpected %A; preceding [%s]" stage unexpected (describe()) + } + try + return! next 8 + with :? OperationCanceledException -> + return failtestf "awaiting %A exceeded three seconds; received [%s]" stage (describe()) + } + let completeAdditionalParent dispatchSequence = task { let request=submit.Clone() request.ParentId<-ByteString.CopyFrom(Guid.NewGuid().ToByteArray()) request.InputId<-ByteString.CopyFrom(Guid.NewGuid().ToByteArray()) do! send socket (LiveClientEnvelope(Submit=request)) - let! (broker: LiveServerEnvelope)=receive socket + let! (broker: LiveServerEnvelope)=awaitParentResult request LiveResultStage.BrokerAdmission Expect.equal broker.Result.Stage LiveResultStage.BrokerAdmission "the next parent is admitted on the same socket" + Expect.equal broker.Result.Status LiveResultStatus.Accepted "the next parent is accepted before awaiting its native child" let! more=commandCall.ResponseStream.MoveNext(CancellationToken.None) Expect.isTrue more "the next parent reaches the live native channel" let nextChild=commandCall.ResponseStream.Current @@ -572,8 +628,9 @@ let tests = testList "production live boundary" [ report.ChannelIncarnation<-nextChild.Binding.Value.CommandChannelIncarnation report.Result<-ValueSome admitted let! (_:CommandBatchResultReportAck)=coordinator.ReportCommandBatchResultAsync(report).ResponseAsync - let! (nativeAccepted:LiveServerEnvelope)=receive socket + let! (nativeAccepted:LiveServerEnvelope)=awaitParentResult request LiveResultStage.NativeAdmission Expect.equal nativeAccepted.Result.Stage LiveResultStage.NativeAdmission "native admission follows broker admission" + Expect.equal nativeAccepted.Result.Status LiveResultStatus.Accepted "the matching child receives accepted native admission" let applied=CommandDispatchEvent.empty() applied.BatchSeq<-nextChild.Batch.Value.BatchSeq applied.ClientCommandId<-nextChild.Batch.Value.ClientCommandId.Value @@ -591,7 +648,7 @@ let tests = testList "production live boundary" [ state.Frame<-applied.Frame state.Delta<-delta do! push.RequestStream.WriteAsync state - let! (terminal:LiveServerEnvelope)=receive socket + let! (terminal:LiveServerEnvelope)=awaitParentResult request LiveResultStage.NativeDispatch Expect.equal terminal.Result.Stage LiveResultStage.NativeDispatch "native dispatch remains terminal" Expect.equal terminal.Result.Status LiveResultStatus.Applied "the next parent completes exactly once" } From f9fd5bf2fd568c3e0577b82fbf25b82f529c08a3 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 23:24:16 +0200 Subject: [PATCH 08/10] Share owned policy transport with finite no-game qualification caller --- .../helper/seed_policy.py | 167 ++++++++++++---- .../helper/test_prearm_startup.py | 13 +- .../helper/test_seed_policy.py | 19 +- .../policy_transport_runner.py | 179 ++++++++++++++++++ .../run-source-controls.py | 4 +- .../seed-attribution-helper/startup_runner.py | 33 ++-- .../test_policy_transport_runner.py | 80 ++++++++ 7 files changed, 436 insertions(+), 59 deletions(-) create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/policy_transport_runner.py create mode 100644 tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/seed_policy.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/seed_policy.py index ae9c933..20d5f50 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/seed_policy.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/helper/seed_policy.py @@ -1,25 +1,109 @@ """Literal owned direct-apphost transport; inherited canonical infolog semantics. Inert without the selected startup runner's concrete live custody context. """ -import os,time,json,hashlib,selectors,signal,subprocess,pathlib +import os,time,json,hashlib,selectors,signal,subprocess,pathlib,re from growing_log import GrowingLog,_unique,_policy_observation,POLICY_TO_MECHANICAL,MAX_POLICY_INPUT from private_io import need,Refused from runtime_identity import mark_failure +def policy_argv(policy,invocation): + need(re.fullmatch('[0-9a-f]{64}',invocation or ''),'exact policy invocation') + need(policy['environment']=={'PATH':'/usr/bin:/bin'},'literal policy environment') + return [policy['apphost'],'--closure-manifest',policy['closurePath'],'--closure-sha256',policy['closureSha256'],'--invocation-id',invocation] + +def _entry(context): + if type(context)is SeedPolicyContext: + import startup_runner as entry + elif type(context)is PolicyOnlyContext: + import policy_transport_runner as entry + else:raise Refused('closed concrete policy context') + return entry + +def _validate(context): + import mechanics as m + need(type(context.owner)is m.LinuxOwner,'concrete held Linux policy owner') + entry=_entry(context);a=m.load(context.admission) + entry.source_binding(a,context.profile) + need(a['operation']==('seed-attribution-startup-only'if type(context)is SeedPolicyContext else 'policy-transport-qualification-only'),'matching admitted policy composition') + need(context.profile==entry.profile(),'actual pinned concrete policy profile') + need(context.evidence_root==pathlib.Path(entry.ROOT)/'evidence','literal bound evidence root') + need(context.profile['policy']['environment']=={'PATH':'/usr/bin:/bin'},'literal bound policy environment') + return entry,a + +def launch_policy(context,invocation,deadline): + import mechanics as m + need(context.active is None and context.evaluations<3 and len(context.reaped)==context.evaluations,'one settled policy child before next evaluation') + entry,a=_validate(context) + command=policy_argv(context.profile['policy'],invocation) + need(context.pump(deadline)==(1 if type(context)is SeedPolicyContext else 0),'exact prelaunch task CLR count') + if type(context)is SeedPolicyContext:entry.runtime_socket().directory_check() + context.evaluations+=1;name='policy-'+str(context.evaluations) + end=min(deadline,context.deadline-8);rd,rw=os.pipe();ar,aw=os.pipe();child=None + try: + argv=[entry.PYTHON,'-I','-B',str(entry.HERE/entry.__file__.split('/')[-1]),'--shim','policy','--admission',str(context.admission),'--ready',str(rw),'--ack',str(ar),'--invocation',invocation] + child=subprocess.Popen(argv,env=context.profile['policy']['environment'],pass_fds=(rw,ar),start_new_session=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,umask=0o077) + context.active=child;context.children[name]=child;context.owner.leaders.add(child.pid) + os.close(rw);rw=-1;os.close(ar);ar=-1 + with selectors.DefaultSelector()as sel: + sel.register(rd,selectors.EVENT_READ) + while True: + context.pump(end) + if sel.select(m.remaining(end,.02)):break + need(os.read(rd,64)==b'BAR-POLICY-READY\n','policy preexec handshake') + ident=m.identity(child.pid) + need(ident['pgid']==ident['session']==child.pid and ident['ppid']==os.getpid()and ident['uid']==os.geteuid(),'actual owned policy identity') + if child.pid not in context.owner.records:context.owner.acquire(ident) + need(m.same(ident,context.owner.records[child.pid])and child.pid in context.owner.handles,'held policy pidfd before GO') + need(os.sched_getaffinity(child.pid)=={a['cpu']},'policy serial affinity') + m.write_new(context.evidence_root/(name+'.process.json'),dict(identity=ident,argv=command,environment=context.profile['policy']['environment'],heldPidfdBeforeGO=True,sourceSHA256=a['sourceSHA256'],profileSHA256=a['profileSHA256'])) + os.write(aw,b'GO\n');return child + except BaseException: + # Ownership is retained in the context even when the handshake failed. + # Caller/outer cleanup records the first failure and held settlement. + if child is not None and child.pid not in context.owner.records: + context.owner.observe() + raise + finally: + for fd in [rd,rw,ar,aw]: + if fd>=0: + try:os.close(fd) + except OSError:pass + +def policy_shim(args,entry,p,a): + import mechanics as m + import sys + kind='startup_runner'if entry is sys.modules.get('startup_runner')else 'policy_transport_runner'if entry is sys.modules.get('policy_transport_runner')else None + need(kind is not None,'literal policy shim caller') + entry.source_binding(a,p) + need(args.shim=='policy'and os.sched_getaffinity(0)=={a['cpu']},'literal policy shim and affinity') + parent=m.load(pathlib.Path(entry.ROOT)/'evidence/worker-start.json') + need(m.same(parent['worker'],m.identity(os.getppid()))and parent['admissionSHA256']==m.digest(args.admission),'source-bound actual policy parent') + if kind=='startup_runner':entry.runtime_socket().directory_check() + argv=policy_argv(p['policy'],args.invocation) + os.write(args.ready,b'BAR-POLICY-READY\n');os.close(args.ready) + need(os.read(args.ack,16)==b'GO\n','actual held policy release');os.close(args.ack) + entry.source_binding(a,p) + if kind=='startup_runner':entry.runtime_socket().directory_check() + os.chdir(entry.ROOT);os.execve(argv[0],argv,p['policy']['environment']) + +def settle_policy(context,child,deadline,force=False): + import mechanics as m + need(child is context.active,'exact policy child settlement') + if child.pid not in context.owner.records:context.owner.observe() + ident=context.owner.records[child.pid] + if force and child.poll()is None:context.owner.send(ident,signal.SIGKILL) + end=min(context.deadline,time.monotonic()+3)if force else deadline + remaining=end-time.monotonic();need(remaining>0,'policy settlement Unknown: deadline') + code=child.wait(timeout=remaining);context.owner.leaders.discard(child.pid) + context.reaped.append({'identity':ident,'exitCode':code,'reaped':True,'forced':force});context.active=None;return code + class SeedPolicyContext: def __init__(self,profile,admission,owner,children,actor_selector,streams,counts,deadline): - self.profile=profile;self.admission=admission;self.owner=owner;self.children=children;self.actor_selector=actor_selector;self.streams=streams;self.counts=counts;self.deadline=deadline;self.evaluations=0;self.active=None;self.reaped=[] + import startup_runner as runner + self.profile=profile;self.admission=admission;self.owner=owner;self.children=children;self.actor_selector=actor_selector;self.streams=streams;self.counts=counts;self.deadline=deadline;self.evaluations=0;self.active=None;self.reaped=[];self.evidence_root=pathlib.Path(runner.ROOT)/'evidence' def pump(self,deadline): - # This is the real actor selector/owner/quota, not an injected success callback. import startup_runner as runner need(time.monotonic()0,'policy settlement Unknown: deadline') - code=child.wait(timeout=remaining);self.owner.leaders.discard(child.pid);self.reaped.append({'identity':ident,'exitCode':code,'reaped':True});self.active=None;return code + need(time.monotonic()=0: + try:os.close(fd) + except OSError:pass + if owned: + try:result['independentCustody']=owned.settle(deadline);need(result['independentCustody']['leaderReaped']and not result['independentCustody']['remaining']and not result['independentCustody']['errors'],'independent complete settlement') + except BaseException as error:fail('outer-cleanup',error) + else:fail('outer-cleanup',m.CleanupUnknown('independent owner unavailable')) + try: + result['postOriginals']=originals(p,deadline);inner=m.load(A/'evidence/transport-result.json');result['inner']=inner + need(inner['transportQualified']and inner['innerCleanup']['status']=='settled'and result.get('workerExitCode')==0,'inner transport and cleanup required') + except BaseException as error:fail('inner/pins',error) + for name,raw in buffers.items(): + try:bytes_new(A/'logs'/('outer-'+name+'.raw'),bytes(raw),524288) + except BaseException as error:fail('raw-publication',error) + try:json_new(A/'evidence/full-output-census.json',{'files':m.census(A),'scope':'before final outer receipt; root final census required'});result['finalQuota']=quota() + except BaseException as error:fail('final-census',error) + result.update(transportQualified=failure is None and not result['secondaryFailures'],failure=str(failure)if failure else None,elapsedSeconds=time.monotonic()-started,operationCount=1,noRetry=True,nativeSeedQualified=False,actorEligible=False,nativeUsefulPlay='0/6',noGameBrowserArmOrCommands=True,sourceLinkRemoteContent='Unknown',rootVerdict='Pending root full review') + encoded=(json.dumps(result,sort_keys=True,separators=(',',':'))+'\n').encode();q=result.get('finalQuota',{}) + if 'files'not in q or q['files']+1>1024 or q['bytes']+len(encoded)>134217728:result.update(transportQualified=False,failure=result.get('failure')or'final receipt quota reserve') + json_new(A/'evidence/outer-result.json',result,1048576) + return 0 if result['transportQualified']else 3 + +def main(): + parser=argparse.ArgumentParser();parser.add_argument('--admission',required=True);parser.add_argument('--worker',action='store_true');parser.add_argument('--shim',choices=['policy']);parser.add_argument('--ready',type=int);parser.add_argument('--ack',type=int);parser.add_argument('--invocation');args=parser.parse_args() + need(not(args.worker and args.shim),'one closed entry') + if args.shim:return shim(args) + if args.worker:return worker(args) + need(args.ready is None and args.ack is None and args.invocation is None,'no outer command/payload selection');return execute(pathlib.Path(args.admission)) +if __name__=='__main__': + sys.modules['policy_transport_runner']=sys.modules[__name__] + raise SystemExit(main()) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py index 55185b3..6aff419 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py @@ -13,11 +13,11 @@ def forbidden(*args,**kwargs):raise RuntimeError('portable source controls prohi os.system=forbidden for name in ['fork','forkpty','posix_spawn','posix_spawnp','execv','execve','execvp','execvpe']: if hasattr(os,name):setattr(os,name,forbidden) -FILES=('test_qualify_prearm.py','test_native_evidence.py','helper/test_prearm_startup.py','helper/test_seed_policy.py','helper/test_capture_custody.py','helper/test_loaded_custody.py','helper/test_settings_custody.py','helper/test_proc_bytes.py','fixture/tests/test_prearm_observation.py') +FILES=('test_policy_transport_runner.py','test_qualify_prearm.py','test_native_evidence.py','helper/test_prearm_startup.py','helper/test_seed_policy.py','helper/test_capture_custody.py','helper/test_loaded_custody.py','helper/test_settings_custody.py','helper/test_proc_bytes.py','fixture/tests/test_prearm_observation.py') suite=unittest.TestSuite() for index,name in enumerate(FILES): spec=importlib.util.spec_from_file_location('portable_seed_controls_'+str(index),ROOT/name);module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module);suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(module)) -expected=71 +expected=87 if suite.countTestCases()!=expected:raise SystemExit('portable source control roster changed: '+str(suite.countTestCases())) result=unittest.TextTestRunner(verbosity=2).run(suite) if not result.wasSuccessful()or result.testsRun!=expected or result.skipped:raise SystemExit(1) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/startup_runner.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/startup_runner.py index 83c907f..47e86ba 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/startup_runner.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/startup_runner.py @@ -25,7 +25,7 @@ def profile(): validate_schema(p) need(p['requiredHostMappings']==required and len(required)==3,'literal pinned apphost/coreclr/clrjit') need(p['policy']['environment']=={'PATH':'/usr/bin:/bin'} and p['policy']['maximumSimultaneousChildren']==1 and p['policy']['maximumEvaluations']==3 and p['policy']['deadlineSeconds']==5 and p['policy']['inputBytes']==16777216 and p['policy']['outputBytes']==131072,'literal closed policy caps') - need(set(p['sourceHelperPins'])=={'helper/runtime_identity.py','helper/stock_bootstrap.py','helper/dynamic_mapping.py','helper/snapshot_publication.py','helper/mapping_profile.py','helper/seed_policy.py','helper/growing_log.py','helper/loaded_custody.py','helper/settings_custody.py','qualify_prearm.py','native_evidence.py'},'closed successor source helpers') + need(set(p['sourceHelperPins'])=={'helper/runtime_identity.py','helper/stock_bootstrap.py','helper/dynamic_mapping.py','helper/snapshot_publication.py','helper/mapping_profile.py','helper/seed_policy.py','helper/growing_log.py','helper/loaded_custody.py','helper/settings_custody.py','qualify_prearm.py','native_evidence.py','policy_transport_runner.py'},'closed successor source helpers') for path,sha in p['sourceHelperPins'].items():need(m.digest(HERE/path)==sha,'exact profile helper byte pin') return p @@ -71,7 +71,8 @@ def commands(p,invocation=None): ports=p['proposedPorts'];A=pathlib.Path(ROOT) result={'host':[p['nativeHost'],'--stock-tactical-live-host',f"127.0.0.1:{ports['grpc']}",f"http://127.0.0.1:{ports['gateway']}",f"http://127.0.0.1:{ports['unusedReceiverOrigin']}",str(A/'host'),p['source']['fsbarCommit']], 'engine':[p['engine'],'--isolation','--isolation-dir',p['dataRoot'],'--write-dir',str(A/'engine'),'--config',str(A/'engine/springsettings.cfg'),p['startscript']]} if invocation is not None: - need(re.fullmatch('[0-9a-f]{64}',invocation),'exact policy invocation');result['policy']=[p['policy']['apphost'],'--closure-manifest',p['policy']['closurePath'],'--closure-sha256',p['policy']['closureSha256'],'--invocation-id',invocation] + from seed_policy import policy_argv + result['policy']=policy_argv(p['policy'],invocation) return result def runtime_socket(): @@ -140,25 +141,25 @@ def record_tail(path,label): def launch(role,p,a_path,owner,deadline,registered,invocation=None,evaluation=None,cleanup_reserve=8,policy_context=None): need(role in ['host','engine','policy'],'literal launch role') - need((role!='policy' and invocation is None and evaluation is None) or (role=='policy' and re.fullmatch('[0-9a-f]{64}',invocation or '') and evaluation in [1,2,3]),'literal invocation/evaluation') - name=role if role!='policy'else 'policy-'+str(evaluation) + if role=='policy': + from seed_policy import SeedPolicyContext,launch_policy + need(type(policy_context)is SeedPolicyContext and policy_context.owner is owner and policy_context.children is registered and policy_context.profile is p and str(policy_context.admission)==str(a_path),'exact native policy caller') + return launch_policy(policy_context,invocation,deadline) + need(role in ['host','engine']and invocation is None and evaluation is None,'literal native actor launch') + name=role end=min(deadline-cleanup_reserve,clock()+5);rd,rw=os.pipe();ar,aw=os.pipe();child=None try: runtime_socket().directory_check() shim_argv=[PYTHON,'-I','-B',str(HERE/'startup_runner.py'),'--shim',role,'--admission',str(a_path),'--ready',str(rw),'--ack',str(ar)] - if role=='policy':shim_argv+=['--invocation',invocation] - child=subprocess.Popen(shim_argv,env=environment(p,role),pass_fds=(rw,ar),start_new_session=True,stdin=subprocess.PIPE if role=='policy'else None,stdout=subprocess.PIPE,stderr=subprocess.PIPE,umask=0o077) + child=subprocess.Popen(shim_argv,env=environment(p,role),pass_fds=(rw,ar),start_new_session=True,stdin=None,stdout=subprocess.PIPE,stderr=subprocess.PIPE,umask=0o077) registered[name]=child;owner.leaders.add(child.pid);os.close(rw);rw=-1;os.close(ar);ar=-1 with selectors.DefaultSelector()as sel: sel.register(rd,selectors.EVENT_READ) while True: - if role=='policy': - from seed_policy import SeedPolicyContext - need(type(policy_context)is SeedPolicyContext,'concrete policy preexec supervision context');policy_context.pump(end) - if sel.select(m.remaining(end,.02 if role=='policy'else 5)):break + if sel.select(m.remaining(end,5)):break need(os.read(rd,64)==b'BAR-STARTUP-READY\n','actor preexec handshake') ident=m.identity(child.pid);need(ident['pgid']==ident['session']==child.pid and ident['ppid']==os.getpid(),'actual owned actor identity');owner.acquire(ident);need(os.sched_getaffinity(child.pid)=={m.load(a_path)['cpu']},'actor serial affinity') - m.write_new(pathlib.Path(ROOT)/'evidence'/(name+'.process.json'),dict(identity=ident,argv=commands(p,invocation)[role],environment=environment(p,role)));os.write(aw,b'GO\n');return child + m.write_new(pathlib.Path(ROOT)/'evidence'/(name+'.process.json'),dict(identity=ident,argv=commands(p)[role],environment=environment(p,role)));os.write(aw,b'GO\n');return child finally: for fd in [rd,rw,ar,aw]: if fd>=0: @@ -312,7 +313,11 @@ def source_binding(a,p): need(a['sourceSHA256']==reviewed_source()and a['profileSHA256']==m.digest(HERE/'startup-profile.json'),'shim/worker exact source') def shim(args): - p=profile();a=m.load(args.admission);source_binding(a,p);need(args.shim in ['host','engine','policy'],'literal roles');need((args.shim=='policy' and re.fullmatch('[0-9a-f]{64}',args.invocation or ''))or(args.shim!='policy' and args.invocation is None),'shim literal invocation');need(os.sched_getaffinity(0)=={a['cpu']},'shim affinity') + p=profile();a=m.load(args.admission);source_binding(a,p) + if args.shim=='policy': + from seed_policy import policy_shim + return policy_shim(args,sys.modules[__name__],p,a) + need(args.shim in ['host','engine','policy'],'literal roles');need((args.shim=='policy' and re.fullmatch('[0-9a-f]{64}',args.invocation or ''))or(args.shim!='policy' and args.invocation is None),'shim literal invocation');need(os.sched_getaffinity(0)=={a['cpu']},'shim affinity') parent=m.load(pathlib.Path(ROOT)/'evidence/worker-start.json');need(m.same(parent['worker'],m.identity(os.getppid()))and parent['admissionSHA256']==m.digest(args.admission),'owned source-bound parent') os.write(args.ready,b'BAR-STARTUP-READY\n');os.close(args.ready);need(os.read(args.ack,16)==b'GO\n','actual preexec observer release');os.close(args.ack);runtime_socket().directory_check();os.chdir(ROOT);c=commands(p,args.invocation)[args.shim];os.execve(c[0],c,environment(p,args.shim)) @@ -413,4 +418,6 @@ def main(): if a.shim:return shim(a) if a.worker:return worker(a) return execute(pathlib.Path(a.admission)) -if __name__=='__main__':raise SystemExit(main()) +if __name__=='__main__': + sys.modules['startup_runner']=sys.modules[__name__] + raise SystemExit(main()) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py new file mode 100644 index 0000000..4863ea6 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py @@ -0,0 +1,80 @@ +"""Pure closed-composition/source-routing controls; no policy/CLR/native execution.""" +import unittest,time,pathlib,tempfile,os,ast +from unittest.mock import patch,Mock +import seed_policy as s +import policy_transport_runner as r +import startup_runner as native +import mechanics as m +from private_io import Refused +class Composition(unittest.TestCase): + def context(self):return s.PolicyOnlyContext({'policy':{'apphost':'/controlled/apphost','closurePath':'/controlled/closure','closureSha256':'c'*64,'environment':{'PATH':'/usr/bin:/bin'}}},'/controlled/a',Mock(),time.monotonic()+20) + def test_closed_binding(self): + with patch.object(s,'_validate')as validation: + for kind in [s.SeedPolicyContext,s.PolicyOnlyContext]: + log=object.__new__(s.SeedGrowingLog);ctx=object.__new__(kind);self.assertIs(log.bind(ctx),log) + with self.assertRaises(Refused):log.bind(ctx) + validation.assert_called() + class Extension(s.PolicyOnlyContext):pass + for ctx in [object.__new__(Extension),object()]: + with self.assertRaises(Refused):object.__new__(s.SeedGrowingLog).bind(ctx) + def test_exact_seven_element_argv(self): + ctx=self.context();argv=s.policy_argv(ctx.profile['policy'],'d'*64) + self.assertEqual(argv,['/controlled/apphost','--closure-manifest','/controlled/closure','--closure-sha256','c'*64,'--invocation-id','d'*64]) + for invocation in ['',None,'d'*63,'d'*64+';']: + with self.assertRaises(Refused):s.policy_argv(ctx.profile['policy'],invocation) + def test_unsettled_and_fourth_refuse_before_source_or_launch(self): + ctx=self.context() + with patch.object(s,'_validate')as validate,patch.object(s.subprocess,'Popen')as process: + ctx.active=object() + with self.assertRaises(Refused):ctx.launch('a'*64,time.monotonic()+1) + ctx.active=None;ctx.evaluations=3;ctx.reaped=[1,2,3] + with self.assertRaises(Refused):ctx.launch('a'*64,time.monotonic()+1) + validate.assert_not_called();process.assert_not_called() + def test_native_and_policy_only_delegate_same_launch_settlement(self): + ctx=self.context();native_ctx=object.__new__(s.SeedPolicyContext) + with patch.object(s,'launch_policy',return_value='CONTROLLED')as launch: + self.assertEqual(ctx.launch('a'*64,1),'CONTROLLED');self.assertEqual(native_ctx.launch('a'*64,1),'CONTROLLED');self.assertEqual(launch.call_count,2) + with patch.object(s,'settle_policy',return_value=0)as settle: + self.assertEqual(ctx.settle('child',1),0);self.assertEqual(native_ctx.settle('child',1),0);self.assertEqual(settle.call_count,2) + def test_policy_only_does_not_import_or_invoke_native_profile_socket_commands(self): + tree=ast.parse(pathlib.Path(r.__file__).read_text());names={x.module for x in ast.walk(tree)if isinstance(x,ast.ImportFrom)}|{a.name for x in ast.walk(tree)if isinstance(x,ast.Import)for a in x.names} + self.assertNotIn('startup_runner',names);self.assertNotIn('runtime_socket',names) + source=pathlib.Path(r.__file__).read_text();self.assertNotIn('nativeHost',source);self.assertNotIn('engineRuntimeEnvironment',source) + def test_native_shim_and_policy_only_shim_use_shared_function(self): + for module in [native,r]: + tree=ast.parse(pathlib.Path(module.__file__).read_text());shim=next(x for x in tree.body if isinstance(x,ast.FunctionDef)and x.name=='shim') + self.assertTrue(any(isinstance(x,ast.Call)and isinstance(x.func,ast.Name)and x.func.id=='policy_shim'for x in ast.walk(shim))) + def test_wrong_admission_does_not_cross_composition(self): + with self.assertRaises(m.Refused):r.source_binding({'schema':'bar.seed-attribution-admission/v2','operation':'seed-attribution-startup-only'}, {}) + ctx=self.context();ctx.owner=object.__new__(m.LinuxOwner) + with patch.object(m,'load',return_value={'operation':'seed-attribution-startup-only'}),patch.object(r,'source_binding'): + with self.assertRaises(Refused):s._validate(ctx) + def test_unprepared_profile_refuses(self): + with self.assertRaises(m.Refused):r.profile() + def test_policy_only_quota_has_no_socket_exception_and_rejects_foreign_descendant(self): + ctx=self.context();ctx.owner.uncertain=False;ctx.owner.records={};ctx.owner.observe.return_value=[] + with patch.object(m,'quota',return_value={'files':0})as quota,patch.object(s,'_clr',return_value=[]): + self.assertEqual(ctx.pump(time.monotonic()+1),0);self.assertNotIn('runtime_socket',quota.call_args.kwargs) + ctx.owner.observe.return_value=[{'pid':999}] + with self.assertRaises(Refused):ctx.pump(time.monotonic()+1) + def test_one_policy_clr_cap(self): + ctx=self.context();ctx.owner.uncertain=False;ctx.owner.records={};ctx.owner.observe.return_value=[] + with patch.object(m,'quota',return_value={'files':0}),patch.object(s,'_clr',return_value=[1,2]): + with self.assertRaises(Refused):ctx.pump(time.monotonic()+1) + def test_native_pump_still_requires_both_live_actors(self): + owner=Mock();owner.observe.return_value=[];selector=Mock();selector.select.return_value=[] + children={'host':Mock(),'engine':Mock()} + ctx=s.SeedPolicyContext({'policy':{}},'/controlled/a',owner,children,selector,{}, {},time.monotonic()+20) + with patch.object(native,'quota'),patch.object(s,'_clr',return_value=[]): + for role in ['host','engine']: + for child in children.values():child.poll.return_value=None + children[role].poll.return_value=1 + with self.subTest(role=role),self.assertRaises(Refused):ctx.pump(time.monotonic()+1) + def test_exact_policy_source_profile_required(self): + ctx=self.context();ctx.owner=object.__new__(m.LinuxOwner) + with patch.object(m,'load',return_value={'operation':'policy-transport-qualification-only'}),patch.object(r,'source_binding'),patch.object(r,'profile',return_value={'different':'CONTROLLED'}): + with self.assertRaises(Refused):s._validate(ctx) + def test_controlled_worker_writer_and_exact_case_order_are_source_wired(self): + source=pathlib.Path(r.__file__).read_text();self.assertIn('os.O_WRONLY|os.O_APPEND|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW',source);self.assertIn("start_ticks(os.getpid())",source);self.assertIn("log.consume('browser'",source);self.assertIn("ctx.evaluations==1",source);self.assertIn("ctx.evaluations==3",source) + self.assertEqual(r.CASES,['stable-consume','malformed-request','withheld-input']) +if __name__=='__main__':unittest.main() From f59ca98d74c5757236ee2e8f8c437e89b8d7fae8 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 23:29:17 +0200 Subject: [PATCH 09/10] Bind policy transport qualification to one-use private admission --- .../seed-attribution-helper/policy_transport_runner.py | 3 +++ .../seed-attribution-helper/run-source-controls.py | 2 +- .../test_policy_transport_runner.py | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/policy_transport_runner.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/policy_transport_runner.py index 0e0ea39..93ebbe9 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/policy_transport_runner.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/policy_transport_runner.py @@ -116,8 +116,11 @@ def worker(args): return 0 if result['transportQualified']else 3 def execute(a_path): + info=a_path.lstat();need(stat.S_ISREG(info.st_mode)and stat.S_IMODE(info.st_mode)==0o600 and info.st_uid==os.geteuid()and info.st_nlink==1,'private single-link actual admission') + consumed=a_path.parent/(a_path.name+'.consumed');need(not os.path.lexists(consumed),'one-use grant already consumed') started=time.monotonic();deadline=started+180;p=profile();a=m.load(a_path) admission(a,p,m.identity(os.getppid()),datetime.datetime.now(datetime.timezone.utc),not os.path.lexists(ROOT),os.sched_getaffinity(0));os.sched_setaffinity(0,{a['cpu']});pre=originals(p,deadline-8) + json_new(consumed,{'sourceSHA256':a['sourceSHA256'],'profileSHA256':a['profileSHA256'],'operationRoot':ROOT,'operator':a['operator'],'noRetry':True}) A=pathlib.Path(ROOT);A.mkdir(mode=0o700) for name in ['logs','evidence']:(A/name).mkdir(mode=0o700) json_new(A/'evidence/outer-start.json',dict(outer=m.identity(os.getpid()),admissionSHA256=m.digest(a_path),deadline=deadline,preOriginals=pre)) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py index 6aff419..452942c 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py @@ -17,7 +17,7 @@ def forbidden(*args,**kwargs):raise RuntimeError('portable source controls prohi suite=unittest.TestSuite() for index,name in enumerate(FILES): spec=importlib.util.spec_from_file_location('portable_seed_controls_'+str(index),ROOT/name);module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module);suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(module)) -expected=87 +expected=88 if suite.countTestCases()!=expected:raise SystemExit('portable source control roster changed: '+str(suite.countTestCases())) result=unittest.TextTestRunner(verbosity=2).run(suite) if not result.wasSuccessful()or result.testsRun!=expected or result.skipped:raise SystemExit(1) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py index 4863ea6..bb17dd1 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py @@ -8,6 +8,13 @@ from private_io import Refused class Composition(unittest.TestCase): def context(self):return s.PolicyOnlyContext({'policy':{'apphost':'/controlled/apphost','closurePath':'/controlled/closure','closureSha256':'c'*64,'environment':{'PATH':'/usr/bin:/bin'}}},'/controlled/a',Mock(),time.monotonic()+20) + def test_outer_one_use_and_private_grant_before_effects(self): + source=pathlib.Path(r.__file__).read_text();self.assertIn("stat.S_IMODE(info.st_mode)==0o600",source);self.assertIn("info.st_nlink==1",source);self.assertLess(source.index('json_new(consumed,'),source.index('A.mkdir(mode=0o700)')) + with tempfile.TemporaryDirectory()as d: + grant=pathlib.Path(d)/'grant';grant.write_text('{}');grant.chmod(0o600);(grant.parent/(grant.name+'.consumed')).write_text('{}') + with patch.object(r,'profile')as profile,patch.object(r.subprocess,'Popen')as process: + with self.assertRaises(m.Refused):r.execute(grant) + profile.assert_not_called();process.assert_not_called() def test_closed_binding(self): with patch.object(s,'_validate')as validation: for kind in [s.SeedPolicyContext,s.PolicyOnlyContext]: From e916fa2ddcc08daf408d5fe6066e2281401591c6 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 4 Oct 2026 23:38:56 +0200 Subject: [PATCH 10/10] Bind malformed policy refusal to retained exact exception diagnostic --- .../policy_transport_runner.py | 14 +++++++++++++- .../run-source-controls.py | 2 +- .../test_policy_transport_runner.py | 17 ++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/policy_transport_runner.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/policy_transport_runner.py index 93ebbe9..99a5293 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/policy_transport_runner.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/policy_transport_runner.py @@ -8,6 +8,7 @@ HERE=pathlib.Path(__file__).resolve().parent;sys.path[:0]=[str(HERE),str(HERE/'helper')] import mechanics as m,outer_custody as outer from seed_policy import SeedGrowingLog,PolicyOnlyContext,policy_shim +from growing_log import _policy_observation from runtime_identity import start_ticks from snapshot_publication import bytes_new,json_new ROOT='/tmp/bar-policy-transport-qualification-20261004';PYTHON='/usr/bin/python3.14' @@ -65,6 +66,17 @@ def shim(args): p=profile();a=m.load(args.admission);source_binding(a,p) return policy_shim(args,sys.modules['policy_transport_runner'],p,a) +def require_malformed_refusal(retained,settlement,encoded,evaluation): + need(evaluation==2 and encoded==b'{malformed','literal second malformed control') + need(retained['requestWrittenBytes']==len(encoded)and retained['requestSha256']==hashlib.sha256(encoded).hexdigest()and settlement['exitCode']==2 and not settlement['forced'],'malformed request must naturally refuse after exact full request') + ack=retained['stderr'];expected=pathlib.Path(ROOT)/'evidence'/('policy-'+str(evaluation)+'.failure.stderr.raw') + need(ack['path']==str(expected)and ack['fullWriteFsyncAcknowledged']is True and ack['bytes']<=2048,'exact acknowledged bounded malformed diagnostic') + m.checked_pin(ack);raw=expected.read_bytes() + need(len(raw)==ack['bytes']and hashlib.sha256(raw).hexdigest()==ack['sha256'],'retained malformed diagnostic byte join');m.checked_pin(ack) + observation=_policy_observation(raw) + need(observation=={'schema':'fsbar.barc-runtime-evidence-failure-observation/v1','checkpoint':'request-evaluation','kind':'exception'},'exact genuine malformed request-evaluation exception diagnostic') + return observation + def run_cases(p,a_path,owner,deadline): import base64 A=pathlib.Path(ROOT);f=A/'fixture';f.mkdir(mode=0o700) @@ -91,7 +103,7 @@ def run_cases(p,a_path,owner,deadline): retained=m.load(A/'evidence'/('policy-'+str(ctx.evaluations)+'.failure.json')) need(retained['ready']is not None,'negative control ready-before-failure coverage required') if case=='withheld-input':need(retained['requestWrittenBytes']==0 and ctx.reaped[-1]['forced']and 'deadline'in str(error),'withheld-input deadline must force held settlement') - else:need(retained['requestWrittenBytes']==len(encoded)and ctx.reaped[-1]['exitCode']==2 and not ctx.reaped[-1]['forced'],'malformed request must naturally refuse after full request') + else:require_malformed_refusal(retained,ctx.reaped[-1],encoded,ctx.evaluations) need(not getattr(error,'_barc_secondary_settlement_failure',None)and not getattr(error,'_barc_secondary_publication_failure',None),'negative cleanup/publication complete') json_new(A/'evidence'/('control-'+case+'.json'),{'case':case,'expectedFailure':str(error),'firstFailure':retained,'observedSettlement':ctx.reaped[-1],'failureIsNotNaturalSuccess':True,'actorEligible':False}) results.append({'case':case,'qualified':True,'expectedNegative':True}) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py index 452942c..1a32265 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/run-source-controls.py @@ -17,7 +17,7 @@ def forbidden(*args,**kwargs):raise RuntimeError('portable source controls prohi suite=unittest.TestSuite() for index,name in enumerate(FILES): spec=importlib.util.spec_from_file_location('portable_seed_controls_'+str(index),ROOT/name);module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module);suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(module)) -expected=88 +expected=89 if suite.countTestCases()!=expected:raise SystemExit('portable source control roster changed: '+str(suite.countTestCases())) result=unittest.TextTestRunner(verbosity=2).run(suite) if not result.wasSuccessful()or result.testsRun!=expected or result.skipped:raise SystemExit(1) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py index bb17dd1..a6671f8 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/seed-attribution-helper/test_policy_transport_runner.py @@ -1,5 +1,5 @@ """Pure closed-composition/source-routing controls; no policy/CLR/native execution.""" -import unittest,time,pathlib,tempfile,os,ast +import unittest,time,pathlib,tempfile,os,ast,json,hashlib from unittest.mock import patch,Mock import seed_policy as s import policy_transport_runner as r @@ -15,6 +15,21 @@ def test_outer_one_use_and_private_grant_before_effects(self): with patch.object(r,'profile')as profile,patch.object(r.subprocess,'Popen')as process: with self.assertRaises(m.Refused):r.execute(grant) profile.assert_not_called();process.assert_not_called() + def test_malformed_requires_retained_exact_exception_diagnostic(self): + with tempfile.TemporaryDirectory()as d: + root=pathlib.Path(d);(root/'evidence').mkdir();file=root/'evidence/policy-2.failure.stderr.raw' + encoded=b'{malformed';observation={'schema':'fsbar.barc-runtime-evidence-failure-observation/v1','checkpoint':'request-evaluation','kind':'exception'} + def retained(value): + raw=(json.dumps(value)+'\n').encode();file.write_bytes(raw);file.chmod(0o600);pin=m.physical(file);pin['fullWriteFsyncAcknowledged']=True + return {'requestWrittenBytes':len(encoded),'requestSha256':hashlib.sha256(encoded).hexdigest(),'stderr':pin} + with patch.object(r,'ROOT',d): + settlement={'exitCode':2,'forced':False};self.assertEqual(r.require_malformed_refusal(retained(observation),settlement,encoded,2),observation) + for value in [{**observation,'checkpoint':'final-closure'},{**observation,'kind':'refused'},{**observation,'extra':'forged'},{'unknown':True}]: + with self.subTest(value=value),self.assertRaises(m.Refused):r.require_malformed_refusal(retained(value),settlement,encoded,2) + for code,force in [(0,False),(2,True),(-9,True)]: + with self.subTest(code=code,force=force),self.assertRaises(m.Refused):r.require_malformed_refusal(retained(observation),{'exitCode':code,'forced':force},encoded,2) + row=retained(observation);file.write_bytes(b'changed') + with self.assertRaises(m.Refused):r.require_malformed_refusal(row,settlement,encoded,2) def test_closed_binding(self): with patch.object(s,'_validate')as validation: for kind in [s.SeedPolicyContext,s.PolicyOnlyContext]: