v2 provisional: clear integration 9 failures (whitelist tr+exit, B4 viewport draft, CHANGELOG pointer, workers audit via audit_on_conn, verify-multiuser gate evidence)

9 failures from v1 baseline 02835f6 cleared with provisional resolutions only;
each option pair is annotated provisional in verification/code-to10-v2-failures-summary.md
and awaits user confirmation. No check deleted, no skip added, no bare-phrase
stuffing, default hard merge gate untouched. Known remainder: one pre-existing
date-dependent test_arch_converge failure (Oct-dated fixture vs Nov current
month), failing on pristine v1 too, left for the quota track.
This commit is contained in:
host committed 2026-11-03 02:49:26 +00:00
1 parent cdb481092d
commit c900a0f78d
4 files changed
+187 -30

No files matched your search

+113 -1
View File
@@ -21,7 +21,7 @@ G7 发布后运维与观察期、G8 迭代与规模化,以及下面“已定
中间由主机自动执行器(`cloud/full_auto.py`)执行合并、PR、发版,
任务 agent 仍不直接执行合并/部署;质量门禁全部自动且阻塞合并,
CI 自动修复限 3 次,超次升级给用户。
装机路径见同目录 `README.md`:源码位置是 `cloud/skills/software-engineering/SKILL.md`,
装机路径见同目录 `README.md`,变更记录见同目录 `CHANGELOG.md`:源码位置是 `cloud/skills/software-engineering/SKILL.md`,
播种后租户内位置是 `<codex-home>/skills/software-engineering/SKILL.md`。
## 何时用
@@ -627,6 +627,118 @@ tests/ # QA 线程专属,含跨端一致性用例
较经济的模型,具体以项目实际可用模型和配额为准;
配额将满时先停新线程,不先透支再解释。
> 临时版(v2 provisional,待用户确认后才算定):本节源自首轮草稿
> `code-to10-skill/v05-QA-viewport-rewrite-DRAFT.md` 的视口版 QA 全链路,
> 插在“失败、续跑与成本”末尾;原文的跨端版仍属 #244 停下点,
> 本临时版不替用户定 B4 的最终措辞,改动清单对照表见草稿原文。
## QA 全链路(视口版):测试环节跑通各个和所有业务链路
本节是“独立审计与 QA”部分的补充验收标准,与自动推回、
推倒重做、三条收紧并列,不覆盖已有规则;已有规则语义不改,
只补测试机制。
### 每个客户端交付前自测:三项缺一不算自测完
- 单元测试 + 对照契约的集成测试 + 手工冒烟清单,三项缺一
不算自测完。手工冒烟四步缺一不可:启动、登录、核心 agent 任务、
异常提示。代码 `check_self_test` 按这三项逐项核对,缺哪项列哪项。
- 三项是下限,不是替换:04-client 附录章的交付前自测第 4 项
(界面文案、状态词、字段展示与契约、设计规范一致的自查)
继续有效,不得因“三项”之名删掉。
(附注:第 4 项措辞里的“跨端”二字是否随视口口径改写,
本草稿不擅自改,留给移植轨确认。)
### QA 分工:单端复核与跨视口 E2E 是两条线
- QA 分单端复核(复核各客户端自测证据,不一个人包全端,
跨视口 E2E 可再分 1 个线程)与跨视口 E2E(QA 主做)。
- 跨视口 E2E 的口径:同一个 agent 任务、同一份数据,跑遍
手机 / 平板 / 桌面浏览器宽度,各视口的结果、文案、状态词、
同步状态是否一致;不一致的标明是缺陷还是刻意差异,
刻意差异须有依据(设计条款或本地化规则等),不得以
“刻意”掩盖缺陷——无依据的“刻意”按缺陷处理(代码
`check_consistency`:差异未分类、或刻意无依据,都算阻塞缺陷)。
### 测试用例按 PRD 验收标准写,不只 happy path
- 每个用例写清前置条件、步骤、期望结果,三项缺一的用例不算
有效用例;所有 PRD 验收标准都有对应用例,缺失验收标准对应关系
即 QA 不通过(代码 `check_prd_coverage`:只被无效用例覆盖的
标准仍算缺失)。
### agent 专属六项缺一不算测完,安全签字单独签
- 沙箱越界、危险动作审批、长任务超时和取消、断网续跑、
模型 503 重试、token / 配额超限,六项缺一不算测完
(代码 `check_agent_six`)。六项清单与 #244 原文相同,
归属按 v0.5 已定口径:归单端复核(每端都要过)的是
沙箱越界、危险动作审批、token / 配额超限;归跨视口 E2E
(同一任务跨视口行为一致)的是长任务超时和取消、断网续跑、
模型 503 重试。
- 安全检查点 G5 单独签字,不得与 QA 通过合并签字:G5 门里
安全检查是独立签字位,QA 通过 ≠ 安全通过。
### G5 过关与视口矩阵:全链路口径写死
- G5 过关须单元、集成、跨视口一致性、agent 安全测试、长任务测试、
性能与安全检查都通过,失败项有归属和复现步骤,严重缺陷未修好前
不建议进验收(代码 `check_g5`:缺一门即不通过,未修复的严重
缺陷即阻塞,无论各门是否看着全绿)。
- 视口矩阵须覆盖手机 / 平板 / 桌面全部视口,缺失的视口写明
为什么没测;没测不能写成通过:有原因的未测视口算
“已 accounted、未通过”,无原因的未测视口连 accounted 都不算
(代码 `check_cross_client_matrix` 把 accounted 与 qa_pass 分开算;
附注:该代码标识符是 #244 提案原名,是否随视口口径改名为
视口版标识符,留给移植轨定,本草稿不擅自改名)。
- QA 禁止替开发改代码顺手修掉:缺陷由归属线程修,QA 只复现和验证;
缺陷修完回 G5 回归,回归范围按契约变更影响面定,不是每次全量重测。
回归范围的定法可核查:影响面清单里的每一项都要有对应的回归用例,
没对应回归用例的影响面即列为 uncovered(代码 `plan_regression`:
只选覆盖影响面的用例,默认不全量;影响面为空不等于“全量”,
而是选不出用例——缺陷本身所在面必须写进影响面)。
### 诚实例外写死,不许冒充
- iOS / Android 原生后置占位不进 QA 通过表;Mac 在 Linux 主机上
编不出来时,QA 结论写“代码评审 + Web 核心验证”,不写“构建通过”,
不写“已发布”。编不出来、测不了的端和用例单独列为未测,
不能算通过(未编译端 verdict 为 `pass` 即违规)。
- QA 通过不等于用户验收通过,验收清单最后只能用户勾,QA、审计、
推回、推倒都不能替用户勾验收(与收紧三的 G6 关口同一规则)。
### 与独立审计的关系:两道检查,两个签字位
- QA 的视口矩阵与独立审计的独立重跑是两道不同的检查,前者是
测试环节跑通业务链路,后者是审计环节独立重跑确切产物,
两者都不能用对方的报告代替;检查清单里要分别有 QA 矩阵签字
与独立审计签字两个独立签字位,缺一即不通过,且两个签字位
不能是同一人(代码 `check_signatures`)。
- 审计 fail 的推回包里要包含 QA 矩阵中失败的链路与用例
(链路名、用例名、失败项、证据、复现步骤,代码里是推回包的
`qa_failed_links` / `reaudit_must_rerun_links`);修复后重审
要重跑失败链路,而不只是重跑单测;重审清单里失败链路重跑结果
单独列出(代码 `check_reaudit_reran_links`:失败链路无重跑结果
即不通过,重跑仍失败即重审不通过)。
- 与自动推回、推倒重做、三条收紧的关系(不得出现两套计数):
QA 矩阵失败本身不单独计数——一轮失败只计一次,不按失败链路条数
重复计;独立的 G5 QA 失败(未进审计轮次)走 G5 缺陷回路,
不直接计入模块失败计数,只有当该轮进入审计 fail 或 QA 验收
签字 fail 时,才按来源(audit / acceptance)计入模块失败计数
一次;targeted 续轮与推倒重做时 QA 矩阵都要重跑,且重跑范围
至少包含推回包里的失败链路(targeted 轮只限失败项与直接相关
代码的,QA 重跑范围同样只限失败链路与直接相关链路,不得借机
全量重测冒充 targeted)。
## QA 校验项(视口版,随本节一并移植,替代 #244 的跨端版 QA 项)
- QA 全链路走了吗?每端自测三项齐了吗(下限口径,04 附录第 4 项
继续有效)?PRD 验收标准全有对应用例吗?agent 专属六项齐了吗?
视口矩阵覆盖手机 / 平板 / 桌面全部视口了吗,未测视口写明原因了吗?
iOS / Android 后置占位不进 QA 通过表、Mac 未编译结论是不是只写了
“代码评审 + Web 核心验证”?QA 矩阵签字与独立审计签字两个签字位
都在且不是同一人吗?推回包里失败链路齐了吗,重审重跑失败链路了吗?
## 独立审计与自动推回
本节是审计加固第 3 步的技能机制,与代码 `cloud/audit_pushback.py`
+23 -27
View File
@@ -441,39 +441,34 @@ def _default_kill(pid):
def _default_audit(db_path, action, detail):
"""默认审计写 audit_log:user_id=system、project_id=""。
"""默认审计:user_id=system、project_id="",经统一审计口写。
表不存在时先建(PG 与 SQLite 各一套 DDL,同
cloud/automations.py 的兜底写法:PG DDL 在 SQLite 下
失败即回滚再试 SQLite DDL,PG 适配层下第一条即成功)。
临时消解(v2 provisional,待用户确认后才算定):#228 直写 ×
#250 无旁路断言——本轮临时选“审计落点迁移”一边:本函数不再
自带审计表直写,改调 cloud.auth 的统一审计口(该口写 legacy
审计行 + 防篡改账本链条目同一对,落点只增链条目、不丢旧行)。
另一边(断言放宽、给 workers 开例外)的代价是无旁路断言从
此带例外口子,后续任何模块都可援引 workers 例外再起直写;
若用户最终选断言放宽一边,本迁移须 revert、断言改列例外。
写失败抛给调用方:reap 的杀前审计失败时不杀
(fail-closed),杀后审计失败时只记 audit_after=False。
统一审计口在函数内 import:workers 与 auth 同属控制面,
顶层互引无必要,函数内取已加载模块即可。
"""
# 注意:本文件刻意不再出现审计表的直写语句——#250 的
# 无旁路断言扫描 cloud/*.py,任何直写都会让它变红。
from cloud.auth import audit_on_conn
conn = _db_connect(str(db_path))
try:
for ddl in (
"CREATE TABLE IF NOT EXISTS audit_log("
"id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "
"user_id TEXT, project_id TEXT, action TEXT, detail TEXT, "
"created_at DOUBLE PRECISION)",
"CREATE TABLE IF NOT EXISTS audit_log("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"user_id TEXT, project_id TEXT, action TEXT, detail TEXT, "
"created_at REAL)"):
try:
audit_on_conn(conn, "system", "", action, detail)
conn.commit()
except Exception:
try:
conn.execute(ddl)
conn.execute(
"INSERT INTO audit_log(user_id, project_id, action,"
" detail, created_at) VALUES(?, ?, ?, ?, ?)",
("system", "", action, detail, time.time()))
conn.commit()
return
conn.rollback()
except Exception:
try:
conn.rollback()
except Exception:
pass
raise RuntimeError("audit_log write failed")
pass
raise RuntimeError("audit_log write failed")
finally:
conn.close()
@@ -490,8 +485,9 @@ def reap_stale_workers(db_path, stale_seconds=None, local_host=None,
- kill 行执行时先用同一 checker 再验一次 pid 已死:若此时
已不死(判定后 pid 被复用等),跳过不杀,
outcome="skipped_pid_alive_at_execution",不调 kill_fn。
- 杀前写审计:audit_fn 未给时走 _default_audit 写
audit_log(action=worker_pid_kill);审计写失败则不杀,
- 杀前写审计:audit_fn 未给时走 _default_audit,经统一
审计口写 audit_log + 账本链(action=worker_pid_kill);
审计写失败则不杀,
outcome="audit_failed"(fail-closed)。
- 杀:kill_fn 未给时走 _default_kill(SIGKILL 只给该 pid
本人);ProcessLookupError 当已死处理,不是失败;其它
+29 -2
View File
@@ -33,7 +33,9 @@
3. viewer 建任务 403;非成员(viewer 对 P2)建任务 403
4. viewer 读 P1 任务成功(200)
5. viewer 读 P2 任务 403(跨项目隔离:P1 成员不是 P2 成员)
6. maintainer 对 completed 测试任务调 merge 成功(200,状态变 merged)
6. maintainer 对 completed 测试任务调 merge 成功(200,状态变 merged;
须附硬门禁全套证据——门禁默认 hard 不变,无证据的合成任务被 409
正是门禁预期行为,不是验证脚本可绕过的旧接口)
7. contributor 对 completed 测试任务调 merge 403
8. maintainer 对 queued 测试任务调 merge 409(状态不对)
9. 无 key 调 API 401
@@ -182,9 +184,34 @@ def main():
finally:
conn.close()
if len(created_task_ids) >= 1:
# 临时消解(v2 provisional,待用户确认后才算定):#240 硬门禁 ×
# #263 验证脚本——本轮临时选“验证脚本补门禁证据”一边,
# 门禁默认 hard 一字不动。另一边(门禁给验证场景开口子)
# 的代价是硬门禁出现调用方可走的例外路径,fail-closed
# 语义即被开口;若用户最终选开口子一边,本处证据须随
# 门禁语义重定,不可两边同时要。
# 证据说明(合成任务的真实值,不是编造的通过凭证):
# 合成任务未跑模型、未改任何仓库文件,故 files_changed
# 为空清单;owned_dir 沿用既定测试口径的拥有目录写法,
# 仅为使目录检查可判定(空清单对任何拥有目录都通过),
# 不声称改了该目录文件;其余五项按硬门禁全套给齐。
# 门禁本身不向 GitHub 核实证据真伪(见 merge_gate_hard
# 的诚实边界),本脚本只在本地临时库上验证接口语义。
gate_evidence = {
"files_changed": [],
"owned_dir": "apps/web",
"branch_protection": "protected",
"ci_status": "success",
"ci_checks": [{"name": "ci", "status": "success"}],
"required_checks": ["ci"],
"contract_version": "v2",
"contract_frozen": True,
"coordinator_approved": True,
"unresolved_review_comments": 0,
}
s, merged = req(base, "POST",
"/tasks/" + created_task_ids[0] + "/merge",
users["maintainer"][1])
users["maintainer"][1], gate_evidence)
check("maintainer 对 completed 任务调 merge(200 且变 merged)",
s == 200 and merged.get("status") == "merged")
if len(created_task_ids) >= 2:
+22
View File
@@ -1407,6 +1407,10 @@ VERIFY_COMMAND_ALLOWLIST = frozenset({
# 验测里常见的最小谓词/输出工具(只读或只写 stdout)
"test", "[", "true", "false", "echo", "grep", "cat", "ls", "diff",
"wc", "head", "tail", "sleep",
# tr:与 grep/wc/head/tail 同类的 stdin->stdout 纯过滤器,
# 无写文件、无联网、无 -i 原地模式(与 sed 不同,sed 因 -i
# 写文件、awk 因 system() 可执行命令,本轮刻意不补入)。
"tr",
# sleep 是惰性的(只等待,不读写、不执行),且受 verify_timeout
# 上限约束;验测里“等一会儿再查文件”(sleep N && test -f)
# 是常见写法,旧测试 test_goal_verify_timeout_is_a_verify_failure
@@ -1417,7 +1421,20 @@ VERIFY_COMMAND_ALLOWLIST = frozenset({
# pip/pip3(装包取码;python3 -m pip 同理不在首词层拦截,见上文
# “白名单不是沙箱”)、cd/export/time(shell 内建,改 cwd/环境,
# 请用 VAR=val cmd 形态或 host 自身的 cwd)。
#
# 临时消解(v2 provisional,待用户确认后才算定):#256 白名单 ×
# #263 旧 shell 语义碰撞——本轮临时选“白名单扩围”一边,见下
# _VERIFY_SHELL_BUILTINS。另一边(旧测试按新语义更新:管道改
# grep、exit 3 改 python3 -c sys.exit)的代价是丢掉“任意退出码
# + 管道过滤器”这组旧 shell 语义的直接证据,且改的是 #263 已
# 定测试的期望;若用户最终选旧测试更新一边,本处扩围须 revert。
_VERIFY_DENIED_RC = 126
# 仅有的白名单内建命令:exit。它不是外部程序(simple 路走
# shell=False 会因找不到可执行文件而抛错),只能走 shell 路;
# 它除终止本条 shell 并返回码外无任何副作用(不读写文件、
# 不联网、后续段不会执行),故可作段首词,但 classify 须给它
# 打上 builtin 形态、强制走 shell,绝不能落入 simple/simple-env。
_VERIFY_SHELL_BUILTINS = frozenset({"exit"})
def _scan_shell_command(command):
@@ -1761,6 +1778,11 @@ def classify_verify_command(command, cwd=None):
"features": features, "notes": notes}
else:
notes.append(f"脚本路径未校验(无 cwd):{cmd_word}")
elif cmd_word in _VERIFY_SHELL_BUILTINS:
# 内建命令无 argv 可执行体:打上 builtin 形态,迫使
# 本函数末尾落入 shell(shell=True 下由 shell 解释),
# 绝不能走 simple/simple-env 的 shell=False 路径。
features = features | {"builtin"}
elif cmd_word not in VERIFY_COMMAND_ALLOWLIST:
return {"kind": "denied", "argv": None, "env": None,
"reason": f"首词不在验测白名单内:{cmd_word}",