Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d73d2e8633 | ||
|
|
7f0f8a1ca2 | ||
|
|
fa8598e89b | ||
|
|
e78b6c1470 | ||
|
|
c9fd8a4d7f | ||
|
|
e488d10840 | ||
|
|
4606119581 | ||
|
|
149dec8993 | ||
|
|
fa2407f92c | ||
|
|
c2efcdbacd | ||
|
|
111f25c9a8 | ||
|
|
feefc322a7 | ||
|
|
3978e6b461 |
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: "修复 CreateOrGetDocument 可选职责 0→NULL FK"
|
||||
status: done
|
||||
created: 2026-09-17
|
||||
updated: 2026-09-17
|
||||
owner: Cursor
|
||||
branch: fix/docgov-optional-duty-null-fk
|
||||
worktree: "-"
|
||||
mutex: docgov
|
||||
---
|
||||
|
||||
# 修复 CreateOrGetDocument 可选职责 0→NULL FK
|
||||
|
||||
## 目标
|
||||
|
||||
无 reviewer/approver 职责时创建文档账本成功,不再因 `reviewer_id=0` FK 失败并被标成 `INTERNAL_ERROR`。
|
||||
|
||||
## 范围
|
||||
|
||||
- `internal/docgov/engine.go`:`nullIfZero` 写入 author/reviewer/approver
|
||||
- `internal/stream/classify.go`:`DOC_LEDGER_ERROR` 分型
|
||||
- 回归测试 + audit/test-report
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不强制项目配置 reviewer
|
||||
- 不改前端 flushSync / 虚拟列表
|
||||
- 不改项目 6417 存量数据(代码修好后重试即可)
|
||||
|
||||
## 步骤
|
||||
|
||||
1. [x] CreateOrGetDocument 0→NULL
|
||||
2. [x] ClassifyError `DOC_LEDGER_ERROR`
|
||||
3. [x] 单元/回归测试(`-short` 已绿)
|
||||
4. [x] 占集成槽位 rebuild + 6417 复测(`run_1789625396102207018`,`doc_proj6417_intake` NULL FK)
|
||||
5. [x] test-report 归档;合入 `dev`
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [x] `go test -short ./internal/docgov/... ./internal/stream/...` PASS
|
||||
- [x] 仅-author 项目 `CreateOrGetDocument` / 任务发起不再 `INTERNAL_ERROR`;`reviewer_id IS NULL`
|
||||
- [x] 项目 6417 账本创建成功(full 职责路径未被本修复改动)
|
||||
|
||||
## 相关链接
|
||||
|
||||
- Audit: [../../audit/2026-09-17-docgov-optional-duty-null-fk.md](../../audit/2026-09-17-docgov-optional-duty-null-fk.md)
|
||||
- Test report: [../../test-reports/2026-09-17-docgov-optional-duty-null-fk-test-report.md](../../test-reports/2026-09-17-docgov-optional-duty-null-fk-test-report.md)
|
||||
|
||||
## 证据 / 备注
|
||||
|
||||
- 失败 run:`run_1789622069585300787`
|
||||
- 验证 run:`run_1789625396102207018` / `doc_proj6417_intake` author=9 reviewer/approver NULL
|
||||
- merge:`e488d10 merge: fix/docgov-optional-duty-null-fk`
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: "可选职责空审核人 CreateOrGetDocument 写 reviewer_id=0 触发 FK"
|
||||
status: done
|
||||
created: 2026-09-17
|
||||
updated: 2026-09-17
|
||||
severity: high
|
||||
---
|
||||
|
||||
# 可选职责空审核人 CreateOrGetDocument 写 reviewer_id=0 触发 FK
|
||||
|
||||
## 现象
|
||||
|
||||
dev 项目「内廷元无创神经刺激器」(6417) 发起 `intake` 任务后 UI 显示:
|
||||
|
||||
- `code: INTERNAL_ERROR`
|
||||
- `userMessage: 系统内部错误,请重新开始会话`
|
||||
- `requestId: run_1789622069585300787`
|
||||
- `sessionId: sess-p6417-u9`
|
||||
|
||||
浏览器偶发 `flushSync was called from inside a lifecycle method`(错误态重渲染副作用,非根因)。
|
||||
|
||||
## 根因分析
|
||||
|
||||
1. app-dev 日志:`persist document ledger: ... document_records_reviewer_id_fkey (SQLSTATE 23503)`
|
||||
2. 项目成员仅 `document_duty=author`(user 9),无 reviewer/approver
|
||||
3. `resolveTaskDocumentDuties` 合法返回 `ReviewerID=0`
|
||||
4. [`internal/docgov/engine.go`](../../internal/docgov/engine.go) `CreateOrGetDocument` 把 `0` 写入可空 FK,PG 拒绝(无 `users.id=0`)
|
||||
5. `ClassifyError` 默认分支掩盖为 `INTERNAL_ERROR`
|
||||
|
||||
对照:[`internal/docduty/store.go`](../../internal/docduty/store.go) heal 路径已有 `nullIfZero`,**创建路径漏改**。
|
||||
|
||||
复现:`INSERT ... reviewer_id=0` → 同 FK 错误。近 7 天同因至少 3 次(6417/6359/6274)。
|
||||
|
||||
## 严重度
|
||||
|
||||
`high`(任何无 reviewer 职责的项目首次落文档账本均会失败;可扩散至 test/main)
|
||||
|
||||
## 关联整改
|
||||
|
||||
- Short-term: [../archive/ops/2026-09-17-docgov-optional-duty-null-fk.md](../archive/ops/2026-09-17-docgov-optional-duty-null-fk.md)
|
||||
- Fix branch: `fix/docgov-optional-duty-null-fk`
|
||||
- Test report: [../test-reports/2026-09-17-docgov-optional-duty-null-fk-test-report.md](../test-reports/2026-09-17-docgov-optional-duty-null-fk-test-report.md)
|
||||
|
||||
## 证据
|
||||
|
||||
- `docker logs shuimu-agent-app-dev` @ `2026-09-17T05:14:29Z` run_1789622069585300787
|
||||
- `task_runs.error_message` 与上述一致;`document_records` 对 project 6417 为 0 行
|
||||
- 手工 `INSERT reviewer_id=0` → `Key (reviewer_id)=(0) is not present in table "users"`
|
||||
|
||||
## 后续
|
||||
|
||||
- 创建路径 0→NULL;ClassifyError 增加 `DOC_LEDGER_ERROR`
|
||||
- 不强制配置 reviewer(保持可选职责语义)
|
||||
- flushSync 另开任务
|
||||
@@ -13,6 +13,14 @@
|
||||
|
||||
## 核心文档(按阅读顺序推荐)
|
||||
|
||||
### 可选职责空审核人 CreateOrGetDocument FK(2026-09-17)
|
||||
|
||||
**文件**: [2026-09-17-docgov-optional-duty-null-fk.md](./2026-09-17-docgov-optional-duty-null-fk.md)
|
||||
|
||||
**结论**: 无 reviewer 职责时 `CreateOrGetDocument` 写 `reviewer_id=0` 触发 FK,被 `ClassifyError` 掩盖为 `INTERNAL_ERROR`;heal 路径已有 `nullIfZero`,创建路径漏改。
|
||||
|
||||
**整改**: [short-term](../archive/ops/2026-09-17-docgov-optional-duty-null-fk.md) · branch `fix/docgov-optional-duty-null-fk`
|
||||
|
||||
### FEATURE-MATRIX production-ready 连续性审计(2026-09-16)
|
||||
|
||||
**文件**: [2026-09-16-feature-matrix-production-readiness-audit.md](./2026-09-16-feature-matrix-production-readiness-audit.md)
|
||||
|
||||
+8
-6
@@ -1,18 +1,20 @@
|
||||
# 当前焦点
|
||||
|
||||
**更新**: 2026-09-17
|
||||
**production_ready**: yes(门禁 B1–B4 已签;dev `schema_version=48`;test/main `schema_version=47`;goose 00048 bootstrap-fields 已在 dev 可迁移。不把 fallback change report 写成完整 PI)
|
||||
**production_ready**: yes(门禁 B1–B4 已签;dev/test `schema_version=48`;main `schema_version=47`;goose 00048 已在 test 迁移。不把 fallback change report 写成完整 PI)
|
||||
|
||||
## 焦点
|
||||
|
||||
1. **partial/deferred 全量收口**:W1–W4 **done**;W5a **done**(矩阵自举行仍 partial);W6 **shadow done**(矩阵 FAV 仍 partial);W7 **盘点 done**(矩阵 D7 仍 deferred)。晋升 `test`:`rc-v1.0.0-beta.3`
|
||||
2. **运维**:nightly `DATA_DIR=dev` 已对齐;KB 宿主机分层加载 / fail-closed **done**(`fix/kb-host-auth-layered-load`)
|
||||
1. **promote null-fk → test**:`rc-v1.0.0-beta.4`(docgov optional-duty FK 修复)
|
||||
2. **partial/deferred 全量收口**:W1–W4 **done**;W5a **done**(矩阵自举行仍 partial);W6 **shadow done**(矩阵 FAV 仍 partial);W7 **盘点 done**(矩阵 D7 仍 deferred)。已晋升:`rc-v1.0.0-beta.3` → 本轮 `beta.4`
|
||||
3. **红线**:禁止整支 merge FAV;不默认升 `MULTI_INTENT_PLAN_TIER=full`(升 `confirm` 须 W4 闭环后独立授权);`SEMANTIC_SUMMARY_REPLACE` 不随 W2 开启;FAV live 顺序 staging→artifact→publication、每级观察窗证据驱动(下限 ≥7 天)、`FAV_DEGRADED_READ=false`;flag 默认 off;项目 143 只读
|
||||
|
||||
## 并行认领
|
||||
|
||||
| owner | task | branch | worktree | mutex | status |
|
||||
|-------|------|--------|----------|-------|--------|
|
||||
| Cursor | 2026-09-17-promote-null-fk-to-test | feature/promote-null-fk-to-test | - | infra | in_progress |
|
||||
| Cursor | 2026-09-17-docgov-optional-duty-null-fk | fix/docgov-optional-duty-null-fk | - | docgov | done |
|
||||
| Cursor | 2026-09-17-leftover-hygiene | chore/leftover-hygiene-2026-09-17 | - | ops | done |
|
||||
| Cursor | 2026-09-17-w2-semantic-summary-shadow | feature/w2-semantic-summary-shadow-live | - | chat | done |
|
||||
| Cursor | 2026-09-17-w3-pi-change-report-narrative | feature/w3-pi-change-report-narrative-live | - | docgov | done |
|
||||
@@ -20,7 +22,7 @@
|
||||
| Cursor | 2026-09-17-w5a-software-bootstrap-ui | feature/w5a-software-bootstrap-ui-pi | - | workflow-design | done |
|
||||
| Cursor | 2026-09-17-w6-fav-subscriber-shadow | feature/w6-fav-subscriber-shadow | - | events | done |
|
||||
| Cursor | 2026-09-17-w7-d7-inventory | feature/w7-d7-data-inventory | - | chat | done |
|
||||
| Cursor | 2026-09-17-promote-dev-to-test | feature/promote-dev-to-test-2026-09-17 | - | infra | in_progress |
|
||||
| Cursor | 2026-09-17-promote-dev-to-test | feature/promote-dev-to-test-2026-09-17 | - | infra | done |
|
||||
| Cursor | 2026-09-17-kb-host-auth-fix | fix/kb-host-auth-layered-load | - | infra | done |
|
||||
| Cursor | 2026-09-17-w1-ci-e2e-green-nightly | feature/w1-ci-e2e-green-nightly | - | ci | done |
|
||||
| Cline | 2026-09-16-partial-deferred-completion | docs/partial-deferred-completion-plan | - | architecture-docs | in_progress |
|
||||
@@ -119,7 +121,7 @@
|
||||
|
||||
| holder | phase | since | scope | queue |
|
||||
|--------|-------|-------|-------|-------|
|
||||
| Cursor | merging | 2026-09-17 | promote dev → test rc-v1.0.0-beta.3 | - |
|
||||
| Cursor | merging | 2026-09-17 | promote null-fk → test (rc-v1.0.0-beta.4) | - |
|
||||
|
||||
**phase**: `idle` | `merging` | `smoke` | `e2e`
|
||||
|
||||
@@ -130,7 +132,7 @@
|
||||
| 环境 | 端口 | schema | 状态 |
|
||||
|------|------|--------|------|
|
||||
| dev | 8080 | **48** | healthy(`pi-agent` typed-result-v1;events worker `:8088`;notify/advance/projection live、fav off;health+ready PASS;demo 6001–6010 retired) |
|
||||
| test | 8081 | **47** | healthy(`rc-v1.0.0-beta.2`;pi-agent `:18866`;events `:8089`;notify/advance/projection live、fav off;health+ready PASS;矩阵深度测试 live 项目 3 PASS) |
|
||||
| test | 8081 | **48** | healthy(`rc-v1.0.0-beta.3`;pi-agent `:18866`;events `:8089`;notify/advance/projection live、fav off;health+ready PASS;goose 00048 已迁移) |
|
||||
| main | 8000 | **47** | healthy(app + events + runner + DB;与 dev/test 对齐) |
|
||||
|
||||
## SSOT
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "晋升 dev → test(rc-v1.0.0-beta.3)"
|
||||
status: in_progress
|
||||
status: done
|
||||
created: 2026-09-17
|
||||
updated: 2026-09-17
|
||||
owner: Cursor
|
||||
@@ -34,16 +34,16 @@ mutex: infra
|
||||
## 步骤
|
||||
|
||||
1. [x] 确认 gap-matrix `production_ready=yes`;FAV/D7/SWBOOT 为 deferred_enhancement
|
||||
2. [ ] 占集成槽;合入 `test` + tag
|
||||
3. [ ] rebuild 测试服务
|
||||
4. [ ] health/ready + schema 冒烟
|
||||
2. [x] 占集成槽;合入 `test` + tag
|
||||
3. [x] rebuild 测试服务
|
||||
4. [x] health/ready + schema 冒烟
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] `:8081` health/ready PASS
|
||||
- [ ] schema 到达 48(00048 additive)
|
||||
- [ ] FAV 仍 off
|
||||
- [ ] 默认 flag 仍 off
|
||||
- [x] `:8081` health/ready PASS
|
||||
- [x] schema 到达 48(00048 additive)
|
||||
- [x] FAV 仍 off
|
||||
- [x] 默认 flag 仍 off
|
||||
|
||||
## 相关链接
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 整改缺口复核矩阵(SSOT)
|
||||
|
||||
**更新**: 2026-09-16
|
||||
**分支**: dev
|
||||
**production_ready**: yes(发布门禁 B1–B4 已签;dev/test/main schema=47。U9–U12 / 软件自举 / FAV / 完整 PI change-report 叙事不挡首发)
|
||||
**更新**: 2026-09-17
|
||||
**分支**: test @ `rc-v1.0.0-beta.3`
|
||||
**production_ready**: yes(发布门禁 B1–B4 已签;dev/test `schema_version=48`;main `schema_version=47`。U9–U12 / 软件自举 / FAV / D7 不挡首发;FAV live 与 D7 删除仍非本门禁范围)
|
||||
**协作焦点**: [../ops/CURRENT.md](../ops/CURRENT.md)
|
||||
**Agent 手册**: [AGENTS.md](../../AGENTS.md) · [PROJECT.md](../../PROJECT.md)
|
||||
**能力矩阵**: [../status/FEATURE-MATRIX.md](../status/FEATURE-MATRIX.md)(Wave 0 校准;与本文件正交)
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
| PI change-report 叙事 | 只读消费 diff+lineage 的 PI 叙事;失败全量降级 fallback | done | deferred_enhancement | [Round20](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md);[go/no-go GO](../ops/research/2026-09-16-wave3-pu6-pu8-go-no-go.md);[pu7](../ops/long-term/2026-09-16-pu7-pi-narrative-contract.md);[脚手架](../test-reports/2026-09-16-pu7-pi-narrative-test-report.md);[W3 真 PI](../test-reports/2026-09-17-w3-pi-change-report-narrative-test-report.md) | **before**:flag 脚手架 + 仅 fallback。**after**:dev 可开 `PI_CHANGE_REPORT_NARRATIVE` 走产品 PI no-tool narrator(默认 off)。digest 绑定;畸形/超时全量 fallback。**不把 fallback 写成完整 PI** | `docgov` |
|
||||
| 前端工作台 | 三栏 + 块文档 + SSE;错误卡/进度/busy;版本冲突信封;demo seed 默认关;终止 preview | done | non_blocker | [workbench](../test-reports/2026-09-08-three-column-workbench-and-block-document-test-report.md);[双标签](../test-reports/assets/2026-09-15-conversation-intent-dual-tab/screenshots/tab-b-busy.png);[demo](../test-reports/2026-09-16-demo-seed-pollution-test-report.md);[B4](../test-reports/2026-09-16-frontend-gate-b4-test-report.md)(项目 6363) | **before**:空「缺口」却 partial → **after**:本行 done。其他 409 消费方信封复用另列 hardening backlog | `web` |
|
||||
| PI Runner / 配置链 | pi-agent + MiniMax + typed-result-v1 | done | non_blocker | [piagent](../test-reports/2026-09-13-codex-audit-p0-remediation-test-report.md);[container-env](../test-reports/2026-09-13-container-env-configuration-test-report.md);[typed-result](../test-reports/2026-09-15-pi-typed-result-v1-test-report.md) | — | `pi-agent` |
|
||||
| 三环境运维 | dev/test/main + 共享 KB;对话/业务库隔离 | done | non_blocker | [PROJECT.md](../../PROJECT.md) §4;CURRENT 环境快照 schema **47** | — | `infra` |
|
||||
| 三环境运维 | dev/test/main + 共享 KB;对话/业务库隔离 | done | non_blocker | [PROJECT.md](../../PROJECT.md) §4;CURRENT 环境快照 dev/test schema **48**、main **47** | — | `infra` |
|
||||
| CI / E2E | PR CI、PG 集成、治理 PG、已签真实 PI 门禁;**nightly** schedule/`workflow_dispatch` + infra/product 分类 receipt | done | 加固(阻止**下一次**晋升,不自动否定已发布) | [BUG-08](../test-reports/2026-09-16-ci-governance-pg-bug08-test-report.md);[PI 三工作流](../test-reports/2026-09-16-pi-three-workflow-e2e-test-report.md);[Wave2B](../test-reports/2026-09-16-wave2b-nightly-promotion-receipt-test-report.md);[Wave2C](../test-reports/2026-09-16-wave2c-same-image-digest-test-report.md);[W1 green](../test-reports/2026-09-17-w1-ci-e2e-green-nightly-test-report.md) + [receipt `6e2476f0…`](../assets/promotion-receipts/6e2476f0f2e408e111fe06ca7c31a38294a340b3.json) | **before**:partial(FullNone product_fail / secrets 文案过时)→ **after**:secrets 已配;duplicate goose 00048 已清;FullNone+U8 nightly `verdict=pass` | `ci` |
|
||||
| Artifact 同 digest 晋升 | 候选 commit 一次构建、按 digest 晋升、promotion receipt | done | 加固 | [计划 PU5B](../remediation/2026-09-16-feature-matrix-production-readiness-plan.md);[Wave2B](../test-reports/2026-09-16-wave2b-nightly-promotion-receipt-test-report.md);[Wave2C](../test-reports/2026-09-16-wave2c-same-image-digest-test-report.md) | **关闭**:same-image pin(`candidate-images-*` + `PINNED_IMAGE_MODE` + promote `--require-digests`)。**声明**:单机 retag/digest pin;远程 registry 推送另立任务 | `infra` |
|
||||
| 可靠事件核心 | PG dispatcher、notify/advance/projection live、生产闭包 hook 退出、Phase2 选择性解耦已合入 | done | non_blocker | [E0A–E2](../test-reports/2026-09-15-event-e0a-production-scope-contract-test-report.md) 等;[复盘](../ops/research/2026-09-15-track-e-retrospective.md);[shared live](../test-reports/2026-09-15-event-notify-shared-live-test-report.md);[phase2 closeout](../test-reports/2026-09-15-phase2-closeout-test-report.md) | **before**:与 FAV off 混成 partial → **after**:FAV 独立行。选择性后续解耦按收益触发 | `architecture-docs` / `events` |
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# CreateOrGetDocument 可选职责 0→NULL FK 测试报告
|
||||
|
||||
## 一、 测试背景 (Background)
|
||||
|
||||
dev 项目「内廷元无创神经刺激器」(6417) 发起 `intake` 任务时,UI 报 `INTERNAL_ERROR` /「系统内部错误,请重新开始会话」(`run_1789622069585300787`)。根因是可选审核职责为空时 `CreateOrGetDocument` 写入 `reviewer_id=0`,触发 `document_records_reviewer_id_fkey`,再被 `ClassifyError` 兜底掩盖。
|
||||
|
||||
## 二、 测试目标 (Test Objectives)
|
||||
|
||||
- 仅-author 项目创建文档账本成功,`reviewer_id`/`approver_id` 为 SQL NULL
|
||||
- 不再出现 `persist document ledger` / `INTERNAL_ERROR`(该路径)
|
||||
- ledger/FK 残差错误分型为 `DOC_LEDGER_ERROR`
|
||||
- 单元与 `-short` 回归通过
|
||||
|
||||
## 三、 测试方案与环境 (Test Plan & Environment)
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| OS | Linux |
|
||||
| 分支合入 | `dev` @ `merge: fix/docgov-optional-duty-null-fk` |
|
||||
| 环境 | agent-dev `:8080`,schema 48,真实 PI |
|
||||
| 层级 | 单元 / DB 集成 / 真实 task run 冒烟 |
|
||||
|
||||
## 四、 预期结果 (Expected Results)
|
||||
|
||||
1. `nullIfZero(0)==nil`;`CreateOrGetDocument(..., reviewer=0, approver=0)` 成功且 DB NULL
|
||||
2. `ClassifyError(persist document ledger ... 23503)` → `DOC_LEDGER_ERROR`
|
||||
3. 项目 6417 重试 `intake`:越过账本创建;`doc_proj6417_intake` 存在且 `rev_null/app_null=true`
|
||||
|
||||
## 五、 实际测试结果与判定 (Actual Results & Evaluation)
|
||||
|
||||
### 用例 1:单元 / short
|
||||
|
||||
- **输入**: `go test -short ./internal/docgov/... ./internal/stream/...`
|
||||
- **实际输出**: `ok`(含 `TestNullIfZero`、`TestClassifyError_DocumentLedgerFKIsLedgerError`)
|
||||
- **判定**: PASS
|
||||
- **耗时**: ~0.5s
|
||||
|
||||
### 用例 2:DB 集成 OptionalDutiesNullFK
|
||||
|
||||
- **输入**: `go test ./internal/docgov/ -run OptionalDutiesNullFK -count=1`
|
||||
- **实际输出**: `--- PASS: TestDocGov_CreateOrGetDocument_OptionalDutiesNullFK`
|
||||
- **判定**: PASS
|
||||
|
||||
### 用例 3:dev 真实任务(项目 6417)
|
||||
|
||||
- **输入**: admin 登录后 `POST /api/v1/tasks/runs`(`task_key=intake`,`session_id=sess-p6417-u9`,`project_id=6417`)
|
||||
- **实际输出**:
|
||||
- 创建响应:`{"run_id":"run_1789625396102207018","status":"running"}`
|
||||
- DB:`doc_proj6417_intake | author_id=9 | rev_null=t | app_null=t | drafting`
|
||||
- 未再出现 `persist document ledger` / `document_records_reviewer_id_fkey`
|
||||
- **判定**: PASS(账本 FK 根因已消除;任务可继续进入 PI 执行阶段)
|
||||
- **耗时**: 账本落盘在秒级完成(对比失败 run 立即 failed)
|
||||
|
||||
## 六、 自动化测试套件汇总 (Test Suite Execution)
|
||||
|
||||
```text
|
||||
go vet ./internal/docgov/... ./internal/stream/...
|
||||
go test -short ./internal/docgov/... ./internal/stream/...
|
||||
# ok docgov / stream
|
||||
./scripts/env-service.sh dev rebuild
|
||||
curl -sf http://127.0.0.1:8080/health # ok, schema_version=48
|
||||
```
|
||||
|
||||
## 七、 测试结论与后续优化建议 (Conclusion & Next Steps)
|
||||
|
||||
- **结论**: 达到合入与冒烟标准;可选职责空审核人不再因 FK=0 导致 `INTERNAL_ERROR`。
|
||||
- **后续**: 前端 `flushSync` 警告另开任务;全链路 PI 完成质量属独立验收,不阻塞本修复。
|
||||
@@ -0,0 +1,66 @@
|
||||
# 晋升 dev → test(rc-v1.0.0-beta.3)测试报告
|
||||
|
||||
## 一、 测试背景 (Background)
|
||||
|
||||
gap-matrix `production_ready=yes`;门禁 B1–B4 已签。本地 `dev` 已含 leftover 收口与 W2–W7,test 仍停在 `rc-v1.0.0-beta.2` / schema 47。需要把候选合入 `test` 并重建服务,供部署后实测。FAV/D7/SWBOOT 的 `partial`/`deferred` 是 deferred_enhancement,不挡本轮晋升。
|
||||
|
||||
## 二、 测试目标 (Test Objectives)
|
||||
|
||||
- 确认发布口径仍为 production-ready
|
||||
- 本地 `test` 合入当前 `dev`,tag `rc-v1.0.0-beta.3`
|
||||
- 重建 test 服务(不 pin beta.2 旧镜像)
|
||||
- `:8081` health/ready;schema 48;FAV off
|
||||
|
||||
## 三、 测试方案与环境 (Test Plan & Environment)
|
||||
|
||||
| 项 | 值 |
|
||||
|----|----|
|
||||
| 源 | 本地 `dev@4fce16c`(含 W7 盘点 + 晋升占槽文档) |
|
||||
| 目标 | 本地 `test` + tag `rc-v1.0.0-beta.3` |
|
||||
| 部署 | `./scripts/env-service.sh test rebuild`(`PINNED_IMAGE_MODE=0`,仅本机 images 文件,未入库) |
|
||||
| 证据 | `.doc/test-reports/assets/2026-09-17-promote-dev-to-test/` |
|
||||
|
||||
`./scripts/promote-to-test.sh` 因缺 `dev@46a0fb2` nightly receipt 被 skip(与 beta.2 晋升相同:手工 `--no-ff` + annotated tag)。未合 `main`。
|
||||
|
||||
## 四、 预期结果 (Expected Results)
|
||||
|
||||
- health/ready `env=test`、`schema_version=48`
|
||||
- vault `consumer_mode=off`、`degraded_read=false`
|
||||
- events `:8089` ok
|
||||
|
||||
## 五、 实际测试结果与判定 (Actual Results & Evaluation)
|
||||
|
||||
### 5.1 发布口径
|
||||
|
||||
- **测试输入 (Input)**:CURRENT / FEATURE-MATRIX / gap-matrix
|
||||
- **实际输出 (Actual Output)**:`production_ready=yes`;B1–B4 已签;FAV/D7/SWBOOT 标明 deferred_enhancement
|
||||
- **评价判定 (Evaluation / Verdict)**:PASS(允许晋升 test,非升 main)
|
||||
- **性能与耗时 (Duration & Latency)**:N/A
|
||||
|
||||
### 5.2 合入与 tag
|
||||
|
||||
- **测试输入 (Input)**:`git checkout test && git merge --no-ff dev`;`git tag -a rc-v1.0.0-beta.3`
|
||||
- **实际输出 (Actual Output)**:`test@e5e0956`;tag `rc-v1.0.0-beta.3`。见 `test-head.txt`
|
||||
- **评价判定 (Evaluation / Verdict)**:PASS
|
||||
- **性能与耗时 (Duration & Latency)**:<1s
|
||||
|
||||
### 5.3 rebuild + 冒烟
|
||||
|
||||
- **测试输入 (Input)**:`./scripts/env-service.sh test rebuild`;`curl :8081/health` `/ready`;`:8089/health`;vault status
|
||||
- **实际输出 (Actual Output)**:
|
||||
|
||||
```json
|
||||
{"status":"ok","env":"test","agent_backend":"pi-agent","version":"v1.0","shared_kb_port":5432,"kb_ready":true,"database_ready":true,"schema_version":48,"schema_ready":true,"task_store_ready":true}
|
||||
```
|
||||
|
||||
vault:`consumer_mode=off`、`degraded_read=false`、`live_not_opened=true`。events:`{"env":"test","owner":"events-1","status":"ok"}`。
|
||||
- **评价判定 (Evaluation / Verdict)**:PASS
|
||||
- **性能与耗时 (Duration & Latency)**:rebuild ~10s(缓存);health <1s
|
||||
|
||||
## 六、 自动化测试套件汇总 (Test Suite Execution)
|
||||
|
||||
本波为环境晋升冒烟,不以 stub 冒充真实 PI 验收。W2–W5a 真实 PI 证据仍在各波 test-report。`go test -short` 已在 W6/W7 合入时 PASS。
|
||||
|
||||
## 七、 测试结论与后续优化建议 (Conclusion & Next Steps)
|
||||
|
||||
test 服务已更新到 `rc-v1.0.0-beta.3`,schema **48**,可进行部署后实测。默认 flag 仍 off;FAV 仍 off。未推远端、未合 `main`。缺少当前 HEAD 的 nightly same-image receipt,正式升 main 前须补 receipt。
|
||||
@@ -0,0 +1 @@
|
||||
{"env":"test","owner":"events-1","status":"ok"}
|
||||
@@ -0,0 +1 @@
|
||||
{"status":"ok","env":"test","agent_backend":"pi-agent","version":"v1.0","shared_kb_port":5432,"kb_ready":true,"database_ready":true,"schema_version":48,"schema_ready":true,"task_store_ready":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"status":"ok","env":"test","agent_backend":"pi-agent","version":"v1.0","shared_kb_port":5432,"kb_ready":true,"database_ready":true,"schema_version":48,"schema_ready":true,"task_store_ready":true}
|
||||
@@ -0,0 +1,2 @@
|
||||
e5e0956
|
||||
rc-v1.0.0-beta.3
|
||||
@@ -0,0 +1 @@
|
||||
{"consumer":"fav","consumer_mode":"off","degraded_read":false,"live_not_opened":true,"note":"fav consumer remains OFF until separate approval; shadow computes intended outcome only; full FAV branch not merged","publisher_enabled":false,"publisher_running":false,"shadow_observe":true,"status":"ok","store_configured":false,"subscriber_default":false,"would_publish":false,"would_stage":false}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -13,6 +14,15 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// nullIfZero maps unset duty holders (0) to SQL NULL so nullable FK columns
|
||||
// (author_id/reviewer_id/approver_id → users.id) accept optional duties.
|
||||
func nullIfZero(id int64) any {
|
||||
if id <= 0 {
|
||||
return nil
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// ModificationCheckResult 智能体或人员发起修改前的认领与授权判定结果
|
||||
type ModificationCheckResult struct {
|
||||
AllowedDirectEdit bool `json:"allowed_direct_edit"` // 是否允许直接执行修改
|
||||
@@ -153,12 +163,15 @@ func (e *GovernanceEngine) CreateOrGetDocument(
|
||||
}
|
||||
|
||||
ensureUser := func(uid int64, uname, urole string) {
|
||||
if uid > 0 {
|
||||
_, _ = e.pool.Exec(ctx, `
|
||||
INSERT INTO users (id, account_id, name, role)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (id) DO NOTHING
|
||||
`, uid, fmt.Sprintf("acc_%d", uid), uname, urole)
|
||||
if uid <= 0 {
|
||||
return
|
||||
}
|
||||
if _, err := e.pool.Exec(ctx, `
|
||||
INSERT INTO users (id, account_id, name, role)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (id) DO NOTHING
|
||||
`, uid, fmt.Sprintf("acc_%d", uid), uname, urole); err != nil {
|
||||
log.Printf("[docgov] ensureUser id=%d role=%s failed: %v", uid, urole, err)
|
||||
}
|
||||
}
|
||||
ensureUser(doc.AuthorID, doc.AuthorName, "engineer")
|
||||
@@ -185,7 +198,8 @@ func (e *GovernanceEngine) CreateOrGetDocument(
|
||||
err := e.pool.QueryRow(ctx, query,
|
||||
doc.DocID, doc.ProjectID, doc.TaskKey, doc.Title, doc.Stage, doc.Version,
|
||||
string(doc.CurrentState), doc.IsLocked, doc.IsTemplateOnly, doc.WorkDir,
|
||||
doc.AuthorID, doc.AuthorName, doc.ReviewerID, doc.ReviewerName, doc.ApproverID, doc.ApproverName,
|
||||
nullIfZero(doc.AuthorID), doc.AuthorName, nullIfZero(doc.ReviewerID), doc.ReviewerName,
|
||||
nullIfZero(doc.ApproverID), doc.ApproverName,
|
||||
cfgJSON, doc.CreatedAt, doc.UpdatedAt,
|
||||
).Scan(&id, &currState, &isLocked, &isTmpl, &authClaim, &revClaim, &appClaim)
|
||||
if err != nil {
|
||||
|
||||
@@ -97,3 +97,64 @@ func TestDocGov_DatabaseIntegration(t *testing.T) {
|
||||
t.Fatalf("expected doc locked in reviewing")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDocGov_CreateOrGetDocument_OptionalDutiesNullFK ensures reviewer/approver=0
|
||||
// persist as SQL NULL (optional DutyMode) instead of FK-violating 0.
|
||||
func TestDocGov_CreateOrGetDocument_OptionalDutiesNullFK(t *testing.T) {
|
||||
cfg, err := config.Load(".env")
|
||||
if err != nil || cfg == nil || cfg.ContextDbHost == "" {
|
||||
t.Skip("Skipping DB integration test: context DB not configured")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pool, err := pgxpool.New(ctx, cfg.ContextDSN())
|
||||
if err != nil || pool.Ping(ctx) != nil {
|
||||
t.Skipf("Skipping DB integration test: cannot connect to %s: %v", cfg.ContextDSN(), err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
engine := docgov.NewGovernanceEngine(pool)
|
||||
testDocID := fmt.Sprintf("doc_opt_duty_%d", time.Now().UnixNano())
|
||||
projectID := time.Now().UnixNano()%900000 + 100000
|
||||
|
||||
doc, err := engine.CreateOrGetDocument(
|
||||
ctx,
|
||||
testDocID,
|
||||
projectID,
|
||||
"intake",
|
||||
"可选职责空审核人",
|
||||
"V0.5_feasibility",
|
||||
"",
|
||||
9, "系统管理员",
|
||||
0, "",
|
||||
0, "",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateOrGetDocument with reviewer/approver=0 failed: %v", err)
|
||||
}
|
||||
if doc.DocID != testDocID {
|
||||
t.Fatalf("unexpected docID: %s", doc.DocID)
|
||||
}
|
||||
|
||||
var reviewerNull, approverNull bool
|
||||
var authorID int64
|
||||
err = pool.QueryRow(ctx, `
|
||||
SELECT author_id,
|
||||
reviewer_id IS NULL,
|
||||
approver_id IS NULL
|
||||
FROM document_records WHERE doc_id = $1
|
||||
`, testDocID).Scan(&authorID, &reviewerNull, &approverNull)
|
||||
if err != nil {
|
||||
t.Fatalf("query document_records: %v", err)
|
||||
}
|
||||
if authorID != 9 {
|
||||
t.Fatalf("author_id=%d want 9", authorID)
|
||||
}
|
||||
if !reviewerNull || !approverNull {
|
||||
t.Fatalf("expected NULL reviewer/approver, got reviewerNull=%v approverNull=%v", reviewerNull, approverNull)
|
||||
}
|
||||
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM document_records WHERE doc_id = $1`, testDocID)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package docgov
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNullIfZero(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := nullIfZero(0); got != nil {
|
||||
t.Fatalf("nullIfZero(0)=%v want nil", got)
|
||||
}
|
||||
if got := nullIfZero(-1); got != nil {
|
||||
t.Fatalf("nullIfZero(-1)=%v want nil", got)
|
||||
}
|
||||
if got := nullIfZero(9); got != int64(9) {
|
||||
t.Fatalf("nullIfZero(9)=%v want 9", got)
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,12 @@ func ClassifyError(err error, phaseID, requestID, sessionID, workspaceID string)
|
||||
info.UserMessage = "该文档正在由项目助手或其他用户编辑中;当前请求未执行,请等待现有任务完成"
|
||||
info.Actions = []string{}
|
||||
|
||||
case isDocumentLedgerError(msg):
|
||||
info.Code = "DOC_LEDGER_ERROR"
|
||||
info.Class = ErrorClassFatal
|
||||
info.UserMessage = "文档职责账本写入失败,请重新开始会话;若反复出现请联系管理员"
|
||||
info.Actions = []string{"restart"}
|
||||
|
||||
case isTimeoutError(msg):
|
||||
info.Code = "CHAT_TIMEOUT"
|
||||
info.Class = ErrorClassTransient
|
||||
@@ -74,6 +80,15 @@ func containsInternalLeak(s string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isDocumentLedgerError(msg string) bool {
|
||||
return strings.Contains(msg, "persist document ledger") ||
|
||||
strings.Contains(msg, "sqlstate 23503") ||
|
||||
strings.Contains(msg, "foreign key constraint") ||
|
||||
strings.Contains(msg, "document_records_reviewer_id_fkey") ||
|
||||
strings.Contains(msg, "document_records_author_id_fkey") ||
|
||||
strings.Contains(msg, "document_records_approver_id_fkey")
|
||||
}
|
||||
|
||||
func isTimeoutError(msg string) bool {
|
||||
return strings.Contains(msg, "timeout") ||
|
||||
strings.Contains(msg, "deadline") ||
|
||||
|
||||
@@ -99,3 +99,24 @@ func TestClassifyError_DocumentBusyIsVisibleStateWithoutRetry(t *testing.T) {
|
||||
t.Fatalf("busy state must not encourage duplicate retry, actions=%v", info.Actions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyError_DocumentLedgerFKIsLedgerError(t *testing.T) {
|
||||
err := errors.New(`persist document ledger: ERROR: insert or update on table "document_records" violates foreign key constraint "document_records_reviewer_id_fkey" (SQLSTATE 23503)`)
|
||||
info := ClassifyError(err, "", "run_1", "sess-p6417-u9", "")
|
||||
if info.Code != "DOC_LEDGER_ERROR" {
|
||||
t.Fatalf("code=%s", info.Code)
|
||||
}
|
||||
if info.Class != ErrorClassFatal {
|
||||
t.Fatalf("class=%s", info.Class)
|
||||
}
|
||||
if len(info.Actions) != 1 || info.Actions[0] != "restart" {
|
||||
t.Fatalf("actions=%v", info.Actions)
|
||||
}
|
||||
if !strings.Contains(info.UserMessage, "文档职责账本") {
|
||||
t.Fatalf("user_message=%q", info.UserMessage)
|
||||
}
|
||||
// SQL detail must not leak into user-facing fields (Upstream may be stripped by leak filter).
|
||||
if strings.Contains(info.UserMessage, "SQLSTATE") || strings.Contains(info.UserMessage, "foreign key") {
|
||||
t.Fatalf("user message leaked SQL: %q", info.UserMessage)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user