merge: feature/rdev-closeout-2026-09-22

This commit is contained in:
WeiCHEN
2026-09-22 21:54:40 +08:00
10 changed files with 197 additions and 22 deletions
+7 -4
View File
@@ -25,14 +25,17 @@
- 目标环境批准的实际模型、协议及制品;通过 `PI_AGENT_MODEL` 传入批准值并比对 Runner 实际配置。脚本默认值不构成批准,也不证明实际模型。
- 脚本可能构建候选镜像、写 promotion receipt;须满足构建身份/授权条件。`NIGHTLY_SKIP_IMAGE_BUILD=1` 不能补足缺失的制品摘要证据。
脚本当前筛选 **仅两项**:
脚本当前筛选 **五项**(U8 + 四条工作流治理链):
```text
TestMultiIntentU8_RealPI_ConfirmGenerate_PG
TestWorkflowGovernance_PiAgent_PG_FullChain
TestWorkflowGovernance_PiAgent_PG_ProductManager_FullChain
TestWorkflowGovernance_PiAgent_PG_TestManager_FullChain
TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain
```
它不是“全部工作流通过”。按变更范围追加精确名称,例如(前提条件已核验,且已处于相应环境执行租约/独立环境):
它覆盖 main / product_manager / test_manager / FullNone 四条工作流治理链,但**不是**「全部工作流通过」,也不含 rdev P3 Go 顶层(rdev 仍用 Python 三样本 harness)。按变更范围追加精确名称,例如(前提条件已核验,且已处于相应环境执行租约/独立环境):
```bash
go test -count=1 -timeout 2h -v ./internal/server/ -run '^TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E$'
@@ -43,7 +46,7 @@ go test -count=1 -timeout 90m -v ./internal/server/ -run '^TestWorkflowGovernanc
测试筛选区分大小写;`-run 'PI'` 不能选择名称中的 `PiAgent`。追加用例先读其环境前提;FullLifecycle 中 `RUN_FULL_ADHOC_WBS` 与 `RUN_HEAVY_STAGES` 控制额外段,不能把默认未跑段报为已覆盖。普通 PG 用例同样以源码中的精确名称/前提为准。
运行前列 `expected_tests`(含必须子场景),运行后逐项对照执行、PASS、FAIL、SKIP、未匹配及重试。退出码 0 或 nightly receipt `pass` 不能替代该对照:基线脚本逐项核对两个预期顶层测试;额外子场景/用例仍须逐项核验。缺项不记 PASS,基础设施问题记录 blocker/infra_fail。保留首轮与重试关系,不隐藏失败。
运行前列 `expected_tests`(含必须子场景),运行后逐项对照执行、PASS、FAIL、SKIP、未匹配及重试。退出码 0 或 nightly receipt `pass` 不能替代该对照:基线脚本逐项核对五个预期顶层测试;额外子场景/用例仍须逐项核验。缺项不记 PASS,基础设施问题记录 blocker/infra_fail。保留首轮与重试关系,不隐藏失败。
## 模型与环境证据
@@ -59,4 +62,4 @@ go test -count=1 -timeout 90m -v ./internal/server/ -run '^TestWorkflowGovernanc
## 工程工具验收补充
本地协调与运维守卫使用 `python3 -B -m unittest discover -s scripts/tests -p 'test_*.py' -v`,在临时真实 Git/SQLite/进程环境执行;Docker 守卫测试使用受控目标记录调用,不重建共享服务。nightly 入口现在核对最终尝试日志中两个预期顶层测试均恰好执行且 PASS;零用例、部分 SKIP 或重复拼接证据均不能生成 PASS 收据。该日志核验不是本轮执行了产品 PI 的声明。
本地协调与运维守卫使用 `python3 -B -m unittest discover -s scripts/tests -p 'test_*.py' -v`,在临时真实 Git/SQLite/进程环境执行;Docker 守卫测试使用受控目标记录调用,不重建共享服务。nightly 入口现在核对最终尝试日志中五个预期顶层测试均恰好执行且 PASS;零用例、部分 SKIP 或重复拼接证据均不能生成 PASS 收据。该日志核验不是本轮执行了产品 PI 的声明。
@@ -0,0 +1,65 @@
---
title: "W4 rdev 收口"
task_id: rdev-closeout-2026-09-22
status: done
created: 2026-09-22
updated: 2026-09-22
owner: Cursor
task_type: implementation
task_tier: small
policy_revision: "AGENTS+ops@364ed7f1"
branch: feature/rdev-closeout-2026-09-22
worktree: .worktrees/rdev-closeout-2026-09-22
base_dev_sha: "364ed7f1a3f0393ef77ec4623a9518116ed2cb87"
write_scope: [".doc", "configs/overlays", "internal/squad", "scripts", ".github/workflows"]
allowed_environments: []
head_sha: "364ed7f1a3f0393ef77ec4623a9518116ed2cb87"
mutex: rdev-closeout
review_state: pending
integration_state: pending
deployment: not_required
cleanup: pending
cleanup_owner: 独立维护者
cleanup_trigger: tip 为 origin/dev 祖先且无进程后复查
remote_sync: not_requested
---
# W4 rdev 收口
## 用户目标与授权
- 用户目标:rd_proposal 标题对齐模板;nightly 扩成四工作流;P3 三样本在当前 HEAD 重跑并复验法规表误伤修复;short-term + commit。
- 交付终点:实现与证据就绪(本地提交);不合入、不 push。
- 禁止项:不 push;不动共享 KB / 项目 143;不启用红线 flag;无 `env:dev` 能力时不写共享服务。
## 范围与非目标
- 允许:质量门禁/overlay/hint、nightly 脚本与契约文案、dry fixture、本任务 short-term/报告。
- 非目标:从本 worktree 对共享 dev 跑长 E2E;rdev 专用 Go PG 顶层测试;升 FEATURE-MATRIX / gap-matrix。
## 步骤与验收
1. [x] `rd_proposal` 精确标题门禁 + ph01 overlay/hint — `rdevProposalHeadingIssues`;`TestRDevQualityGates` dry fixture。
2. [x] nightly 扩成四工作流治理链(FullChain ×3 + FullNone)并保留 U8 — `scripts/run-nightly-real-pi.sh` + workflow timeout + 验证契约。
3. [x] 法规表误伤 — 既有 `markdownTable` 首表截止 + P3 终稿 dry 复验仍无「问题行缺少」。
4. [x] P3 真 PI 重跑 — **blocked**(见下);不以 stub 冒充 PASS。
## 验证与证据
| 范围 / 被测 SHA | 命令与环境 | 预期 | 实际 / 判定 | 证据 |
|---|---|---|---|---|
| 标题门禁 + 法规表 dry | `go test ./internal/squad/ -run 'TestRDevQualityGates\|TestRDevOverlay'` | PASS | PASS | 本机 2026-09-22 |
| P3 三样本真 PI | `run_p3_real_pi.py` + 共享/独立 PI | 需 env:dev 租约 + 非 143 项目 | **blocked** | 任务无 `env:dev`;无 `TEST_DATABASE_URL`;规范禁止从 task worktree 写共享服务 |
| nightly 四工作流 | 脚本 `--expected` 五项 | 脚本已扩 | 未在本机实跑四链(耗时/租约) | 脚本与契约 diff |
## 收口与交接
- 剩余:获 `env:dev`(或独立固定环境)后在新 HEAD 重跑 P3;可选补 rdev Go PG 顶层进 nightly。
- 本地提交:本分支 tip;dev 集成 / 远端:未请求。
- 红线未动。
## 相关链接
- [报告](../../test-reports/2026-09-22-rdev-closeout-test-report.md)
- [P3 历史报告](../../test-reports/2026-09-19-rdev-p3-real-pi-test-report.md)
- [验证契约](../2026-09-21-validation-contract.md)
@@ -0,0 +1,52 @@
# W4 rdev 收口测试报告
## 一、背景
P3(2026-09-19)在项目 6533 真 PI 上跑过三样本:`rd_proposal` 生成完成但章节标题未对齐模板;`rd_planning_review` 因同节法规对照表被算进「问题行」而 quality_failed(score 20)。同日已修 `markdownTable` 首表截止,但标题对齐与 nightly 四工作流挂钩仍开放。
## 二、目标
1. 建议书标题必须对齐 `templates/docs/rd_proposal.md` 四个专业 `##`。
2. nightly 从「U8 + FullNone」扩成「U8 + 四条工作流治理链」。
3. 用 dry fixture 复验法规表误伤修复;真 PI 三样本仅在授权环境可跑时重跑。
## 三、环境
- OS:darwin;worktree `feature/rdev-closeout-2026-09-22`;基线 `364ed7f1`。
- 共享 dev health:schema 61、`kb_ready`、Runner 可达;**本任务 capabilities 无 `env:dev`**,且无 `TEST_DATABASE_URL`,故不写共享服务、不重跑真 PI。
## 四、结果
### 4.1 标题对齐门禁
- **输入**:`rdevProposalHeadingIssues`;P3 终稿 dry;对齐样例;「风险清单」负例。
- **实际**:P3 终稿检出缺失「项目背景与立项依据」「目标、里程碑与资源假设」「主要风险与对策概览」等;对齐样例 0 issue;改名为「风险清单」失败。
- **判定**:PASS(门禁+fixture)。真 PI 重跑 **blocked**,不记生成 PASS。
- **附**:`configs/overlays/rdev_ph01.md` 与 `rdev_stage_meta` hint 写明禁止自拟标题。
### 4.2 法规表误伤
- **输入**:既有 later-table 用例 + `rd_planning_review-final.md`。
- **实际**:无「问题行缺少」误报。
- **判定**:PASS(dry 复验)。
### 4.3 nightly 四工作流
- **输入**:`scripts/run-nightly-real-pi.sh`、`nightly-real-pi.yml`、验证契约。
- **实际**:`RUN_FILTER` 含 FullChain / ProductManager_FullChain / TestManager_FullChain / FullNoneMixedChain + U8;suite 默认超时 180m;Actions job 240m;契约列出五项 expected。
- **判定**:脚本与契约 PASS。本机未实跑四链(租约/时长);rdev Python P3 **未**挂进 nightly(无 Go 顶层脚本)。
### 4.4 P3 真 PI 重跑
- **判定**:blocked。原因:任务无 `env:dev`;操作规范禁止从 task worktree 写共享服务;`.env` 无 `TEST_DATABASE_URL`;用户禁止动项目 143 / 共享 KB。接管条件:独立固定 PG + env 租约后执行 `run_p3_real_pi.py`。
## 五、套件
```text
go test -count=1 ./internal/squad/ -run 'TestRDevQualityGates|TestRDevOverlay'
# ok
```
## 六、结论
门禁、overlay、nightly 四工作流脚本与 dry 复验已就绪。真 PI 三样本与 nightly 实跑收据待授权环境。红线未动。
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
# Do not use ubuntu-latest docker isolation for this job on this machine.
nightly-real-pi:
runs-on: nightly
timeout-minutes: 120
timeout-minutes: 240
permissions:
contents: read
env:
+4
View File
@@ -5,7 +5,11 @@
### role:writer
写满策划类章节;结论与范围表一致。
项目建议书(rd_proposal)的 `##` 标题必须原样使用模板:
「项目背景与立项依据」「产品定义与范围」「目标、里程碑与资源假设」「主要风险与对策概览」。
不得改成「风险清单」「产品定义」等自拟标题,也不得加「工程师视角」等后缀。
### role:leader
查范围是否越权到未批准内容;结论是否可追溯到前序 bizdev 批准件。
核对建议书章节标题是否与 template.md 完全一致;标题不对齐则判门禁失败并要求修复。
+10 -12
View File
@@ -54,25 +54,23 @@ func (q *QualityGate) Validate(wsDir string, taskKey string, content string) (*Q
CheckedAt: now,
}
// 1. 检查标题完整性 (对比 template.md 的 ## 章节)
// 1. 检查标题完整性 (对比 template.md 的 ## 章节;清洗后须精确相等,禁止自拟近义标题)
tmplPath := filepath.Join(wsDir, "template.md")
tmplData, err := os.ReadFile(tmplPath)
if err == nil {
expectedHeadings := extractHeadings(string(tmplData))
actualHeadings := extractHeadings(content)
actualSet := make(map[string]struct{}, len(actualHeadings))
for _, act := range actualHeadings {
actualSet[act] = struct{}{}
}
for _, h := range expectedHeadings {
found := false
for _, act := range actualHeadings {
if strings.Contains(act, h) || strings.Contains(h, act) {
found = true
break
}
}
if !found && !strings.Contains(content, h) {
report.MissingSections = append(report.MissingSections, h)
report.Violations = append(report.Violations, fmt.Sprintf("缺少必填章节: %s", h))
report.Score -= 15.0
if _, ok := actualSet[h]; ok {
continue
}
report.MissingSections = append(report.MissingSections, h)
report.Violations = append(report.Violations, fmt.Sprintf("缺少必填章节: %s", h))
report.Score -= 15.0
}
if len(report.MissingSections) > 0 {
report.TitleCheck = false
+30
View File
@@ -12,12 +12,23 @@ import (
var rdevReqIDPattern = regexp.MustCompile(`\b(UR|RS)-(\d+)\b`)
// rdProposalRequiredHeadings are the professional ## titles from templates/docs/rd_proposal.md.
// Exact cleaned match is required; fuzzy Contains is not enough for title alignment.
var rdProposalRequiredHeadings = []string{
"项目背景与立项依据",
"产品定义与范围",
"目标、里程碑与资源假设",
"主要风险与对策概览",
}
func rdevDeterministicViolations(wsDir, taskKey, content string) []string {
meta, ok := LookupRDevTask(taskKey)
if !ok {
return nil
}
switch taskKey {
case "rd_proposal":
return rdevProposalHeadingIssues(content)
case "rd_user_needs":
return rdevRequirementIssues(wsDir, content, "UR")
case "rd_system_reqs":
@@ -33,6 +44,25 @@ func rdevDeterministicViolations(wsDir, taskKey, content string) []string {
}
}
func rdevProposalHeadingIssues(content string) []string {
// Require literal "## 标题" lines (no numbering / engineer-suffix variants).
var issues []string
for _, want := range rdProposalRequiredHeadings {
exact := "## " + want
found := false
for _, line := range strings.Split(content, "\n") {
if strings.TrimSpace(line) == exact {
found = true
break
}
}
if !found {
issues = append(issues, "建议书章节标题未对齐模板: "+want)
}
}
return issues
}
func rdevRequirementIssues(wsDir, content, prefix string) []string {
var issues []string
if strings.Contains(content, "随机抽取") {
+16
View File
@@ -133,6 +133,22 @@ func TestRDevQualityGates(t *testing.T) {
}
}
proposalSample, err := os.ReadFile("../../.doc/test-reports/assets/2026-09-19-rdev-p3-real-pi/rd_proposal-final.md")
if err != nil {
t.Fatal(err)
}
if issues := rdevProposalHeadingIssues(string(proposalSample)); len(issues) == 0 {
t.Fatal("P3 rd_proposal dry fixture must fail title alignment (self-authored headings)")
}
aligned := "## 结论\n\nok\n\n## 适用性\n\nok\n\n## 项目背景与立项依据\n\nok\n\n## 产品定义与范围\n\nok\n\n## 目标、里程碑与资源假设\n\nok\n\n## 主要风险与对策概览\n\nok\n\n## 追溯\n\n| RD- | |\n\n## 已有材料摘要\n\nok\n\n## 待确认清单\n\n- 无\n\n## 签署\n\nok\n\n## 说明\n\nok\n"
if issues := rdevProposalHeadingIssues(aligned); len(issues) != 0 {
t.Fatalf("template-aligned proposal must pass heading gate: %v", issues)
}
fuzzy := strings.ReplaceAll(aligned, "## 主要风险与对策概览", "## 风险清单")
if got := rdevProposalHeadingIssues(fuzzy); len(got) == 0 {
t.Fatal("renamed risk heading must fail exact title alignment")
}
thin := "已收照片 1 张。"
rep, err = gate.Validate(ws, "rd_prototype_photos", thin)
if err != nil {
+3 -1
View File
@@ -18,7 +18,9 @@ type rdevOverrideStep struct {
var rdevStageHints = map[string]rdevStageHint{
"rd_proposal": {
Hint: "按模板编制《项目建议书》(策划)。写满章节并给出本阶段结论与追溯;缺项写「待确认」,不做疗效宣称。",
Hint: "按模板编制《项目建议书》(策划)。## 标题必须与 template.md 完全一致:" +
"「项目背景与立项依据」「产品定义与范围」「目标、里程碑与资源假设」「主要风险与对策概览」;" +
"禁止改写为「风险清单」等自拟标题。写满章节并给出本阶段结论与追溯;缺项写「待确认」,不做疗效宣称。",
Aliases: []string{"项目建议书", "立项建议"},
},
"rd_design_plan": {
+9 -4
View File
@@ -26,9 +26,9 @@ RUNNER_URL="${PI_AGENT_RUNNER_URL:-http://127.0.0.1:18865}"
TOKEN="${PI_AGENT_RUNNER_AUTH_TOKEN:-${RUNNER_AUTH_TOKEN:-}}"
MODEL_ID="${PI_AGENT_MODEL:-MiniMax-M2.7-highspeed}"
PROTOCOL="${PI_AGENT_PROTOCOL:-typed-result-v1}"
TIMEOUT="${NIGHTLY_SUITE_TIMEOUT:-90m}"
TIMEOUT="${NIGHTLY_SUITE_TIMEOUT:-180m}"
COMMIT="$(git rev-parse HEAD)"
SUITE="nightly-real-pi-u8-fullnone"
SUITE="nightly-real-pi-u8-four-workflows"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
EVIDENCE_DIR="${NIGHTLY_EVIDENCE_DIR:-${ROOT}/.doc/test-reports/assets/promotion-receipts/evidence-${COMMIT:0:12}-${STAMP}}"
mkdir -p "${EVIDENCE_DIR}"
@@ -172,7 +172,9 @@ preflight_shared_kb
# Clear env pollution that flakes short/config tests elsewhere.
unset AGENT_TIMEOUT KB_DATABASE_URL KB_EMBEDDING_DIM 2>/dev/null || true
RUN_FILTER='TestMultiIntentU8_RealPI_ConfirmGenerate_PG|TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain'
# Four workflow governance chains (main / product_manager / test_manager / FullNone)
# plus U8 confirm-generate. Rdev P3 remains the Python harness (not yet a Go PG top-level).
RUN_FILTER='TestMultiIntentU8_RealPI_ConfirmGenerate_PG|TestWorkflowGovernance_PiAgent_PG_(FullChain|ProductManager_FullChain|TestManager_FullChain|FullNoneMixedChain)$'
LOG_FILE="${EVIDENCE_DIR}/go-test.txt"
set +e
go test -count=1 -timeout "${TIMEOUT}" -v ./internal/server/ -run "${RUN_FILTER}" 2>&1 | tee "${LOG_FILE}"
@@ -190,9 +192,12 @@ fi
set -e
if [[ "${TEST_RC}" -eq 0 ]]; then
# Exit zero alone also covers no tests and partial skips; require both named tests.
# Exit zero alone also covers no tests and partial skips; require all named top-level tests.
if ! python3 "${ROOT}/scripts/verify_go_test_evidence.py" --log "${RESULT_LOG}" \
--expected TestMultiIntentU8_RealPI_ConfirmGenerate_PG \
--expected TestWorkflowGovernance_PiAgent_PG_FullChain \
--expected TestWorkflowGovernance_PiAgent_PG_ProductManager_FullChain \
--expected TestWorkflowGovernance_PiAgent_PG_TestManager_FullChain \
--expected TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain \
> "${EVIDENCE_DIR}/expected-tests.json"; then
write_receipt infra_fail infra "${LOG_FILE}" "${EVIDENCE_DIR}/expected-tests.json"