fix(co4e): 4 chỗ ghi JSON của Gamma đi qua AtomicJsonFile — tiêu chí nghiệm thu A
Soát lại plan.md thì thấy CASAN là NĂM tiêu chí C-A-S-A-N, không phải ba. Tiêu chí A có hai vế, tôi mới đạt vế đầu: vế 1 0 API key plaintext trong JSON -> đã đạt từ 25/08 vế 2 MỌI thao tác ghi tệp đi qua AtomicJsonFile -> CHƯA Toàn repo còn 15 chỗ ghi JSON thẳng. Bốn trong đó là của Gamma (vùng Co4E): core/co4e.py:236 lưu workflow ghi thẳng, không nguyên tử gì cả core/co4e.py:310 lưu agent ghi thẳng core/co4e_run_manager.py:156 tmp + replace tự viết application/workflows/co4e_workflow_service.py:178 tmp + replace tự viết Hai chỗ đầu nguy hơn: tắt máy giữa lúc lưu là mất luôn workflow hoặc agent. Hai chỗ sau nhìn thì có vẻ ổn vì đã tmp + replace, nhưng thiếu hai thứ: * không fsync — dữ liệu có thể còn nằm trong bộ đệm ổ đĩa khi mất điện, nên "nguyên tử" chỉ đúng với crash tiến trình, không đúng với mất điện; * dùng thẳng Path.replace, đúng chỗ dính PermissionError [WinError 5] mà tôi vá hôm 25/08 — Defender giữ handle file vừa tạo. Tần suất đo được khoảng 1/140 lần lưu, nhân với số lần lưu lịch sử chạy flow. 11 chỗ còn lại thuộc team khác (accounts, admin_agents, custom_agents, flows, groups, history, projects, skills, tasks). Không đụng vào; cần báo lên vì tiêu chí A là tiêu chí TOÀN DỰ ÁN, Gamma sạch không cứu được cổng. Đã kiểm application/ vẫn không kéo PySide6 vào sau khi thêm import mới (tiêu chí C). 714 test xanh, 24/24 checker qua. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6c68417103
commit
af8a3712e2
@@ -35,6 +35,8 @@ service này.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ...infrastructure.persistence.json.atomic_json_file import AtomicJsonFile
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -174,10 +176,10 @@ class Co4EWorkflowService:
|
|||||||
payload = {"runs": [r.to_dict() for r in runs]}
|
payload = {"runs": [r.to_dict() for r in runs]}
|
||||||
try:
|
try:
|
||||||
self._history_path_value.parent.mkdir(parents=True, exist_ok=True)
|
self._history_path_value.parent.mkdir(parents=True, exist_ok=True)
|
||||||
tmp = self._history_path_value.with_suffix(".json.tmp")
|
# AtomicJsonFile thay cho tmp+replace tự viết: bản cũ thiếu fsync
|
||||||
tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2),
|
# (dữ liệu có thể còn trong bộ đệm khi mất điện) và dùng thẳng
|
||||||
encoding="utf-8")
|
# Path.replace, vốn thỉnh thoảng bị Defender từ chối trên Windows.
|
||||||
tmp.replace(self._history_path_value) # atomic — khong bao gio de lai file ghi do dang
|
AtomicJsonFile(self._history_path_value).write(payload)
|
||||||
except OSError:
|
except OSError:
|
||||||
# Giu dung hanh vi cu (core/co4e_run_manager.py::_save_history):
|
# Giu dung hanh vi cu (core/co4e_run_manager.py::_save_history):
|
||||||
# mot lan luu that bai (day dia, mat quyen...) KHONG duoc phep
|
# mot lan luu that bai (day dia, mat quyen...) KHONG duoc phep
|
||||||
|
|||||||
+6
-2
@@ -18,6 +18,8 @@ existing ``core/skills.py`` registry.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ..infrastructure.persistence.json.atomic_json_file import AtomicJsonFile
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -233,7 +235,9 @@ def save_workflow(wf: Workflow, directory: Optional[Path] = None) -> Path:
|
|||||||
directory = directory or WORKFLOWS_DIR
|
directory = directory or WORKFLOWS_DIR
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
path = directory / f"{wf.id}.json"
|
path = directory / f"{wf.id}.json"
|
||||||
path.write_text(json.dumps(workflow_to_dict(wf), ensure_ascii=False, indent=2), encoding="utf-8")
|
# Tiêu chí nghiệm thu A: mọi thao tác ghi tệp đi qua AtomicJsonFile. Trước
|
||||||
|
# đây ghi thẳng, nên tắt máy giữa lúc lưu là mất luôn workflow.
|
||||||
|
AtomicJsonFile(path).write(workflow_to_dict(wf))
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
@@ -307,7 +311,7 @@ def save_custom_agent(agent: CustomAgent, directory: Optional[Path] = None) -> P
|
|||||||
directory = directory or AGENTS_DIR
|
directory = directory or AGENTS_DIR
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
path = directory / f"{agent.id}.json"
|
path = directory / f"{agent.id}.json"
|
||||||
path.write_text(json.dumps(agent_to_dict(agent), ensure_ascii=False, indent=2), encoding="utf-8")
|
AtomicJsonFile(path).write(agent_to_dict(agent))
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ the run that is currently open.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ..infrastructure.persistence.json.atomic_json_file import AtomicJsonFile
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
@@ -152,10 +154,10 @@ class Co4ERunManager(QObject):
|
|||||||
payload = {"runs": [h.to_record() for h in runs]}
|
payload = {"runs": [h.to_record() for h in runs]}
|
||||||
try:
|
try:
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
tmp = path.with_suffix(".json.tmp")
|
# AtomicJsonFile thay cho tmp+replace tự viết: bản cũ thiếu fsync
|
||||||
tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2),
|
# (dữ liệu có thể còn trong bộ đệm khi mất điện) và dùng thẳng
|
||||||
encoding="utf-8")
|
# Path.replace, vốn thỉnh thoảng bị Defender từ chối trên Windows.
|
||||||
tmp.replace(path) # atomic — never leaves a half-written file
|
AtomicJsonFile(path).write(payload)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user