diff --git a/docs/roadmaps/barc-01-useful-play.md b/docs/roadmaps/barc-01-useful-play.md index d503b6e..b55d543 100644 --- a/docs/roadmaps/barc-01-useful-play.md +++ b/docs/roadmaps/barc-01-useful-play.md @@ -55,6 +55,30 @@ not amend the engine or installed failed packet. RP3 capacity, RP4 executable an engine/plugin custody, RP5 one changed Count1 attempt, and useful play **0/6** remain open under the linked qualification plan. +## Initial closure diagnostic source candidate — 2026-10-03 + +The single admitted owned-damage Count1 attempt refused after handoff and before +browser startup. Its retained policy observation is `initial-closure:exception`; +the exact failed closure check remains unknown. Actual engine/plugin mapping +custody and settled owned cleanup establish their separate boundaries, while the +selected smoke remains unaccepted and useful play remains **0/6**. + +The source candidate adds a bounded initial-closure diagnostic with closed +subcheckpoint and code enums, including a distinct unexpected-exception code. +It retains the original exception and every closure/custody/digest check. The +capture parser and failure projection accept the new initial frame and retain +legacy frames; noninitial diagnostics keep their existing bytes. No raw exception +message, path, stack, credential or envelope is published. The local locked +Release build and complete source gate pass, including genuine missing/hash/JSON/ +custody/argv failures, closed parser/projection controls, 38 Quint scenarios, +500 invariant samples, complete-record correspondence and 13 rejected mutants. +The initial local-clone build refused at the fixture provenance join; explicit +exact-commit SourceLink supplied the required source proof for local verification. +Hosted qualification at the canonical repository and protected delivery remain +pending. Changed policy bytes require a reviewed +protected build successor and a distinct future operation grant; the consumed +attempt and sealed candidate remain immutable. + ## Current frontier — 2026-10-01 Selected product profile: unmodified Recoil2025.06.19 plus the ABI-matched HighBar plugin and bounded synced Lua queue observer. Delivered default source heads are HighBar `f08555372168cd911f438de0be5ec2898fd1cfb5` and FSBar `6b9139e83334da903ea6861adb57d97578af2239`; neither source merge establishes installation or gameplay acceptance. The existing custom FR1–FR6 experiment remains optional, inactive and open. diff --git a/tests/Broker.NativeProof/RuntimeEvidence/CodecTests.fs b/tests/Broker.NativeProof/RuntimeEvidence/CodecTests.fs index 6974fa8..00950b1 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/CodecTests.fs +++ b/tests/Broker.NativeProof/RuntimeEvidence/CodecTests.fs @@ -80,6 +80,19 @@ module CodecTests = let diagnostic=$"{{\"schema\":\"fsbar.barc-stock-failure-projection/v1\",\"expected\":{{\"configSha256\":\"{String('d',64)}\",\"sourceSetSha256\":\"{String('a',64)}\",\"policySha256\":\"{String('b',64)}\",\"closureSha256\":\"{String('c',64)}\"}},\"observation\":{observation},\"observationSha256\":\"{observationHash}\",\"operationResultBase64\":\"{operationBase64}\",\"operationResultSha256\":\"{operationHash}\"}}" let projected=evaluate diagnostic|>Codec.complete if not(projected.Contains("\"status\":\"observed-failure\"")) || not(projected.Contains("\"nativeAcceptance\":false")) || projected.Contains("PRIVATE_SENTINEL") then failwith "valid failure diagnostic projection refused" + let initialObservation detail = + observation.Replace("\"check\":\"policy-result-join\"","\"check\":\"policy-closure-precheck\"").Replace("\"outcome\":\"policy-nonaccepted\"","\"outcome\":\"refused\"").Replace(policyObservation,detail) + let initialDiagnostic detail = + let value=initialObservation detail + diagnostic.Replace(observation,value).Replace(observationHash,sha(bytes(value+"\n"))) + for stage,code in ["manifest-read","refused";"manifest-hash","refused";"manifest-json","malformed";"directory-custody","refused";"manifest-hash","unexpected-exception"] do + let detail=FailureDiagnostic.initialClosureObservation stage code + let actual=evaluate(initialDiagnostic detail)|>Codec.complete + if not(actual.Contains("\"status\":\"observed-failure\"") && actual.Contains("\"subcheckpoint\":\""+stage+"\"") && actual.Contains("\"code\":\""+code+"\"")) then failwith "closed initial diagnostic lost in projection" + let initialDetail=FailureDiagnostic.initialClosureObservation "manifest-hash" "refused" + for invalid in [initialDetail.Replace("manifest-hash","PRIVATE_SENTINEL");initialDetail.Replace("\"code\":\"refused\"","\"code\":\"PRIVATE_SENTINEL\"");initialDetail.Replace("initial-closure","current-process");initialDetail.Replace("\"kind\":\"exception\"","\"kind\":\"refused\"");initialDetail.Replace("{","{\"path\":\"PRIVATE_SENTINEL\",",StringComparison.Ordinal)] do + let actual=evaluate(initialDiagnostic invalid)|>Codec.complete + if not(actual.Contains("\"status\":\"diagnostic-unavailable\"")) || actual.Contains("PRIVATE_SENTINEL") then failwith "initial detail escaped closed projection" let invalidDiagnostics = [ diagnostic.Replace("\"sourceSetSha256\":\""+String('a',64),"\"sourceSetSha256\":\""+String('f',64)) diagnostic.Replace(observationHash,String('0',64)) diff --git a/tests/Broker.NativeProof/RuntimeEvidence/FailureDiagnostic.fs b/tests/Broker.NativeProof/RuntimeEvidence/FailureDiagnostic.fs index fdf6e43..a1d1f9c 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/FailureDiagnostic.fs +++ b/tests/Broker.NativeProof/RuntimeEvidence/FailureDiagnostic.fs @@ -38,6 +38,15 @@ module FailureDiagnostic = let outcomes = Set [ "refused"; "os-unavailable"; "deadline"; "malformed"; "unexpected"; "policy-nonaccepted" ] let policyCheckpoints = Set [ "initial-closure"; "current-process"; "invocation-ready"; "request-read"; "request-evaluation"; "final-closure"; "invocation-completion" ] let policyKinds = Set [ "exception"; "pending"; "refused"; "unknown" ] + [] + let InitialClosureObservationSchema = "fsbar.barc-runtime-evidence-failure-observation/v2" + let initialClosureStages = + Set [ "argv"; "invocation"; "manifest-path"; "manifest-read"; "manifest-hash" + "manifest-json"; "manifest-schema"; "closure-roots"; "inventory" + "managed-census"; "provenance-census"; "runtime-census"; "search-layout" + "custody"; "runtime-roles"; "directory-custody"; "managed-custody" + "runtime-custody"; "provenance-join" ] + let initialClosureCodes = Set [ "refused"; "malformed"; "unavailable"; "unexpected-exception" ] let private shaPattern = Regex("\\A[0-9a-f]{64}\\z",RegexOptions.CultureInvariant) let private refuse () = raise(InvalidOperationException("diagnostic unavailable")) @@ -64,6 +73,18 @@ module FailureDiagnostic = node["kind"]<-JsonValue.Create kind node.ToJsonString(JsonSerializerOptions(WriteIndented=false)) + // These two strings come only from closed source stages and exception categories. + // Exception messages, stack traces and paths never enter the observation. + let initialClosureObservation (stage:string) (code:string) = + require(initialClosureStages.Contains stage && initialClosureCodes.Contains code) + let node=JsonObject() + node["schema"]<-JsonValue.Create InitialClosureObservationSchema + node["checkpoint"]<-JsonValue.Create "initial-closure" + node["kind"]<-JsonValue.Create "exception" + node["subcheckpoint"]<-JsonValue.Create stage + node["code"]<-JsonValue.Create code + node.ToJsonString(JsonSerializerOptions(WriteIndented=false)) + let private compatible (check:string) (checkpoint:string) = match check,checkpoint with | "policy-closure-precheck","initial-closure" @@ -122,10 +143,15 @@ module FailureDiagnostic = let policyNode,policyKind = if policyElement.ValueKind=JsonValueKind.Null then null,None else - exact policyElement ["schema";"checkpoint";"kind"] - require(text "schema" policyElement=PolicyObservationSchema) + let schema=text "schema" policyElement let checkpoint=text "checkpoint" policyElement let kind=text "kind" policyElement + if schema=PolicyObservationSchema then + exact policyElement ["schema";"checkpoint";"kind"] + else + exact policyElement ["schema";"checkpoint";"kind";"subcheckpoint";"code"] + require(schema=InitialClosureObservationSchema && checkpoint="initial-closure" && kind="exception") + require(initialClosureStages.Contains(text "subcheckpoint" policyElement) && initialClosureCodes.Contains(text "code" policyElement)) require(policyCheckpoints.Contains checkpoint && policyKinds.Contains kind && compatible check checkpoint) JsonNode.Parse(policyElement.GetRawText()),Some kind let observationHash=digest "observationSha256" root diff --git a/tests/Broker.NativeProof/RuntimeEvidence/PolicyClosure.fs b/tests/Broker.NativeProof/RuntimeEvidence/PolicyClosure.fs index eef4113..a30eb8a 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/PolicyClosure.fs +++ b/tests/Broker.NativeProof/RuntimeEvidence/PolicyClosure.fs @@ -63,7 +63,17 @@ module PolicyClosure = RuntimeRoots:Set;RequiredSearchLayout:Set;SourceIdentity:string } - let private fail message = invalidOp message + let private refusalKey = "fsbar.closure-guard-refusal" + let private fail message = + let error=InvalidOperationException(message) + error.Data[refusalKey] <- true + raise error + let private diagnosticKey = "fsbar.initial-closure-detail" + let initialFailureDetail (error:exn) = + match error.Data[diagnosticKey] with + | :? (string * string) as detail -> detail + | _ -> "invocation","unexpected-exception" + let private hashBytes bytes = SHA256.HashData(bytes:byte[]) |> Convert.ToHexStringLower let private readBounded path maximum = use stream=File.Open(path,FileMode.Open,FileAccess.Read,FileShare.Read) @@ -257,24 +267,32 @@ module PolicyClosure = for role in outputs.EnumerateObject() do if role.Value.GetString()<>managedRoles[role.Name].Sha256 then fail "build output receipt drift" policyCommit+":"+policyTree+":"+productCommit+":"+productTree+":"+helperHead+":"+helperTree+":"+roles["lockedBuildInputs"].Sha256+":"+roles["toolchainReceipt"].Sha256,productSourceSet - let loadAndVerify manifestPath expectedSha invocationId = + let private loadAndVerifyCore stage manifestPath expectedSha invocationId = + stage "invocation" if String.IsNullOrWhiteSpace invocationId || invocationId.Length>128 then fail "invalid invocation id" + stage "manifest-path" let path=full manifestPath "manifest" + stage "manifest-read" let info=FileInfo path if not info.Exists || info.Length<=0L || info.Length>1024L*1024L then fail "manifest unavailable/bound" let raw=readBounded path (1024*1024) + stage "manifest-hash" let digest=hashBytes raw if digest<>hex 64 expectedSha "manifest digest" then fail "manifest digest drift" + stage "manifest-json" use document=JsonDocument.Parse(raw,JsonDocumentOptions(MaxDepth=32,CommentHandling=JsonCommentHandling.Disallow,AllowTrailingCommas=false)) let root=document.RootElement + stage "manifest-schema" exact root ["schema";"custodyRoot";"managedRoot";"provenanceRoot";"runtimeRoots";"searchLayout";"managed";"provenance";"runtime";"runtimeRoles";"source";"custody"] "policy closure" if text root "schema"<>Schema then fail "policy closure schema" + stage "closure-roots" let custodyRoot=full(text root "custodyRoot") "custody root" let managedRoot=full(text root "managedRoot") "managed root" let provenanceRoot=full(text root "provenanceRoot") "provenance root" if managedRoot=provenanceRoot || not(managedRoot.StartsWith(custodyRoot+string Path.DirectorySeparatorChar)) || not(provenanceRoot.StartsWith(custodyRoot+string Path.DirectorySeparatorChar)) then fail "closure roots" let runtimeRoots=root.GetProperty("runtimeRoots").EnumerateArray() |> Seq.map(fun e->full(e.GetString()) "runtime root") |> Seq.toList if runtimeRoots.IsEmpty || runtimeRoots.Length>16 || (Set.ofList runtimeRoots).Count<>runtimeRoots.Length then fail "runtime roots" + stage "inventory" let managed=pins root "managed" true let provenance=pins root "provenance" true let runtime=pins root "runtime" false @@ -282,13 +300,17 @@ module PolicyClosure = if all.Length>MaxFiles || all|>List.sumBy _.Bytes>MaxTotalBytes || (all|>List.map _.Path|>Set.ofList).Count<>all.Length then fail "closure aggregate/duplicate bound" let managedRoles=roleMap managed if managed.Length<>5 || Set.ofSeq managedRoles.Keys<>Set.ofList["apphost";"managedDll";"depsJson";"runtimeConfigJson";"fsharpCore"] then fail "closed managed roles" + stage "managed-census" let managedCensus=enumerateBounded [managedRoot] if managedCensus<>(managed|>List.map _.Path|>Set.ofList) then fail "managed census drift" + stage "provenance-census" let provenanceCensus=enumerateBounded [provenanceRoot] if provenanceCensus<>(provenance|>List.map _.Path|>Set.ofList) then fail "provenance census drift" + stage "runtime-census" let runtimeCensus=enumerateBounded runtimeRoots let runtimeSet=runtime|>List.map _.Path|>Set.ofList if not(Set.isSubset runtimeCensus runtimeSet) then fail "runtime directory census drift" + stage "search-layout" let searchRows=root.GetProperty "searchLayout" if searchRows.ValueKind<>JsonValueKind.Array then fail "search layout" let searchBuffer=ResizeArray() @@ -298,6 +320,7 @@ module PolicyClosure = let search=List.ofSeq searchBuffer if search.IsEmpty || (search|>List.map _.Path|>Set.ofList).Count<>search.Length then fail "search layout" let searchMap=search|>List.map(fun d->d.Path,d)|>Map.ofList + stage "custody" let custody=root.GetProperty "custody" exact custody ["ownerUid";"executablePath";"fixedArgv"] "custody" let owner=custody.GetProperty("ownerUid").GetInt32() @@ -306,6 +329,7 @@ module PolicyClosure = let executable=full(text custody "executablePath") "executable" let argv=custody.GetProperty("fixedArgv").EnumerateArray()|>Seq.map _.GetString()|>Seq.toList if argv<>[executable;"--closure-manifest";"--closure-sha256";"--invocation-id"] || managedRoles["apphost"].Path<>executable then fail "fixed apphost argv" + stage "runtime-roles" let roles=root.GetProperty "runtimeRoles" exact roles ["hostfxr";"hostpolicy";"coreLib";"coreClr";"jit"] "runtime roles" let rolePaths=roles.EnumerateObject()|>Seq.map(fun p->p.Name,full(p.Value.GetString()) p.Name)|>Map.ofSeq @@ -326,14 +350,31 @@ module PolicyClosure = yield! runtime|>Seq.map(fun pin->Directory.GetParent(pin.Path).FullName) } |> Set.ofSeq for required in requiredSearch do if not(searchMap.ContainsKey required) then fail("unsealed runtime search directory: "+required) + stage "directory-custody" let custodyStat=stat custodyRoot verifyDirectory custodyRoot false { Path=custodyRoot;OwnerUid=owner;Mode=int custodyStat.Mode&&&0o777;EntriesSha256=directoryEntries custodyRoot } for pin in search do verifyDirectory (if pin.Path.StartsWith(custodyRoot) then custodyRoot else "/") (pin.Path=managedRoot || pin.Path=provenanceRoot) pin + stage "managed-custody" for pin in managed@provenance do verifyFile custodyRoot true pin + stage "runtime-custody" for pin in runtime do verifyFile "/" false pin + stage "provenance-join" let sourceIdentity,productSourceSet=provenanceJoin (root.GetProperty "source") managed provenance { ManifestPath=path;ManifestSha256=digest;ExecutablePath=executable;ApphostSha256=managedRoles["apphost"].Sha256;ProductSourceSetSha256=productSourceSet;OwnerUid=owner;Files=all|>List.map(fun p->p.Path,p)|>Map.ofList;ManagedRoles=managedRoles|>Map.map(fun _ pin->pin.Path);RuntimeRoles=rolePaths;RuntimeRoots=Set.ofList runtimeRoots;RequiredSearchLayout=requiredSearch;SourceIdentity=sourceIdentity } + let loadAndVerify manifestPath expectedSha invocationId = + let mutable stage="invocation" + try loadAndVerifyCore (fun value->stage<-value) manifestPath expectedSha invocationId + with error -> + let code = + match error with + | _ when error.Data[refusalKey] :? bool && unbox error.Data[refusalKey] -> "refused" + | :? JsonException -> "malformed" + | :? IOException | :? UnauthorizedAccessException -> "unavailable" + | _ -> "unexpected-exception" + error.Data[diagnosticKey] <- (stage,code) + reraise() + let verifyCurrentProcess (verified:Verified) = let exe=File.ResolveLinkTarget("/proc/self/exe",true).FullName|>Path.GetFullPath if exe<>verified.ExecutablePath then fail "running executable drift" diff --git a/tests/Broker.NativeProof/RuntimeEvidence/PolicyClosureTests.fs b/tests/Broker.NativeProof/RuntimeEvidence/PolicyClosureTests.fs index 4cdd873..8014e26 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/PolicyClosureTests.fs +++ b/tests/Broker.NativeProof/RuntimeEvidence/PolicyClosureTests.fs @@ -1,11 +1,48 @@ namespace FSBar.NativeProof.RuntimeEvidence.Tests open FSBar.NativeProof.RuntimeEvidence +open System +open System.IO +open System.Security.Cryptography +open System.Text module PolicyClosureTests = let private accepted = PolicyInvocation.requireAccepted let private refused = function InvocationRefused state when state.StickyInvalid -> state | other -> failwithf "expected sticky refusal: %A" other + let private initialClosureFailures() = + let privateMarker="PRIVATE_SENTINEL_credential_envelope" + let root=Path.Combine(Path.GetTempPath(),"bar-initial-closure-"+Guid.NewGuid().ToString("N")) + Directory.CreateDirectory root |> ignore + let hash bytes=SHA256.HashData(bytes:byte[]) |> Convert.ToHexStringLower + let check label path digest expectedStage expectedCode = + try + PolicyClosure.loadAndVerify path digest "invocation" |> ignore + failwith(label+" accepted") + with error -> + let stage,code=PolicyClosure.initialFailureDetail error + if (stage,code)<>(expectedStage,expectedCode) then failwith(label+" wrong closed detail") + let observation=FailureDiagnostic.initialClosureObservation stage code + if observation.Length>512 || observation.Contains root || observation.Contains privateMarker || observation.Contains error.Message then failwith(label+" leaked detail") + try + check "missing manifest" (Path.Combine(root,privateMarker)) (String.replicate 64 "a") "manifest-read" "refused" + let path=Path.Combine(root,"manifest.json") + File.WriteAllText(path,"{"+privateMarker) + check "hash drift" path (String.replicate 64 "a") "manifest-hash" "refused" + check "malformed JSON" path (hash(File.ReadAllBytes path)) "manifest-json" "malformed" + File.WriteAllText(path,"{}") + check "malformed schema" path (hash(File.ReadAllBytes path)) "manifest-schema" "refused" + check "unexpected null digest" path null "manifest-hash" "unexpected-exception" + for stage in FailureDiagnostic.initialClosureStages do + for code in FailureDiagnostic.initialClosureCodes do + if (FailureDiagnostic.initialClosureObservation stage code).Length>512 then failwith "diagnostic byte bound" + try + FailureDiagnostic.initialClosureObservation privateMarker "refused" |> ignore + failwith "unclosed diagnostic stage accepted" + with :? InvalidOperationException -> () + finally + Directory.Delete(root,true) let run() = + initialClosureFailures() let identity={InvocationId="invocation";ClosureSha256=String.replicate 64 "a";Pid=100;StartTicks="1";Uid=1000} let started=PolicyInvocation.beginInvocation identity PolicyInvocation.empty|>accepted let ready=PolicyInvocation.ready identity started|>accepted diff --git a/tests/Broker.NativeProof/RuntimeEvidence/Program.fs b/tests/Broker.NativeProof/RuntimeEvidence/Program.fs index f476c6f..0f50bb7 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/Program.fs +++ b/tests/Broker.NativeProof/RuntimeEvidence/Program.fs @@ -13,11 +13,13 @@ module Program = [] let main argv = let mutable checkpoint="initial-closure" + let mutable initialStage="argv" try if argv.Length<>6 then invalidOp "closed policy argv" let invocation=argument "--invocation-id" argv let manifestPath=argument "--closure-manifest" argv let closureSha=argument "--closure-sha256" argv + initialStage<-"invocation" let closure=PolicyClosure.loadAndVerify manifestPath closureSha invocation checkpoint<-"current-process" PolicyClosure.verifyCurrentProcess closure @@ -55,6 +57,14 @@ module Program = | Some(stage,kind) -> Console.Error.WriteLine(FailureDiagnostic.policyObservation stage kind);Console.Error.Flush() | None -> () 0 - with _ -> - Console.Error.WriteLine(FailureDiagnostic.policyObservation checkpoint "exception") + with error -> + let observation = + if checkpoint<>"initial-closure" then FailureDiagnostic.policyObservation checkpoint "exception" + elif initialStage="argv" then + let code=if error :? InvalidOperationException then "refused" else "unexpected-exception" + FailureDiagnostic.initialClosureObservation "argv" code + else + let stage,code=PolicyClosure.initialFailureDetail error + FailureDiagnostic.initialClosureObservation stage code + Console.Error.WriteLine observation 2 diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/growing_log.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/growing_log.py index 84d54b3..534948a 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/growing_log.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/growing_log.py @@ -15,6 +15,9 @@ POLICY_OBSERVATION_SCHEMA='fsbar.barc-runtime-evidence-failure-observation/v1' POLICY_CHECKPOINTS={'initial-closure','current-process','invocation-ready','request-read','request-evaluation','final-closure','invocation-completion'} POLICY_KINDS={'exception','pending','refused','unknown'} +INITIAL_CLOSURE_OBSERVATION_SCHEMA='fsbar.barc-runtime-evidence-failure-observation/v2' +INITIAL_CLOSURE_STAGES={'argv','invocation','manifest-path','manifest-read','manifest-hash','manifest-json','manifest-schema','closure-roots','inventory','managed-census','provenance-census','runtime-census','search-layout','custody','runtime-roles','directory-custody','managed-custody','runtime-custody','provenance-join'} +INITIAL_CLOSURE_CODES={'refused','malformed','unavailable','unexpected-exception'} POLICY_TO_MECHANICAL={'initial-closure':'policy-closure-precheck','current-process':'policy-artifact','invocation-ready':'policy-ready','request-read':'policy-transport','request-evaluation':'policy-result-join','final-closure':'policy-final-precheck','invocation-completion':'policy-completion'} def _unique(pairs): @@ -31,7 +34,11 @@ def _policy_observation(raw): need(isinstance(raw,bytes) and len(raw)<=2048,'policy diagnostic bound') lines=raw.splitlines();need(len(lines)==1,'single policy diagnostic frame') value=json.loads(lines[0].decode('utf-8'),object_pairs_hook=_unique) - need(isinstance(value,dict) and set(value)=={'schema','checkpoint','kind'} and value['schema']==POLICY_OBSERVATION_SCHEMA and value['checkpoint'] in POLICY_CHECKPOINTS and value['kind'] in POLICY_KINDS,'closed policy diagnostic frame') + need(isinstance(value,dict),'closed policy diagnostic frame') + if value.get('schema')==POLICY_OBSERVATION_SCHEMA: + need(set(value)=={'schema','checkpoint','kind'} and value['checkpoint'] in POLICY_CHECKPOINTS and value['kind'] in POLICY_KINDS,'closed policy diagnostic frame') + else: + need(set(value)=={'schema','checkpoint','kind','subcheckpoint','code'} and value['schema']==INITIAL_CLOSURE_OBSERVATION_SCHEMA and value['checkpoint']=='initial-closure' and value['kind']=='exception' and value['subcheckpoint'] in INITIAL_CLOSURE_STAGES and value['code'] in INITIAL_CLOSURE_CODES,'closed initial closure diagnostic frame') return value except Exception:return None diff --git a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/test_growing_log.py b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/test_growing_log.py index ed3d265..7c2e39e 100644 --- a/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/test_growing_log.py +++ b/tests/Broker.NativeProof/RuntimeEvidence/fixtures/complete-record-helper/test_growing_log.py @@ -488,6 +488,42 @@ def stream(raw): finally:omitted_path.unlink(missing_ok=True) print(json.dumps(diagnostic,separators=(',',':'),sort_keys=True),flush=True) self.assertIn(diagnostic['policyDisposition'],('ready-completed','ready-request-refused')) + def test_initial_closure_closed_detail_failures(self): + def observe(path,digest,stage,code): + runner=object.__new__(GrowingLog);runner.last_policy_observation=None + with self.assertRaises(Refused) as caught: + runner._run_policy(str(self.policy),str(path),digest,b'{}',time.monotonic()+5) + fact=caught.exception._barc_failure_observation + self.assertEqual(fact['check'],'policy-closure-precheck') + self.assertEqual(fact['policyObservation'],{'schema':growing_log.INITIAL_CLOSURE_OBSERVATION_SCHEMA,'checkpoint':'initial-closure','kind':'exception','subcheckpoint':stage,'code':code}) + self.assertNotIn('PRIVATE_SENTINEL',json.dumps(fact));self.assertNotIn(str(self.fixture.name),json.dumps(fact)) + missing=pathlib.Path(self.fixture.name)/'PRIVATE_SENTINEL_missing' + observe(missing,'a'*64,'manifest-read','refused') + observe(self.closure,'a'*64,'manifest-hash','refused') + malformed=pathlib.Path(self.fixture.name)/'malformed.json' + malformed.write_bytes(b'{PRIVATE_SENTINEL') + try:observe(malformed,hashlib.sha256(malformed.read_bytes()).hexdigest(),'manifest-json','malformed') + finally:malformed.unlink() + # Mutate a real sealed managed directory only for this fixture, then restore it. + managed=self.policy.parent;old_mode=stat.S_IMODE(managed.stat().st_mode) + try: + os.chmod(managed,0o755) + observe(self.closure,self.closure_sha,'directory-custody','refused') + finally:os.chmod(managed,old_mode) + process=subprocess.run([str(self.policy),'PRIVATE_SENTINEL'],stdout=subprocess.PIPE,stderr=subprocess.PIPE,env={'PATH':'/usr/bin:/bin'}) + self.assertEqual(process.returncode,2);self.assertEqual(process.stdout,b'') + detail=growing_log._policy_observation(process.stderr) + self.assertEqual((detail['subcheckpoint'],detail['code']),('argv','refused')) + self.assertNotIn(b'PRIVATE_SENTINEL',process.stderr) + def test_initial_closure_parser_is_closed_and_preserves_legacy(self): + legacy={'schema':growing_log.POLICY_OBSERVATION_SCHEMA,'checkpoint':'initial-closure','kind':'exception'} + self.assertEqual(growing_log._policy_observation(json.dumps(legacy).encode()),legacy) + good={'schema':growing_log.INITIAL_CLOSURE_OBSERVATION_SCHEMA,'checkpoint':'initial-closure','kind':'exception','subcheckpoint':'manifest-read','code':'refused'} + self.assertEqual(growing_log._policy_observation(json.dumps(good).encode()),good) + for mutation in [dict(good,subcheckpoint='PRIVATE_SENTINEL'),dict(good,code='PRIVATE_SENTINEL'),dict(good,checkpoint='current-process'),dict(good,kind='refused'),dict(good,path='PRIVATE_SENTINEL'),dict(good,schema=growing_log.POLICY_OBSERVATION_SCHEMA)]: + self.assertIsNone(growing_log._policy_observation(json.dumps(mutation).encode())) + self.assertIsNone(growing_log._policy_observation(json.dumps(good).encode()+b'\n{}')) + self.assertIsNone(growing_log._policy_observation(b'X'*2049)) @staticmethod def state_projection(value): if value is None:return None diff --git a/tests/Broker.NativeProof/RuntimeEvidence/tests/check-runtime-evidence.sh b/tests/Broker.NativeProof/RuntimeEvidence/tests/check-runtime-evidence.sh index 47aada2..4711ef2 100755 --- a/tests/Broker.NativeProof/RuntimeEvidence/tests/check-runtime-evidence.sh +++ b/tests/Broker.NativeProof/RuntimeEvidence/tests/check-runtime-evidence.sh @@ -37,7 +37,10 @@ test -f "$BAR_GROWING_LOG_HELPER/growing_log.py" (cd "$BAR_GROWING_LOG_HELPER" && \ BAR_RUNTIME_EVIDENCE_BUILD="$ROOT/bin/RuntimeEvidence/Release/net10.0" \ BAR_RUNTIME_EVIDENCE_SOURCE="$(cd "$ROOT/../../.." && pwd)" \ - python3 -m unittest -v test_growing_log.GrowingLogTests.test_staged_runtime_policy_readiness_preflight) + python3 -m unittest -v \ + test_growing_log.GrowingLogTests.test_staged_runtime_policy_readiness_preflight \ + test_growing_log.GrowingLogTests.test_initial_closure_closed_detail_failures \ + test_growing_log.GrowingLogTests.test_initial_closure_parser_is_closed_and_preserves_legacy) (cd "$BAR_GROWING_LOG_HELPER" && python3 -m unittest -v test_buffered_writer) (cd "$BAR_GROWING_LOG_HELPER" && \ BAR_RUNTIME_EVIDENCE_BUILD="$ROOT/bin/RuntimeEvidence/Release/net10.0" \