@@ -111,11 +111,15 @@ def test_late_discovery_ranges_are_complete_and_gaps_are_not_offered(tmp_path):
111111 parallel .preferred_sources (stage , tmp_path , "new-feature" , ["giant.ts" ], 100000 )
112112
113113
114- @pytest .mark .parametrize ("damage" , ["judge" , "generator" , "owner" , "facet" , "offered" , "section" ])
115- def test_native_checkpoint_replay_exact_approval_and_tamper_rejected (world , damage ):
114+ def _complete_native (world , * , publish = False ):
116115 seed_gateway = KnowledgeGateway ()
117116 _chain (world , seed_gateway )
118- state = _runtime (world ).state_dir
117+ if publish :
118+ from test_kb_init_skeleton import _commit
119+ skeleton = InitRecord .load (_runtime (world ).state_dir , "toy" , "skeleton" )
120+ modules = InitRecord .load (_runtime (world ).state_dir , "toy" , "modules" )
121+ _commit (world ["origin" ], {** _tree (skeleton ), ** _tree (modules )}, "merge prerequisite pages" )
122+ state = _runtime (world ).state_dir if not publish else world ["tmp" ] / "publish-state"
119123 traces = TraceStore (state / "init" / "traces" )
120124 offline = KnowledgeGateway ()
121125
@@ -135,10 +139,24 @@ def complete(self, *, system, messages, model, effort, role, **kwargs):
135139 return Reply (blocks = [Block (type = "text" , text = text )], model = model )
136140
137141 gateway = ModelGateway (None , transport_factory = lambda _ : NativeTransport (), recorder = trace_recorder (traces ))
138- rt = _runtime (world , gateway , generator = ModelRole ("generator" , "zcode" , "GLM-5.3" ))
142+ rt = _runtime (world , gateway , state_dir = state , generator = ModelRole ("generator" , "zcode" , "GLM-5.3" ))
143+ if publish :
144+ from test_kb_init_skeleton import FakeGh
145+ rt .gh_run = FakeGh (fail_create = 1 )
139146 rt .environ = {** rt .environ , "KB_KNOWLEDGE_CONCURRENCY" : "2" , "KB_KNOWLEDGE_START_INTERVAL_S" : "0.001" }
140- record = run_stage (rt , _modules_lifecycle (), "knowledge" , dry_run = True , unlimited_subscription = True )
141- assert record .status == "dry_run" , record .problems
147+ if publish :
148+ rt .environ .update (ALLOW_PUSH = "1" , ALLOW_POST = "1" , KB_INIT_GIT_AUTHOR = "Tester <tester@example.com>" )
149+ record = run_stage (rt , _modules_lifecycle (), "knowledge" , dry_run = not publish ,
150+ from_existing = publish , unlimited_subscription = True )
151+ assert record .status == ("blocked" if publish else "dry_run" ), record .problems
152+ assert not record .unfinished
153+ return rt , record , offline , traces
154+
155+
156+
157+ @pytest .mark .parametrize ("damage" , ["judge" , "generator" , "owner" , "facet" , "offered" , "section" ])
158+ def test_native_checkpoint_replay_exact_approval_and_tamper_rejected (world , damage ):
159+ rt , record , offline , traces = _complete_native (world )
142160 artifacts = [a for task in record .coverage ["foundation_jobs" ]["tasks" ].values () for a in task ["artifacts" ]]
143161 assert len (artifacts ) == 12 and all (a ["judge_receipt" ]["native_trace_id" ] for a in artifacts )
144162 first_tree = _tree (record )
@@ -261,3 +279,70 @@ def test_shared_page_assembly_preserves_existing_sections_and_both_owners():
261279 assert historical in stage .head [page ]
262280 assert stage .head [page ].index ("owner=left" ) < stage .head [page ].index ("owner=right" )
263281 assert len (stage .record .evidence ) == 2
282+
283+
284+ @pytest .mark .parametrize ("damage" , ["missing_generator" , "missing_judge" , "tampered_generator" , "tampered_judge" , "binding" , "config" , "role" ])
285+ def test_completed_cache_cannot_skip_proof_replay_or_overwrite_on_failure (world , damage ):
286+ rt , record , offline , traces = _complete_native (world )
287+ assert record .status == "dry_run" and record .unfinished == []
288+ artifact = next (iter (record .coverage ["foundation_jobs" ]["tasks" ].values ()))["artifacts" ][0 ]
289+ if damage .startswith ("missing_" ) or damage .startswith ("tampered_" ):
290+ role = damage .rsplit ("_" , 1 )[1 ]
291+ native = traces .get (artifact [role + "_receipt" ]["native_trace_id" ])
292+ attempt = traces .root / "attempts" / native ["result" ]["native_attempt_id" ] / "attempt.json"
293+ if damage .startswith ("missing_" ):
294+ attempt .unlink ()
295+ else :
296+ saved = json .loads (attempt .read_text ())
297+ saved ["status" ] = "interrupted"
298+ attempt .write_text (json .dumps (saved ))
299+ elif damage == "binding" :
300+ record .coverage ["foundation_jobs" ]["binding" ] = "different-inputs"
301+ record .save (rt .state_dir )
302+ elif damage == "config" :
303+ rt .gateway ._settings = SimpleNamespace (zcode_reasoning_level = "low" )
304+ else :
305+ rt .judge = ModelRole ("judge" , "codex" , "different-model" , "medium" )
306+ path = InitRecord .path (rt .state_dir , "toy" , "knowledge" )
307+ before , calls = path .read_bytes (), len (offline .calls )
308+ with pytest .raises (InitError , match = "foundation" ):
309+ run_stage (rt , _modules_lifecycle (), "knowledge" , dry_run = True , unlimited_subscription = True )
310+ assert path .read_bytes () == before and len (offline .calls ) == calls
311+
312+
313+ @pytest .mark .parametrize ("damage" , ["missing_generator" , "missing_judge" , "digest" , "role" , "config" ])
314+ def test_prepared_publication_checks_original_proofs_before_resume (world , damage ):
315+ rt , record , offline , traces = _complete_native (world , publish = True )
316+ assert record .pr ["prepared" ] and record .status == "blocked" and not record .unfinished
317+ artifact = next (iter (record .coverage ["foundation_jobs" ]["tasks" ].values ()))["artifacts" ][0 ]
318+ restore = None
319+ if damage .startswith ("missing_" ):
320+ role = damage .rsplit ("_" , 1 )[1 ]
321+ native = traces .get (artifact [role + "_receipt" ]["native_trace_id" ])
322+ attempt = traces .root / "attempts" / native ["result" ]["native_attempt_id" ] / "attempt.json"
323+ restore = attempt , attempt .read_bytes ()
324+ attempt .unlink ()
325+ elif damage == "digest" :
326+ record .inputs_digest = "wrong-inputs"
327+ record .save (rt .state_dir )
328+ elif damage == "role" :
329+ rt .judge = ModelRole ("judge" , "codex" , "different-model" , "medium" )
330+ else :
331+ rt .gateway ._settings = SimpleNamespace (zcode_reasoning_level = "low" )
332+ path = InitRecord .path (rt .state_dir , "toy" , "knowledge" )
333+ before , calls , pushes = path .read_bytes (), len (offline .calls ), len (rt .gh_run .pushed )
334+ with pytest .raises (InitError , match = "foundation" ):
335+ run_stage (rt , _modules_lifecycle (), "knowledge" , dry_run = False , from_existing = True , unlimited_subscription = True )
336+ assert path .read_bytes () == before and len (offline .calls ) == calls and len (rt .gh_run .pushed ) == pushes
337+ if restore :
338+ restore [0 ].write_bytes (restore [1 ])
339+ resumed = run_stage (rt , _modules_lifecycle (), "knowledge" , dry_run = False , from_existing = True , unlimited_subscription = True )
340+ assert resumed .status == "published" and len (offline .calls ) == calls
341+ # A previously published record remains immutable historical output,
342+ # including retained limitations; archive loss cannot trigger a rerun.
343+ resumed .unfinished .append ("retained historical scan limitation" )
344+ resumed .save (rt .state_dir )
345+ restore [0 ].unlink ()
346+ published_bytes = path .read_bytes ()
347+ cached = run_stage (rt , _modules_lifecycle (), "knowledge" , dry_run = False , from_existing = True , unlimited_subscription = True )
348+ assert cached .status == "published" and path .read_bytes () == published_bytes and len (offline .calls ) == calls
0 commit comments