Просмотр исходного кода

运行记录:补齐 Production 0.5 路径公式与不可变字节写入

在现有 run/layout.py 中增加共享视觉锚点、ProductionPackage、Assembly、Inspection、Preflight、Validation、Summary 和 FinalDelivery 的纯路径公式。

为正式媒体和不可变记录补充 write_once_bytes,继续使用原子写入和内容一致性保护,不引入有状态 RunLayout、Repository 或新的存储框架。

路径测试固定全部相对位置与工具输出目录,确保阶段 13、14 的恢复和 completed no-op 可以依赖稳定文件布局。
SamLee 3 дней назад
Родитель
Сommit
f4b80f63af

+ 44 - 0
production_build_agents/run/layout.py

@@ -121,6 +121,10 @@ def production_input_catalog_path(run_dir: Path) -> Path:
     return run_dir / "production_input_catalog.json"
 
 
+def shared_visual_anchor_path(run_dir: Path, suffix: str) -> Path:
+    return run_dir / "production_inputs" / f"shared_visual_anchor{suffix}"
+
+
 def segment_package_path(run_dir: Path, segment_id: str) -> Path:
     return run_dir / "segment_packages" / f"{segment_id}.json"
 
@@ -185,6 +189,46 @@ def segment_summary_path(
     )
 
 
+def production_package_path(run_dir: Path) -> Path:
+    return run_dir / "production_package.json"
+
+
+def production_assembly_package_path(run_dir: Path) -> Path:
+    return run_dir / "production_assembly_package.json"
+
+
+def production_assembly_delivery_path(run_dir: Path) -> Path:
+    return run_dir / "production_assembly_delivery.json"
+
+
+def production_media_inspection_path(run_dir: Path) -> Path:
+    return run_dir / "production_media_inspection.json"
+
+
+def production_preflight_report_path(run_dir: Path) -> Path:
+    return run_dir / "production_preflight_report.json"
+
+
+def production_validator_candidate_path(run_dir: Path) -> Path:
+    return run_dir / "production_validator_candidate.json"
+
+
+def production_validation_report_path(run_dir: Path) -> Path:
+    return run_dir / "production_validation_report.json"
+
+
+def final_production_delivery_path(run_dir: Path) -> Path:
+    return run_dir / "final_production_delivery.json"
+
+
+def production_run_summary_path(run_dir: Path) -> Path:
+    return run_dir / "production_run_summary.json"
+
+
+def production_tool_output_dir(run_dir: Path) -> Path:
+    return run_dir / "production_tool_outputs"
+
+
 def tool_output_dir(run_dir: Path) -> Path:
     return run_dir / "tool_outputs"
 

+ 30 - 0
production_build_agents/run/records.py

@@ -96,6 +96,36 @@ def write_once_text(path: Path, content: str) -> Path:
     return path
 
 
+def write_once_bytes(path: Path, content: bytes) -> Path:
+    """只写一次二进制内容;相同字节复用,不同字节报版本冲突。"""
+
+    path.parent.mkdir(parents=True, exist_ok=True)
+    if path.exists():
+        if path.read_bytes() == content:
+            return path
+        raise VersionConflictError(f"文件版本冲突:{path}")
+
+    descriptor, temporary_name = tempfile.mkstemp(
+        dir=path.parent,
+        prefix=f".{path.name}.",
+        suffix=".tmp",
+    )
+    temporary_path = Path(temporary_name)
+    try:
+        with os.fdopen(descriptor, "wb") as handle:
+            handle.write(content)
+            handle.flush()
+            os.fsync(handle.fileno())
+        try:
+            os.link(temporary_path, path)
+        except FileExistsError:
+            if path.read_bytes() != content:
+                raise VersionConflictError(f"文件版本冲突:{path}")
+    finally:
+        temporary_path.unlink(missing_ok=True)
+    return path
+
+
 def write_once_json(path: Path, payload: Any) -> Path:
     """以稳定 JSON 编码写入不可变业务文件。"""
 

+ 58 - 0
tests/run/test_layout.py

@@ -13,11 +13,22 @@ from production_build_agents.run.layout import (
     plan_json_path,
     plan_mermaid_path,
     preprocess_report_path,
+    final_production_delivery_path,
+    production_assembly_delivery_path,
+    production_assembly_package_path,
     production_brief_path,
+    production_media_inspection_path,
+    production_package_path,
+    production_preflight_report_path,
+    production_run_summary_path,
+    production_tool_output_dir,
+    production_validation_report_path,
+    production_validator_candidate_path,
     run_lock_path,
     run_directory,
     run_metrics_path,
     run_summary_path,
+    shared_visual_anchor_path,
     stage_candidate_path,
     stage_validation_report_path,
     stage_validator_tool_output_dir,
@@ -111,6 +122,53 @@ class RunLayoutTest(unittest.TestCase):
             root / "tool_operations" / f"{filename}.json",
         )
 
+    def test_production_v05_paths_are_isolated(self) -> None:
+        root = Path("/tmp/production-run")
+        self.assertEqual(
+            production_package_path(root),
+            root / "production_package.json",
+        )
+        self.assertEqual(
+            shared_visual_anchor_path(root, ".png"),
+            root / "production_inputs" / "shared_visual_anchor.png",
+        )
+        self.assertEqual(
+            production_assembly_package_path(root),
+            root / "production_assembly_package.json",
+        )
+        self.assertEqual(
+            production_assembly_delivery_path(root),
+            root / "production_assembly_delivery.json",
+        )
+        self.assertEqual(
+            production_media_inspection_path(root),
+            root / "production_media_inspection.json",
+        )
+        self.assertEqual(
+            production_preflight_report_path(root),
+            root / "production_preflight_report.json",
+        )
+        self.assertEqual(
+            production_validator_candidate_path(root),
+            root / "production_validator_candidate.json",
+        )
+        self.assertEqual(
+            production_validation_report_path(root),
+            root / "production_validation_report.json",
+        )
+        self.assertEqual(
+            final_production_delivery_path(root),
+            root / "final_production_delivery.json",
+        )
+        self.assertEqual(
+            production_run_summary_path(root),
+            root / "production_run_summary.json",
+        )
+        self.assertEqual(
+            production_tool_output_dir(root),
+            root / "production_tool_outputs",
+        )
+
 
 if __name__ == "__main__":
     unittest.main()