@@ -1178,3 +1178,98 @@ def recover_from_checkpoint(
11781178 except run_projector .ProjectionError as exc :
11791179 raise CheckpointError (_bound ("projection failed" ), category = "projection" ) from exc
11801180 return _restore_run_json_from_checkpoint (run_dir , restore_bytes , run_meta = run_meta )
1181+
1182+
1183+ # -- Export boundary (issue #636) ---------------------------------------------
1184+
1185+ _ARTIFACT_REFERENCE_KEYS = frozenset ({"path" , "sha256" , "media_type" , "byte_size" , "privacy_class" })
1186+
1187+
1188+ def checkpoint_artifact_reference (* , sha256 : str , byte_size : int ) -> dict [str , Any ]:
1189+ """Return the closed artifact-reference shape used at export boundaries."""
1190+ if not isinstance (sha256 , str ) or not _HEX64 .fullmatch (sha256 ):
1191+ raise CheckpointError (_bound ("checkpoint export digest is invalid" ), category = "export-privacy" )
1192+ if isinstance (byte_size , bool ) or not isinstance (byte_size , int ) or byte_size < 0 :
1193+ raise CheckpointError (_bound ("checkpoint export byte size is invalid" ), category = "export-privacy" )
1194+ return {
1195+ "path" : f"events/{ CHECKPOINT_DIR_NAME } /{ sha256 } .json" ,
1196+ "sha256" : sha256 ,
1197+ "media_type" : CHECKPOINT_MEDIA_TYPE ,
1198+ "byte_size" : byte_size ,
1199+ "privacy_class" : CHECKPOINT_PRIVACY_CLASS ,
1200+ }
1201+
1202+
1203+ def refuse_checkpoint_body_export (* , reason : str = "checkpoint body is private" ) -> None :
1204+ """Refuse an export that would emit checkpoint body content."""
1205+ raise CheckpointError (
1206+ _bound (f"{ reason } (privacy_class={ CHECKPOINT_PRIVACY_CLASS } )" ),
1207+ category = "export-privacy" ,
1208+ )
1209+
1210+
1211+ def is_checkpoint_artifact_reference (payload : Mapping [str , Any ]) -> bool :
1212+ """True when payload is exactly the closed artifact-reference shape."""
1213+ if set (payload ) != _ARTIFACT_REFERENCE_KEYS :
1214+ return False
1215+ sha = payload .get ("sha256" )
1216+ path = payload .get ("path" )
1217+ byte_size = payload .get ("byte_size" )
1218+ return (
1219+ isinstance (sha , str )
1220+ and bool (_HEX64 .fullmatch (sha ))
1221+ and path == f"events/{ CHECKPOINT_DIR_NAME } /{ sha } .json"
1222+ and payload .get ("media_type" ) == CHECKPOINT_MEDIA_TYPE
1223+ and payload .get ("privacy_class" ) == CHECKPOINT_PRIVACY_CLASS
1224+ and not isinstance (byte_size , bool )
1225+ and isinstance (byte_size , int )
1226+ and byte_size >= 0
1227+ )
1228+
1229+
1230+ def strip_checkpoint_bodies_for_export (run_dir : Path ) -> list [dict [str , Any ]]:
1231+ """Replace recovery-checkpoint file bodies with artifact references.
1232+
1233+ Local recovery continues to read the original private bodies under the run
1234+ directory. Call this only on a copy that is about to leave the run tree
1235+ (archive, bundle, sync, or similar export boundary).
1236+ """
1237+ cp_dir = checkpoint_dir (run_dir )
1238+ if not cp_dir .is_dir ():
1239+ return []
1240+ replaced : list [dict [str , Any ]] = []
1241+ for path in sorted (cp_dir .glob ("*.json" )):
1242+ if not path .is_file () or path .is_symlink ():
1243+ refuse_checkpoint_body_export (reason = "checkpoint export path is not a regular file" )
1244+ raw = path .read_bytes ()
1245+ try :
1246+ parsed = json .loads (raw .decode ("utf-8" ))
1247+ except (UnicodeDecodeError , json .JSONDecodeError ):
1248+ parsed = None
1249+ if isinstance (parsed , dict ) and is_checkpoint_artifact_reference (parsed ):
1250+ replaced .append (dict (parsed ))
1251+ continue
1252+ sha = hashlib .sha256 (raw ).hexdigest ()
1253+ if path .name != f"{ sha } .json" :
1254+ refuse_checkpoint_body_export (reason = "checkpoint export filename digest mismatch" )
1255+ reference = checkpoint_artifact_reference (sha256 = sha , byte_size = len (raw ))
1256+ path .write_text (json .dumps (reference , indent = 2 , sort_keys = True ) + "\n " , encoding = "utf-8" )
1257+ os .chmod (path , 0o600 )
1258+ replaced .append (reference )
1259+ return replaced
1260+
1261+
1262+ def assert_export_tree_has_no_checkpoint_bodies (root : Path ) -> None :
1263+ """Fail closed if any recovery-checkpoint file under root still holds a body."""
1264+ for path in sorted (Path (root ).rglob (f"*/{ CHECKPOINT_DIR_NAME } /*.json" )):
1265+ if not path .is_file ():
1266+ continue
1267+ try :
1268+ payload = json .loads (path .read_text (encoding = "utf-8" ))
1269+ except (OSError , UnicodeDecodeError , json .JSONDecodeError ) as exc :
1270+ raise CheckpointError (
1271+ _bound ("checkpoint export body is unreadable" ),
1272+ category = "export-privacy" ,
1273+ ) from exc
1274+ if not isinstance (payload , dict ) or not is_checkpoint_artifact_reference (payload ):
1275+ refuse_checkpoint_body_export (reason = "checkpoint body crossed an export boundary" )
0 commit comments