From aa6179b5e1b9928e583d7a675fe62b247440708c Mon Sep 17 00:00:00 2001 From: host Date: Wed, 4 Nov 2026 09:06:27 +0000 Subject: [PATCH] gapfill G3: unify all 5 private row_get onto peer_messages.row_get (approvals, automations, marker_tools, todos, wait_ops; resident import retargeted); semantics identical on None/dict/sqlite3.Row, identity tests added --- cloud/approvals.py | 11 +------ cloud/automations.py | 12 +++----- cloud/marker_tools.py | 9 +----- cloud/resident.py | 2 +- cloud/tests/test_row_get_unified.py | 45 ++++++++++++++++++++++++++--- cloud/todos.py | 14 ++++----- cloud/wait_ops.py | 17 ++++------- 7 files changed, 58 insertions(+), 52 deletions(-) diff --git a/cloud/approvals.py b/cloud/approvals.py index 375f4ee..317705b 100644 --- a/cloud/approvals.py +++ b/cloud/approvals.py @@ -74,6 +74,7 @@ import json import time import uuid +from cloud.peer_messages import row_get from cloud.timeouts import APPROVAL_DEFAULT_SECONDS as _APPROVAL_DEFAULT from cloud.timeouts import expires_at_for as _expires_at_for from cloud.timeouts import resolve_module_timeout as _resolve_timeout @@ -86,16 +87,6 @@ MAX_SUMMARY_CHARS = 2000 MAX_DETAIL_CHARS = 20000 -def row_get(row, name): - """行取值容错:sqlite3.Row 缺列报 IndexError、dict 缺键报 - KeyError、空行报 TypeError,一律回 None(与 peer_messages - 的同名函数同款,模块独立所以这里自带一份)。""" - try: - return row[name] - except (KeyError, IndexError, TypeError): - return None - - def ensure_approvals_table(conn): """幂等建审批队列表。调用方负责 commit(服务端入口在写 操作后统一 commit;本函数自己不 commit,避免半截事务)。""" diff --git a/cloud/automations.py b/cloud/automations.py index c9de7c3..6a4bbaa 100644 --- a/cloud/automations.py +++ b/cloud/automations.py @@ -56,6 +56,7 @@ import time import uuid from datetime import datetime, timezone +from cloud.peer_messages import row_get from cloud.quota import decide as _quota_decide from cloud.quota import default_quota_usd_micros as _quota_default from cloud.quota import monthly_usage as _quota_monthly_usage @@ -73,14 +74,9 @@ ACTIVE = "active" CANCELLED = "cancelled" -def row_get(row, name): - """行取值容错:sqlite3.Row 缺列报 IndexError、dict 缺键报 - KeyError、空行报 TypeError,一律回 None(老库行缺列时 - 读自动化列表不能因此炸掉整页)。""" - try: - return row[name] - except (KeyError, IndexError, TypeError): - return None +# 行取值走 cloud.peer_messages.row_get(A7 全仓统一):旧私有版 +# 与统一版在真实行类型(None / dict / sqlite3.Row)下语义完全 +# 相同(缺键/缺列/空行一律回 None),故直接复用,不再自带一份。 AUTOMATION_COLUMNS = ( diff --git a/cloud/marker_tools.py b/cloud/marker_tools.py index 2954049..f1a26dd 100644 --- a/cloud/marker_tools.py +++ b/cloud/marker_tools.py @@ -139,6 +139,7 @@ from typing import Any from cloud.gates import RETRYABLE_STATUSES as _GATE_RETRYABLE from cloud.gates import TERMINAL_STATUSES as _GATE_TERMINAL +from cloud.peer_messages import row_get from cloud.gates import decide_control as _gate_decide_control from cloud.gates import is_goal_task as _gate_is_goal_task from cloud.gates import resolve_inside as _gate_resolve_inside @@ -285,14 +286,6 @@ def parse_first_run_at(value): return epoch -def row_get(row, name): - """与 peer_messages.row_get 同款容错取值。""" - try: - return row[name] - except (KeyError, IndexError, TypeError): - return None - - def _as_int(value, default): if isinstance(value, bool): return default diff --git a/cloud/resident.py b/cloud/resident.py index 1d649dc..001f663 100644 --- a/cloud/resident.py +++ b/cloud/resident.py @@ -89,7 +89,7 @@ from urllib.parse import urlsplit from cloud.automations import _audit_row as _auto_audit_row from cloud.automations import _automation_quota_exceeded -from cloud.automations import row_get +from cloud.peer_messages import row_get from cloud.db import PostgresIntegrityError as _PgIntegrityError MIN_INTERVAL_SECONDS = 60 diff --git a/cloud/tests/test_row_get_unified.py b/cloud/tests/test_row_get_unified.py index 65f10b2..5d2bc08 100644 --- a/cloud/tests/test_row_get_unified.py +++ b/cloud/tests/test_row_get_unified.py @@ -2,16 +2,24 @@ 只用标准库 + 内存 SQLite,不联网。锁定点: - 统一身份:auto_memory._row_get 与 lifecycle._row_get 就是 - peer_messages.row_get 本体(不是又一份复写)。 + peer_messages.row_get 本体(不是又一份复写);gapfill G3 + 之后 approvals、automations、marker_tools、todos、wait_ops + 的 row_get 以及 resident.py 导入的 row_get 也是同一本体。 - 真实行类型矩阵(None / dict / sqlite3.Row)下取值与回落 - 与三处旧版一致:现值原样回(含 0 / 空串等假值),缺列 / + 与旧版一致:现值原样回(含 0 / 空串等假值),缺列 / 缺键 / 空行回 default(默认 None)。 -- default 回落差异钉住:三处旧版的默认回落都是 None—— +- default 回落差异钉住:旧版的默认回落都是 None—— auto_memory 旧版 default 参数默认 None,lifecycle 旧版无 - default 参数一律回 None,peer 旧版回 None;统一后两模块 + default 参数一律回 None,peer 旧版回 None;统一后各模块 调用处仍不传 default,缺失时仍为 None,不算改语义。 - 统一路径实测:lifecycle.snapshot_to_dict 在 dict 缺键行与 sqlite3.Row 行上都经统一口出正确字段。 +- gapfill G3 全仓收尾:五处旧版与统一版在真实行类型下 + 语义完全相同(缺键/缺列/空行一律回 None,调用处均不传 + default),故统一不算改语义;todos / wait_ops 模块 + docstring 里“不 import cloud 其它模块”的表述同步修正为 + “除行取值复用 peer_messages(本身只依赖标准库)外不 + import cloud 其它模块”。 """ import sqlite3 @@ -20,13 +28,23 @@ import unittest from pathlib import Path try: + import cloud.approvals as approvals import cloud.auto_memory as auto_memory + import cloud.automations as automations import cloud.lifecycle as lifecycle + import cloud.marker_tools as marker_tools + import cloud.todos as todos + import cloud.wait_ops as wait_ops from cloud.peer_messages import row_get except ImportError: sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + import cloud.approvals as approvals import cloud.auto_memory as auto_memory + import cloud.automations as automations import cloud.lifecycle as lifecycle + import cloud.marker_tools as marker_tools + import cloud.todos as todos + import cloud.wait_ops as wait_ops from cloud.peer_messages import row_get @@ -48,6 +66,25 @@ class RowGetUnifiedTestCase(unittest.TestCase): self.assertIs(auto_memory._row_get, row_get) self.assertIs(lifecycle._row_get, row_get) + def test_gapfill_five_modules_are_the_same_function(self): + # gapfill G3:五处私有 row_get 已删,模块属性就是统一本体; + # resident.py 同样从 peer_messages 导入,故一并钉住。 + import cloud.resident as resident + + self.assertIs(approvals.row_get, row_get) + self.assertIs(automations.row_get, row_get) + self.assertIs(marker_tools.row_get, row_get) + self.assertIs(todos.row_get, row_get) + self.assertIs(wait_ops.row_get, row_get) + self.assertIs(resident.row_get, row_get) + # 五处调用处均不传 default,缺失回落仍是 None。 + for fn in (approvals.row_get, automations.row_get, + marker_tools.row_get, todos.row_get, + wait_ops.row_get): + self.assertIsNone(fn({}, "nope")) + self.assertIsNone(fn(None, "nope")) + self.assertEqual(fn({"a": 0}, "a"), 0) + def test_real_row_type_matrix(self): full, partial = _sqlite_row() # 现值原样回(含假值 0,不被当成缺失)。 diff --git a/cloud/todos.py b/cloud/todos.py index cc436a8..f65c7ce 100644 --- a/cloud/todos.py +++ b/cloud/todos.py @@ -27,8 +27,9 @@ pending / in_progress / blocked / completed / cancelled, 一个项目里多人并行,多项 in_progress 是正常态,强制 单一会把板子变成单人队列,所以这里不做互斥。 -本模块只依赖标准库,不 import cloud 其它模块,避免循环 -import;表迁移写法仿 automations(CREATE TABLE IF NOT +本模块除行取值复用 cloud.peer_messages(该模块本身只依赖 +标准库,故不产生循环 import)外不 import cloud 其它模块; +表迁移写法仿 automations(CREATE TABLE IF NOT EXISTS + 查列补齐,SQLite 与 Postgres 适配层同款:调用方 仍写 ? 占位符,db.py 适配层转 PG 方言)。 """ @@ -36,6 +37,8 @@ EXISTS + 查列补齐,SQLite 与 Postgres 适配层同款:调用方 import time import uuid +from cloud.peer_messages import row_get + STATUSES = ("pending", "in_progress", "blocked", "completed", "cancelled") PRIORITIES = ("low", "medium", "high") MAX_TITLE_LEN = 500 @@ -47,13 +50,6 @@ TODO_COLUMNS = ( "updated_at", "completed_at") -def row_get(row, name): - try: - return row[name] - except (KeyError, IndexError, TypeError): - return None - - def _row_dict(row, columns): if row is None or isinstance(row, dict): return row diff --git a/cloud/wait_ops.py b/cloud/wait_ops.py index 9afdde8..08f13cf 100644 --- a/cloud/wait_ops.py +++ b/cloud/wait_ops.py @@ -1,7 +1,8 @@ """线程等待与移交操作(对照 codex app 的 8 项工具面)。 -本模块只做控制面纯逻辑,不跑模型、不联网、不 import cloud 其它 -模块(与 automations / peer_messages 同款,避免循环 import); +本模块只做控制面纯逻辑,不跑模型、不联网;行取值复用 +cloud.peer_messages(该模块本身只依赖标准库,故不产生循环 +import),其余不 import cloud 其它模块; server.py 只负责鉴权、路由与错误码映射。 8 项在控制面的落点(详见 docs/thread-wait-ops.md 对照表): @@ -40,6 +41,8 @@ import re import time import uuid +from cloud.peer_messages import row_get + HANDOFF_STATUSES = ("pending", "accepted", "running", "completed", "failed") # report 允许的状态转移:终态(completed / failed)不可再动; @@ -74,16 +77,6 @@ SIGNIFICANT_KINDS = ("terminal", "needs_approval", "needs_input", "needs_continue") -def row_get(row, name): - """行取值容错:sqlite3.Row 缺列报 IndexError、dict 缺键报 - KeyError、空行报 TypeError,一律回 None(与 automations - 的 row_get 同款,老库行缺列时读移交列表不能因此炸掉)。""" - try: - return row[name] - except (KeyError, IndexError, TypeError): - return None - - def _row_dict(row, columns): if row is None or isinstance(row, dict): return row