gapfill G7: make _merge_task status commit and success audit atomic (same connection, audit_on_conn, one commit; audit failure rolls back to completed instead of merged-plus-500; ledger-head IntegrityError retries whole segment 5x mirroring AuthStore.audit); success semantics unchanged
This commit is contained in:
1 parent
7524a779f2
commit
5d0651e70a
2 files changed
+223
-8
No files matched your search
+39
-8
@@ -68,6 +68,7 @@ from cloud.quota import quota_for_owner as _quota_for_owner
|
||||
from cloud.timeouts import STALE_SWEEP_SECONDS as _STALE_SWEEP
|
||||
from cloud.timeouts import approval_default_seconds as _approval_default
|
||||
from cloud.timeouts import validate_server_timeout as _validate_timeout
|
||||
from cloud.auth import audit_on_conn as _audit_on_conn
|
||||
from cloud.db import connect as _db_connect
|
||||
from cloud.db import create_database as _create_database
|
||||
from cloud.db import init_postgres_schema as _init_postgres_schema
|
||||
@@ -6578,15 +6579,47 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
"merge_gate": gate,
|
||||
})
|
||||
return
|
||||
# G7(A4 时序残留):状态提交与成功审计必须同一事务。
|
||||
# 旧写法先 commit 状态、再另开 AuthStore 连接写审计:
|
||||
# 审计失败时状态已是 merged、响应却是 500——“合并了却
|
||||
# 报失败”,调用方无法靠状态判断,也无法安全重试
|
||||
# (重复合并 409)。新写法用 audit_on_conn 把审计对
|
||||
# 写进同一连接、一次 commit:审计失败则整段回滚,状态
|
||||
# 仍是 completed、响应 500(_dispatch_safe 兜底),调用
|
||||
# 方可原样重试合并。链头唯一冲突的重试口径与
|
||||
# AuthStore.audit 完全一致(5 次、只重试
|
||||
# IntegrityError,其它错误直接抛)。成功语义不变:
|
||||
# 200 体、审计内容(merge_task/task_id)、门禁响应全同。
|
||||
# 前提:tasks 与 audit_log/audit_ledger 在同一库
|
||||
# (_db 与 _auth_store 都连 server.db_path;PG 模式下
|
||||
# _connect 同样分发到同一 PG 库,? 占位由适配层转 %s)。
|
||||
now = time.time()
|
||||
conn = self._db()
|
||||
try:
|
||||
conn.execute(
|
||||
"UPDATE tasks SET status = 'merged', updated_at = ?"
|
||||
" WHERE id = ?",
|
||||
(now, task_id),
|
||||
)
|
||||
conn.commit()
|
||||
last_exc = None
|
||||
for _attempt in range(5):
|
||||
try:
|
||||
conn.execute(
|
||||
"UPDATE tasks SET status = 'merged',"
|
||||
" updated_at = ? WHERE id = ?",
|
||||
(now, task_id),
|
||||
)
|
||||
_audit_on_conn(conn, user_id, task["project_id"],
|
||||
"merge_task", task_id)
|
||||
conn.commit()
|
||||
break
|
||||
except Exception as exc:
|
||||
last_exc = exc
|
||||
try:
|
||||
conn.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
if not isinstance(
|
||||
exc, (sqlite3.IntegrityError,
|
||||
_PgIntegrityError)):
|
||||
raise
|
||||
else:
|
||||
raise last_exc
|
||||
task = _task_to_dict(
|
||||
conn.execute(
|
||||
"SELECT * FROM tasks WHERE id = ?", (task_id,)
|
||||
@@ -6594,8 +6627,6 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
with _auth_store(self.server.db_path) as store:
|
||||
store.audit(user_id, task["project_id"], "merge_task", task_id)
|
||||
task["merge_gate"] = gate
|
||||
self._send_json(200, task)
|
||||
|
||||
|
||||
Reference in new issue
Block a user