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:
1 parent
cdb481092d
commit
c900a0f78d
4 files changed
+187
-30
No files matched your search
@@ -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
@@ -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 当已死处理,不是失败;其它
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}",
|
||||
|
||||
Reference in new issue
Block a user