diff --git a/docs/roadmaps/barc-01-useful-play.md b/docs/roadmaps/barc-01-useful-play.md index 4ab1f3a..39132d0 100644 --- a/docs/roadmaps/barc-01-useful-play.md +++ b/docs/roadmaps/barc-01-useful-play.md @@ -258,3 +258,36 @@ Protected source delivery and the fresh packet's independent source/build, configuration, current-process and actually loaded engine/plugin joins remain required. RP3 actual capacity, RP4 custody, RP5 bounded Count1 and useful-play journeys remain open at **0/6**. + +## RP4 bounded stock attempt and complete map census repair — 2026-10-03 + +The independently reviewed protected `a7eb52a` packet received one prospective +Count1 grant. Its sole attempt refused before browser launch or Count1 in +17.234 seconds. The actual browser admission consumed 32 completed probes and +one compiled-policy evaluation in 1,127,818 microseconds: its 159,096-byte log +retained a stable 139-byte incomplete tail, so the shared probe cap exhausted +before the five-second deadline. All owned host, engine and receiver processes +settled; the complete 24,544-file packet and its immutable settings seed remained +unchanged. The grant is consumed and the original attempt is preserved. + +The selected unmodified Recoil 2026.07.04 source writes newline-terminated records +through a buffered file sink whose default flush threshold is ERROR. The observed +console record continued beyond the file's incomplete tail. Three real libc FILE +controls distinguish that buffered-prefix exposure from the official +`LogFlushLevel = 0` configuration, which flushes every emitted record. A new +immutable settings seed with that setting is prepared separately; neither a +stable partial record nor a longer polling interval grants admission. The +32-probe, three-evaluation, five-second, byte and exact-read limits stay fixed. + +Preparing positive loaded-custody receipts exposed a separate source defect: +`proc_bytes` performed one unbuffered read, which may return before EOF on +`/proc//maps`. The repair reads bounded chunks through EOF and still refuses +maximum-plus-one bytes. Five focused controls cover real complete process maps, +short reads, exact-bound acceptance, over-bound refusal, empty input and invalid +bounds. A private successor helper must persist the positive actual engine and +plugin mapping census before browser effect; its actual-process controls are +preparation evidence only. The source repair preserves the protected B1 adapter +and receiver dependency correction. Protected delivery, a new fully reviewed +source/build/configuration packet, actual loaded custody, actual successor +capacity and a distinct bounded native grant remain required. RP3/RP4/RP5 and +all useful-play journeys remain open at **0/6**. diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/runtime_identity.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/runtime_identity.py index 2e75674..37dec98 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/runtime_identity.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/runtime_identity.py @@ -17,9 +17,14 @@ def mark_failure(error,check,outcome=None,policy_observation=None): def proc_bytes(pid,name,maximum): # The only proc files read are nonsecret stat/maps/fdinfo, never environ/cmdline. + need(type(maximum) is int and maximum>0,'proc positive byte bound') + chunks=[];length=0 with open(f'/proc/{pid}/{name}','rb',buffering=0) as stream: - value=stream.read(maximum+1) - need(len(value)<=maximum,'proc byte bound');return value + 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] diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/test_proc_bytes.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/test_proc_bytes.py new file mode 100644 index 0000000..86e07f9 --- /dev/null +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/test_proc_bytes.py @@ -0,0 +1,26 @@ +import io,os,pathlib,unittest +from unittest.mock import patch +from runtime_identity import proc_bytes +from private_io import Refused + +class Chunked(io.BytesIO): + def read(self,n=-1):return super().read(min(n,7)) + +class ProcBytes(unittest.TestCase): + def test_actual_proc_maps_reads_to_eof(self): + expected=pathlib.Path('/proc/self/maps').read_bytes();actual=proc_bytes(os.getpid(),'maps',4*1024*1024) + # Address mappings are read twice; assert full stable path census, not addresses. + paths=lambda b:{l.split(None,5)[5] for l in b.splitlines() if len(l.split(None,5))==6} + self.assertEqual(paths(actual),paths(expected));self.assertIn(b'libc.so.',actual);self.assertGreater(len(actual),4096) + def test_short_reads_continue_to_eof(self): + data=b'x'*100 + with patch('builtins.open',return_value=Chunked(data)):self.assertEqual(proc_bytes(os.getpid(),'maps',100),data) + def test_maximum_plus_one_refused_after_short_reads(self): + with patch('builtins.open',return_value=Chunked(b'x'*101)): + with self.assertRaises(Refused):proc_bytes(os.getpid(),'maps',100) + def test_empty_proc_file(self): + with patch('builtins.open',return_value=Chunked(b'')):self.assertEqual(proc_bytes(os.getpid(),'maps',100),b'') + def test_nonpositive_bound_refused(self): + with self.assertRaises(Refused):proc_bytes(os.getpid(),'maps',0) + +if __name__=='__main__':unittest.main()