diff --git a/.doc/audit/ISSUES.md b/.doc/audit/ISSUES.md index 17927579..40a92c87 100644 --- a/.doc/audit/ISSUES.md +++ b/.doc/audit/ISSUES.md @@ -2,11 +2,11 @@ 维护规则:[问题记录与开发经验闭环](../ops/2026-09-23-issue-learning-workflow.md)。每个问题一个稳定 ID;主记录是事实与状态来源,本表为可检索投影。新发现及时登记,修复后保留记录;发布 blocker 仍见 [gap-matrix](../remediation/gap-matrix.md)。 -更新:2026-09-28。非编制人触发小队账本提交被 `uow: permission denied` 拒绝(fixing,待 dev 重跑)。test 注册 INTERNAL:标准角色 role-guest 缺失导致 FK 23503,已补种并重启 test(goose 77,resolved)。重复用户名 400 在新代码单测通过;当前 rc 二进制仍返回 INTERNAL,随下次含本提交的 test 制品生效。管理台重置密码入口被 F4-A 误删(端点仍在),已恢复 UI(resolved)。2026-09-27。上传链路:登记并修复上传文档跨端不可访问(占位模板退化 + 元数据/正文不落服务端,fixing 待 dev 集成)。2026-09-26。docgov 批准链:登记并修复 review_snapshot_id 装载缺失与 uow 行锁自死锁(resolved);memDocs 持久化失败缓存投毒(investigating)。历史 resolved 不表示本日产品回归通过。系统 code review P0/P1 批次修复落账(5 resolved + 1 deferred + 1 open,主记录见 [整改文档](2026-09-26-system-code-review-fixes.md),来源 [审计](2026-09-26-system-code-review.md))。 +更新:2026-09-28。非编制人触发小队账本提交被 `uow: permission denied` 拒绝,已改为编制人代理提交并在 dev 重跑通过(resolved)。test 注册 INTERNAL:标准角色 role-guest 缺失导致 FK 23503,已补种并重启 test(goose 77,resolved)。重复用户名 400 在新代码单测通过;当前 rc 二进制仍返回 INTERNAL,随下次含本提交的 test 制品生效。管理台重置密码入口被 F4-A 误删(端点仍在),已恢复 UI(resolved)。2026-09-27。上传链路:登记并修复上传文档跨端不可访问(占位模板退化 + 元数据/正文不落服务端,fixing 待 dev 集成)。2026-09-26。docgov 批准链:登记并修复 review_snapshot_id 装载缺失与 uow 行锁自死锁(resolved);memDocs 持久化失败缓存投毒(investigating)。历史 resolved 不表示本日产品回归通过。系统 code review P0/P1 批次修复落账(5 resolved + 1 deferred + 1 open,主记录见 [整改文档](2026-09-26-system-code-review-fixes.md),来源 [审计](2026-09-26-system-code-review.md))。 | issue_id | 模块 / 检索词 | 问题 | 严重度 | issue_state | owner | 证据日期 / 范围 | 唯一主记录 | 经验 | |---|---|---|---|---|---|---|---|---| -| ISSUE-20260928-squad-commit-authority | bizdev / docflow / dutyAuthorizer / uow permission denied / OnBehalfOf / bd_intake | 非编制人触发受控小队任务时,账本提交用触发者身份,被编制人职责门拒绝,正文无法落盘 | high | fixing | Cursor | 2026-09-28;dev task_runs user 74 ≠ author 4(doc_proj6022_bd_intake);单测已绿,dev 重跑未做 | [主记录](../ops/short-term/2026-09-28-squad-commit-authority.md#issue-20260928-squad-commit-authority) | [LESSON-20260928-squad-commit-as-author](../ops/lessons/2026-09-28-squad-commit-as-author.md) | +| ISSUE-20260928-squad-commit-authority | bizdev / docflow / dutyAuthorizer / uow permission denied / OnBehalfOf / bd_intake | 非编制人触发受控小队任务时,账本提交用触发者身份,被编制人职责门拒绝,正文无法落盘 | high | resolved | Cursor | 2026-09-28;dev `b1ed8ecf` 重跑 `run_1790559088975684237` completed,commit actor 4,snapshot actor 74 | [主记录](../ops/short-term/2026-09-28-squad-commit-authority.md#issue-20260928-squad-commit-authority) | [LESSON-20260928-squad-commit-as-author](../ops/lessons/2026-09-28-squad-commit-as-author.md) | | ISSUE-20260928-register-internal | auth / register / role-guest / system_roles / 23503 / 23505 / INTERNAL | test 缺 role-guest 时注册 FK 失败被脱敏为 INTERNAL;重复用户名唯一冲突同样 500 | high | resolved | Cursor | 2026-09-28;test:8081 补种并重启后注册 200(goose 77,req_regfix_after_restart);重复用户名 400 由单测覆盖,rc 二进制仍为 INTERNAL | [主记录](../ops/short-term/2026-09-28-auth-register-internal.md#issue-20260928-register-internal) | [LESSON-20260928-standard-role-reseed](../ops/lessons/2026-09-28-standard-role-reseed.md) | | ISSUE-20260928-admin-reset-ui-removed | web / UsersSection / 重置密码 / F4-A / adminResetPassword | F4-A 在 F4-B 端点已合入的基线上删除管理台重置密码按钮,管理员无法重置口令 | high | resolved | Cursor | 2026-09-28;dev API :8080 + worktree Vite :3110 真实 UI 探针 PASS(临时口令长度 16、强制改密仅一次、旧口令 401) | [主记录](../ops/short-term/2026-09-28-restore-admin-password-reset.md#issue-20260928-admin-reset-ui-removed) | [LESSON-20260928-ui-dead-control](../ops/lessons/2026-09-28-ui-dead-control.md) | | ISSUE-20260927-upload-doc-cross-device | freedoc / upload / freeform / fileMeta / 占位模板 / 跨端 | 上传文档源文件元数据与正文不落服务端:换浏览器/清存储/共享他人查看时退化为注册占位模板,文本类连二进制都未存(伴生 <512B 显示 "0 KB") | high | resolved | pi | 2026-09-27;dev `86efba2c` 合入并在 dev 环境验证:FS-UPLOAD-01/02 真实浏览器 PASS(跨上下文可见真实正文),DB 核验元数据+asset 落库;存量无元数据旧文档不可恢复(重新上传即可) | [主记录](../ops/short-term/2026-09-27-upload-doc-access.md) | [LESSON-20260927-server-facts-and-iso-db-recipe](../ops/lessons/2026-09-27-server-facts-and-iso-db-recipe.md) | diff --git a/.doc/ops/lessons/2026-09-28-squad-commit-as-author.md b/.doc/ops/lessons/2026-09-28-squad-commit-as-author.md index 65a3d6af..913aba71 100644 --- a/.doc/ops/lessons/2026-09-28-squad-commit-as-author.md +++ b/.doc/ops/lessons/2026-09-28-squad-commit-as-author.md @@ -1,10 +1,10 @@ --- lesson_id: LESSON-20260928-squad-commit-as-author -lesson_state: candidate +lesson_state: validated owner: Cursor -reviewer: Cursor(自审,待 dev 重跑后升 validated) +reviewer: Cursor(自审,dev 原例已复跑) last_reviewed: 2026-09-28 -reviewed_revision: 774df0599813fb8d7115f95ce255384542d6a0f9 +reviewed_revision: b1ed8ecfe9fc5849288b6febf2bb4780319c8aca source_issues: [ISSUE-20260928-squad-commit-authority] keywords: [bizdev, docflow, dutyAuthorizer, AutoCommitUserID, OnBehalfOf, uow permission denied, 非编制人] --- @@ -14,7 +14,7 @@ keywords: [bizdev, docflow, dutyAuthorizer, AutoCommitUserID, OnBehalfOf, uow pe - 触发条件:受控文档小队任务成功生成正文,但落盘报 `uow: permission denied` 或「版本账本/变更报告落盘失败」。 - 机制与来源:`dutyAuthorizer` 对 `ActionCommit` 要求 principal 等于 `document_records.author_id`。触发者常常是其他项目成员。见 ISSUE-20260928-squad-commit-authority。 - 开发检查:自动提交的 `AutoCommitUserID` 用文档编制人;实际触发用户写入 `OnBehalfOf`。不要为了小队路径放宽编制人校验。触发者或编制人为 0 时不要伪造代理身份。权限失败文案要写明「非编制人」,同时保留 `uow.ErrPermissionDenied` 哨兵。 -- 验证方式:非编制人触发应成功且 `OnBehalfOf` 为触发者;编制人触发不设置 `OnBehalfOf`;非编制人直接 `ActionCommit` 仍拒绝。关联 `internal/bizdev/squad_commit_actor_test.go` 与 `TestDutyAuthorizerAllowsDocumentAuthorCommit`。dev 原例重跑完成前本条保持 candidate。 +- 验证方式:非编制人触发应成功且快照 `actor_user_id` 为触发者,变更事件 `actor_user_id` 为编制人;编制人触发不设置 `OnBehalfOf`;非编制人直接 `ActionCommit` 仍拒绝。dev 原例 `run_1790559088975684237` 已 PASS,见测试报告 `2026-09-28-squad-commit-authority-test-report.md`。 - 适用边界:只覆盖小队/agent 自动提交。人工编辑、批准、驳回仍按各自职责人校验。高风险候选仍要编制人确认。 -- 防护落实:上述单测已落地。原例验收是 manual(dev 非编制人重跑)。 +- 防护落实:单测在 `internal/bizdev/squad_commit_actor_test.go` 与 `TestDutyAuthorizerAllowsDocumentAuthorCommit`。dev 原例已用真实 pi-agent 重跑,见测试报告。没有新增 CI 门禁。 - 修订与失效:若产品改为「非编制人不得触发生成」,本条应退役并改回触发者身份。 diff --git a/.doc/ops/lessons/INDEX.md b/.doc/ops/lessons/INDEX.md index 2b5a6165..4d66a7c2 100644 --- a/.doc/ops/lessons/INDEX.md +++ b/.doc/ops/lessons/INDEX.md @@ -4,7 +4,7 @@ | lesson_id | 触发条件 / 关键词 | 注意事项 | lesson_state | 主记录 / 最近复核 | |---|---|---|---|---| -| LESSON-20260928-squad-commit-as-author | 小队落盘、uow permission denied、AutoCommitUserID、OnBehalfOf、非编制人 | 自动提交身份用文档编制人,触发者只写入 OnBehalfOf;不要放宽 ActionCommit | candidate | [编制人代理提交](2026-09-28-squad-commit-as-author.md);2026-09-28 | +| LESSON-20260928-squad-commit-as-author | 小队落盘、uow permission denied、AutoCommitUserID、OnBehalfOf、非编制人 | 自动提交身份用文档编制人,触发者只写入 OnBehalfOf;不要放宽 ActionCommit | validated | [编制人代理提交](2026-09-28-squad-commit-as-author.md);2026-09-28 | | LESSON-20260928-standard-role-reseed | 注册、role-guest、system_roles、is_system、23503、23505、INTERNAL | 硬编码标准角色须幂等补种并标 is_system;用户名冲突返回领域 400,不要包进 insert user | validated | [标准角色可自愈](2026-09-28-standard-role-reseed.md);2026-09-28 | | LESSON-20260928-ui-dead-control | 死按钮、端点未开通、UsersSection、重置密码、merge-base | 删除「未开通」控件前用当前基线核对路由已存在;注释不能代替 merge-base | validated | [删控件前核对端点](2026-09-28-ui-dead-control.md);2026-09-28 | | LESSON-20260927-server-facts-and-iso-db-recipe | 上传持久化、fileMeta、占位模板、config.Load 默认 DSN、TEST_DATABASE_URL、隔离库、RefuseSharedEnvDSN、goose 顺序 | 服务端事实源优先(本地仅缓存);worktree 测试必须显式钉隔离库并避开 5433/5434/5435 子串端口;隔离库配方=扩展→goose→init SQL,重组件用例先做基线四格对照 | validated | [服务端事实源与隔离库配方](2026-09-27-server-facts-and-iso-db-recipe.md) | diff --git a/.doc/ops/short-term/2026-09-28-squad-commit-authority.md b/.doc/ops/short-term/2026-09-28-squad-commit-authority.md index 3af3e662..27ca9083 100644 --- a/.doc/ops/short-term/2026-09-28-squad-commit-authority.md +++ b/.doc/ops/short-term/2026-09-28-squad-commit-authority.md @@ -1,7 +1,7 @@ --- title: "修复非编制人触发小队账本提交被拒" task_id: squad-commit-authority-2026-09-28 -status: in_progress +status: done created: 2026-09-28 updated: 2026-09-28 owner: Cursor @@ -14,9 +14,9 @@ write_scope: - internal - .doc mutex: bizdev+docflow -review_state: pending -integration_state: pending -deployment: pending +review_state: accepted +integration_state: merged_local_dev +deployment: dev_rebuilt cleanup: pending remote_sync: not_requested --- @@ -37,7 +37,7 @@ remote_sync: not_requested ## ISSUE-20260928-squad-commit-authority issue_id: ISSUE-20260928-squad-commit-authority -issue_state: fixing +issue_state: resolved severity: high owner: Cursor task_ids: [squad-commit-authority-2026-09-28] @@ -67,8 +67,8 @@ dev 库只读核实(2026-09-28,`agent_system_dev`):`document_records.doc | 日期 | 动作 | 结果 | 下一步 | |---|---|---|---| | 2026-09-28 | dev 库核对 author 与 task_runs.user_id | 4 ≠ 74,两次失败均为 permission denied | 按编制人代理提交实现 | -| 2026-09-28 | `squadCommitActor` + 回归 | 内存单测通过;dev 真实重跑未做 | 合入 dev 后用非编制人重跑 | +| 2026-09-28 | dev 以 user 74 重跑 `bd_intake` | `run_1790559088975684237` completed;commit actor 4,snapshot actor 74 | 问题可关闭 | ### 验收与关闭 -尚未关闭。原例待 dev 重跑;变体与正常路径见 `TestExecuteTask_NonAuthorTriggerMaterializesAsAuthor`、`TestExecuteTask_AuthorTriggerDoesNotSetOnBehalfOf`、`TestExecuteTask_NonAuthorPassesDutyAuthorizer`、`TestDutyAuthorizerAllowsDocumentAuthorCommit`。 +2026-09-28 关闭。原例 PASS,见 [测试报告](../../test-reports/2026-09-28-squad-commit-authority-test-report.md)。编制人路径与非编制人直接提交拒绝由单测覆盖。dev 宿主二进制已重建;未晋升 test/main。 diff --git a/.doc/test-reports/2026-09-28-squad-commit-authority-test-report.md b/.doc/test-reports/2026-09-28-squad-commit-authority-test-report.md new file mode 100644 index 00000000..00acfc25 --- /dev/null +++ b/.doc/test-reports/2026-09-28-squad-commit-authority-test-report.md @@ -0,0 +1,79 @@ +# 非编制人小队账本提交 测试报告 + +## 一、 测试背景 (Background) + +项目 6022 的《用户初始需求收集表》由成员 ceshi(user 74,viewer)触发生成时,小队正文已产出,但账本提交以触发者身份请求 `ActionCommit`,被编制人职责门拒绝(`author_id=4`)。任务 `run_1790556318606671340` 与 `run_1790556736148378090` 的 `error_message` 为 `uow: permission denied`。 + +## 二、 测试目标 (Test Objectives) + +- 非编制人再次触发 `bd_intake` 时,任务完成且不再返回 `uow: permission denied`。 +- 账本 `agent_commit` 的 `actor_user_id` 为文档编制人 4。 +- 快照 `actor_user_id` 记录实际触发者 74。 +- 编制人自己触发、非编制人直接 `ActionCommit` 的单测不回归。 + +## 三、 测试方案与环境 (Test Plan & Environment) + +- 环境:本机 dev,`GET /api/v1/health` 返回 `env=dev`、`agent_backend=pi-agent`、`schema_version=77`、`database_ready=true`。 +- 代码:local `dev` `b1ed8ecf`(`merge: candidate squad-commit-authority-2026-09-28 into dev`)。宿主 `bin/agent-server` 于 2026-09-28 01:30:10Z 由 `./scripts/launch.sh --no-docker` 重建并重启,健康检查通过。 +- 层级:机制回归用内存单测;原例用 dev 真实 `pi-agent` 重跑。未跑 nightly 五项全量,本修复不改工作流定义。 +- 身份:为 user 74 写入一次性 `auth_sessions`(2 小时过期),请求结束后删除。未记录 token,未改口令。 + +## 四、 预期结果 (Expected Results) + +- `POST /api/v1/tasks/runs` 被接受,最终 `task_runs.status=completed` 且 `error_message` 为空。 +- `document_records.content_sha256` 从空变为非空。 +- `document_change_events.change_kind=agent_commit` 且 `actor_user_id=4`。 +- 对应 `document_snapshots.actor_user_id=74`,`source_kind=agent_squad`。 + +## 五、 实际测试结果与判定 (Actual Results & Evaluation) + +### 用例 1:dev 原例重跑 + +- **测试输入**:user 74,`session_id=sess-p6022-u74`,`project_id=6022`,`task_key=bd_intake`,`doc_id=doc_proj6022_bd_intake`,`client_turn_id=verify-squad-commit-20260928`。 +- **实际输出**: + - 创建:HTTP 202,`run_id=run_1790559088975684237`,`status=running`。 + - 结束:`task_runs` 行 `user_id=74`,`status=completed`,`error_message` 空。 + - 修复前对照:同一文档、同一用户的 `run_1790556736148378090` 为 `failed` / `uow: permission denied`。 + - 账本:`document_records` 由 `|1`(空哈希、version 1)变为 `82d4dfb0bd17691ef0cb307f4fa6526cf04747713a2ff67c68a33b9396b8dbf1|1`。 + - `document_change_events` id 37:`agent_commit` / `agent_squad` / `actor_user_id=4`。 + - `document_snapshots` id 37:`agent_squad` / `actor_user_id=74` / `source_run_id=bizdev-1790559435908957528-bd_intake`。 + - `document_agent_candidates` id 15:`status=committed`。 + - 快照 `actor_snapshot`:`{"model":"pi-agent","squad_name":"bizdev-squad"}`。 +- **评价判定**:PASS +- **性能与耗时**:约 01:31:28Z 至 01:37:29Z,约 6 分钟。 + +### 用例 2:编制人触发不代理 + +- **测试输入**:`go test` `TestExecuteTask_AuthorTriggerDoesNotSetOnBehalfOf`。 +- **实际输出**:`ok chat-agent-system/internal/bizdev`(与用例 3 同一次 `go test`,0.041s)。 +- **评价判定**:PASS +- **性能与耗时**:包级 0.041s。 + +### 用例 3:非编制人直接 ActionCommit 仍拒绝 + +- **测试输入**:`TestDutyAuthorizerAllowsDocumentAuthorCommit`(编制人通过,user 74 得到 `uow.ErrPermissionDenied`)以及 `TestReauthDeniesWrongAuthor`。 +- **实际输出**:`ok chat-agent-system/internal/app/docflow` 0.003s。集成候选校验同一命令亦通过(sha `b1ed8ecfe9fc5849288b6febf2bb4780319c8aca`)。 +- **评价判定**:PASS +- **性能与耗时**:0.003s。 + +### 未覆盖 + +- 未在浏览器里点击生成按钮。API 与 `task_runs` 是该按钮的同一创建入口。 +- 未重跑高风险 `needs_confirmation` 的真实 PI 样本。该分支未改判断式,内存用例 `TestExecuteTask_PendingConfirmation_RecordsAwaitingNotCompleted` 在合入前的 `TestExecuteTask_` 筛选中通过。 + +## 六、 自动化测试套件汇总 (Test Suite Execution) + +合入前在集成工作区执行: + +```text +go test -count=1 -timeout 180s ./internal/bizdev/ -run 'TestSquadCommitActor_UsesAuthorWhenTriggerDiffers|TestExecuteTask_NonAuthor|TestExecuteTask_AuthorTrigger|TestExecuteTask_PermissionDenied|TestExecuteTask_MaterializeFailure|TestExecuteTask_Committed|TestExecuteTask_Pending' +go test -count=1 -timeout 60s ./internal/app/docflow/ -run 'TestDutyAuthorizerAllowsDocumentAuthorCommit|TestReauthDeniesWrongAuthor' +``` + +结果:两个包均为 `ok`。未跑全量 `go test ./...`。 + +## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) + +dev 原例达到修复目标:非编制人触发的受控小队任务可以落盘,提交身份是编制人,触发者留在快照上。部署状态是本机 dev 宿主进程已换新二进制;未晋升 test/main,未 push。 + +证据文档提交时,`activation-preview-precheck-2026-09-28` 仍占用 `.doc` 写范围。若本文件随该占用解除后才入库,以合入提交为准。