merge: fix/bizdev-locator-removal
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: "D4 bizdev locator 最终移除"
|
||||
status: done
|
||||
created: 2026-09-15
|
||||
updated: 2026-09-15
|
||||
owner: Cursor
|
||||
branch: fix/bizdev-locator-removal
|
||||
worktree: "-"
|
||||
mutex: bizdev
|
||||
---
|
||||
|
||||
# D4 bizdev locator 最终移除
|
||||
|
||||
## 目标
|
||||
|
||||
删除 bizdev 上 8 个 locator getter;组合根一次性 Snapshot 后由 Runtime/Server 持有端口。
|
||||
|
||||
## 范围
|
||||
|
||||
- `Pool` `DocGov` `DocRevision` `ContextManager` `CAS` `ProjectWorkflow` `FreeformDoc` `KB`
|
||||
- Runtime / Server 字段
|
||||
- archtest 禁止把 getter 加回去
|
||||
- 调用方迁移
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不删 SchemaVersion / DatabaseReady
|
||||
- 不切事件 live
|
||||
- 不实现 Track P
|
||||
|
||||
## 步骤
|
||||
|
||||
1. [x] Snapshot + Runtime 端口
|
||||
2. [x] 迁移 server/cmd/tests
|
||||
3. [x] 同批删除 8 getter
|
||||
4. [x] 报告合入
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [x] 生产无 8 个 getter
|
||||
- [x] [S1 报告](../../test-reports/2026-09-15-bizdev-locator-removal-test-report.md)
|
||||
|
||||
## 相关链接
|
||||
|
||||
- Phase 2 D4
|
||||
+4
-3
@@ -5,7 +5,7 @@
|
||||
|
||||
## 焦点
|
||||
|
||||
1. **TRACK-S**: [D5 stageflow](./short-term/2026-09-15-stageflow-type-ownership.md)(D6/D7a 已合入。D4 getter 仍开放)
|
||||
1. **TRACK-S**: [D4 locator 已删](../test-reports/2026-09-15-bizdev-locator-removal-test-report.md);下一步 D7b chat 生产 import
|
||||
2. **CHAT-INTENT**: [对话意图运行时状态](../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md)(双标签 busy 已签:项目 **6275** / `DOC_BUSY` 单 run;不删 `sess-p143-u9` 探针)
|
||||
3. **CHAT**: [对话历史刷新活验已补](../test-reports/2026-09-13-chat-history-refresh-test-report.md)(项目 6252 闲聊刷新同集 + guest 403;编制任务链仍开放)
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
| Cursor | event-e2-hook-guard | fix/event-e2-hook-guard | - | events | done |
|
||||
| Cursor | doccontent-canonical-boundary | fix/doccontent-canonical-boundary | - | docgov | done |
|
||||
| Cursor | intent-bizdev-boundary | fix/intent-bizdev-boundary | - | intent | done |
|
||||
| Cursor | stageflow-type-ownership | fix/stageflow-type-ownership | - | stageflow | in_progress |
|
||||
| Cursor | stageflow-type-ownership | fix/stageflow-type-ownership | - | stageflow | done |
|
||||
| Cursor | bizdev-locator-removal | fix/bizdev-locator-removal | - | bizdev | done |
|
||||
|
||||
> 新任务开始前登记。`branch` 必须为 `feature/<slug>` 或 `fix/<slug>`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。
|
||||
|
||||
@@ -78,7 +79,7 @@
|
||||
|
||||
| holder | phase | since | scope | queue |
|
||||
|--------|-------|-------|-------|-------|
|
||||
| Cursor | merging | 2026-09-15 | D5 stageflow types;mode=off | - |
|
||||
| Cursor | merging | 2026-09-15 | D4 bizdev locator removal;mode=off | - |
|
||||
|
||||
**phase**: `idle` | `merging` | `smoke` | `e2e`
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
| PI Runner / 配置链 | pi-agent + MiniMax 真实链路 | done | [test 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) | — | `pi-agent` |
|
||||
| 三环境运维 | dev/test/main + 共享 KB | done | [PROJECT.md](../../PROJECT.md) §4;四环境 healthy | — | `infra` |
|
||||
| CI / E2E | PG 集成 + 真实 PI E2E | partial | [gap-matrix BUG-08](../remediation/gap-matrix.md);[test pg-ci](../test-reports/2026-09-12-pg-ci-soak-e2e-test-report.md);[运行时 UI Round20](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md) | CI `TEST_DATABASE_URL` 隔离库仍缺;文档运行时真实 UI 已按 UI_PHASE 分文件且 post-restart 断言最终 A1 正文。`-race` 全包并行 TempDir flake 为基建残留 | `ci` |
|
||||
| 模块解耦与可靠事件 | 文档可靠性纵向试点、PG 持久事件交付与选择性模块化 | partial | [架构](../system-design/2026-09-14-module-decoupling-architecture.md);[ADR](../adr/2026-09-14-modular-monolith-decoupling.md);[Phase 1](../remediation/2026-09-14-module-decoupling-refactor-plan.md);[Phase 2 审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md);[Phase 2 修订计划](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md);[E0A](../test-reports/2026-09-15-event-e0a-production-scope-contract-test-report.md);[E0B](../test-reports/2026-09-15-event-e0b-dispatcher-pg-test-report.md);[E1A](../test-reports/2026-09-15-event-e1a-notify-cutover-test-report.md);[E1B](../test-reports/2026-09-15-event-e1b-workflow-advance-test-report.md);[E1C](../test-reports/2026-09-15-event-e1c-projection-test-report.md);[E2](../test-reports/2026-09-15-event-e2-hook-guard-test-report.md);[复盘](../ops/research/2026-09-15-track-e-retrospective.md);[D6](../test-reports/2026-09-15-doccontent-canonical-boundary-test-report.md);[D7a](../test-reports/2026-09-15-intent-bizdev-boundary-test-report.md);[D5](../test-reports/2026-09-15-stageflow-type-ownership-test-report.md) | E0A–E2 隔离库已过;dispatcher 默认 off。D5/D6/D7a/D7b-chatflow 已合入。删除闭包 API 须等 live。D4 getter 原子删除与 Track P 实现、FAV 另列。HTTP/PI 已有独立 remediation(Phase 2 只立项) | `architecture-docs` |
|
||||
| 模块解耦与可靠事件 | 文档可靠性纵向试点、PG 持久事件交付与选择性模块化 | partial | [架构](../system-design/2026-09-14-module-decoupling-architecture.md);[ADR](../adr/2026-09-14-modular-monolith-decoupling.md);[Phase 1](../remediation/2026-09-14-module-decoupling-refactor-plan.md);[Phase 2 审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md);[Phase 2 修订计划](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md);[E0A](../test-reports/2026-09-15-event-e0a-production-scope-contract-test-report.md);[E0B](../test-reports/2026-09-15-event-e0b-dispatcher-pg-test-report.md);[E1A](../test-reports/2026-09-15-event-e1a-notify-cutover-test-report.md);[E1B](../test-reports/2026-09-15-event-e1b-workflow-advance-test-report.md);[E1C](../test-reports/2026-09-15-event-e1c-projection-test-report.md);[E2](../test-reports/2026-09-15-event-e2-hook-guard-test-report.md);[复盘](../ops/research/2026-09-15-track-e-retrospective.md);[D6](../test-reports/2026-09-15-doccontent-canonical-boundary-test-report.md);[D7a](../test-reports/2026-09-15-intent-bizdev-boundary-test-report.md);[D5](../test-reports/2026-09-15-stageflow-type-ownership-test-report.md);[D4](../test-reports/2026-09-15-bizdev-locator-removal-test-report.md) | E0A–E2 隔离库已过;dispatcher 默认 off。D4–D7a 与 D7b 首批已合入。删除闭包 API 须等 live。`internal/chat` 生产仍 import bizdev(D7b 剩余)。Track P 实现、FAV 另列。HTTP/PI 已有独立 remediation(Phase 2 只立项) | `architecture-docs` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -73,13 +73,18 @@ imports:
|
||||
- bizdev
|
||||
- config
|
||||
- coordinator
|
||||
- ctxmgr
|
||||
- docgov
|
||||
- docrevision
|
||||
- events
|
||||
- freedoc
|
||||
- intent
|
||||
- kb
|
||||
- notification
|
||||
- observability
|
||||
- projectinstance
|
||||
- projectworkflow
|
||||
- storage
|
||||
- taskrun
|
||||
- workflowdef
|
||||
- workspace
|
||||
@@ -169,11 +174,13 @@ imports:
|
||||
- events
|
||||
- freedoc
|
||||
- intent
|
||||
- kb
|
||||
- notification
|
||||
- observability
|
||||
- projectinstance
|
||||
- projectworkflow
|
||||
- replyexport
|
||||
- storage
|
||||
- squad
|
||||
- stream
|
||||
- stream/transport
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# D4 bizdev locator 移除测试报告
|
||||
|
||||
## 一、 测试背景 (Background)
|
||||
|
||||
`bizdev.Service` 对外暴露 8 个 locator getter(`Pool` / `DocGov` / `DocRevision` / `ContextManager` / `CAS` / `ProjectWorkflow` / `FreeformDoc` / `KB`),HTTP handler 与 CLI 在每次请求上穿过编排根取协作对象。D4 要求组合根一次性 `Snapshot`,由 Runtime/Server 持有端口,并原子删除 getter。
|
||||
|
||||
## 二、 测试目标 (Test Objectives)
|
||||
|
||||
- 生产代码不再调用上述 8 个 getter。
|
||||
- `*bizdev.Service` 上不再存在这 8 个方法;`BizdevService` 接口同步去掉 locator。
|
||||
- archtest 禁止把 getter 加回去。
|
||||
- `go test -short ./...` 与 `arch-check --mode block` 全绿。
|
||||
- 不切事件 live;不删 hook API。
|
||||
|
||||
## 三、 测试方案与环境 (Test Plan & Environment)
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| OS | Linux 7.0.0-31-generic |
|
||||
| Go | go1.27.1 linux/amd64 |
|
||||
| 分支 | `fix/bizdev-locator-removal` |
|
||||
| 层级 | 表征/短测 + arch-check |
|
||||
| 项目 143 | 只读 |
|
||||
|
||||
## 四、 预期结果 (Expected Results)
|
||||
|
||||
| 用例 | 期望 |
|
||||
|------|------|
|
||||
| Snapshot + Assemble | Runtime 字段来自 `bizdev.Snapshot(svc)`,不再走 getter |
|
||||
| Server 端口 | handlers 使用 `s.gov` / `s.ctxMgr` / `s.rev` 等 |
|
||||
| 删除 getter | `func (s *Service) DocGov()` 等 8 个方法不存在 |
|
||||
| archtest | `TestBizdevNoLocatorGetters` PASS |
|
||||
| 短测 | `go test -short ./...` PASS |
|
||||
|
||||
## 五、 实际测试结果与判定 (Actual Results & Evaluation)
|
||||
|
||||
### 5.1 getter 门禁
|
||||
|
||||
- **测试输入 (Input)**:`go test -count=1 ./internal/archtest -run TestBizdevNoLocatorGetters`
|
||||
- **实际输出 (Actual Output)**:`ok chat-agent-system/internal/archtest 0.120s`(含于 arch-check)
|
||||
- **评价判定 (Evaluation / Verdict)**:**PASS**
|
||||
- **性能与耗时 (Duration & Latency)**:~0.12s
|
||||
|
||||
### 5.2 arch-check
|
||||
|
||||
- **测试输入 (Input)**:`./scripts/arch-check.sh --mode block`
|
||||
- **实际输出 (Actual Output)**:`arch-check block: production gate passed`
|
||||
- **评价判定 (Evaluation / Verdict)**:**PASS**
|
||||
- **性能与耗时 (Duration & Latency)**:~0.79s
|
||||
|
||||
### 5.3 短测全量
|
||||
|
||||
- **测试输入 (Input)**:`unset TEST_DATABASE_URL DATABASE_URL CONTEXT_DB_HOST CONTEXT_DSN ALLOW_IN_MEMORY; go vet ./... && go test -short -count=1 ./...`
|
||||
- **实际输出 (Actual Output)**:`go vet` 无输出;各包 `ok`(server 9.926s,cli 2.007s,其余 <2s)
|
||||
- **评价判定 (Evaluation / Verdict)**:**PASS**
|
||||
- **性能与耗时 (Duration & Latency)**:约 12.2s
|
||||
|
||||
## 六、 自动化测试套件汇总 (Test Suite Execution)
|
||||
|
||||
| 命令 | 结果 |
|
||||
|------|------|
|
||||
| `go vet ./...` | PASS |
|
||||
| `go test -short -count=1 ./...` | PASS |
|
||||
| `./scripts/arch-check.sh --mode block` | PASS |
|
||||
|
||||
## 七、 测试结论与后续优化建议 (Conclusion & Next Steps)
|
||||
|
||||
D4 **PASS**。8 个 locator getter 已删除;组合根持有端口。`internal/chat` 生产包仍 import bizdev 跑 `CreateAndStart`(D7b 剩余批次)。Track P 仍只立项。不切 shared consumer live。
|
||||
+24
-24
@@ -330,7 +330,7 @@ func main() {
|
||||
|
||||
func handleStageRun(
|
||||
ctx context.Context,
|
||||
svc bizdev.BizdevService,
|
||||
svc *bizdev.Service,
|
||||
stageName string,
|
||||
project, customer, device, use, market, backend string,
|
||||
autoRepair bool,
|
||||
@@ -455,7 +455,7 @@ func printAdvisorBrief(brief *bizdev.AdvisorBriefing) {
|
||||
fmt.Println("==================================================================")
|
||||
}
|
||||
|
||||
func handleInspect(ctx context.Context, svc bizdev.BizdevService, workDir string, addStepFlag string, backend string) {
|
||||
func handleInspect(ctx context.Context, svc *bizdev.Service, workDir string, addStepFlag string, backend string) {
|
||||
state, err := svc.InspectWorkspace(ctx, workDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "查看工作区失败: %v\n", err)
|
||||
@@ -586,7 +586,7 @@ func createCLIStreamListener(verbose bool) stream.Listener {
|
||||
|
||||
func handleChatMode(
|
||||
ctx context.Context,
|
||||
svc bizdev.BizdevService,
|
||||
svc *bizdev.Service,
|
||||
chatQuery string,
|
||||
workDir string,
|
||||
project string,
|
||||
@@ -664,7 +664,7 @@ func handleChatMode(
|
||||
rec := intent.NewRecognizer()
|
||||
engine := coordinator.NewCoordinatorEngine(stageflow.New(svc))
|
||||
session := chat.NewSession("cli_chat_sess", userCtx, rec, engine)
|
||||
session.CtxMgr = svc.ContextManager()
|
||||
session.CtxMgr = bizdev.Snapshot(svc).Ctx
|
||||
|
||||
// 单次提问模式 (支持脚本与自动化测试)
|
||||
if chatQuery != "" {
|
||||
@@ -805,8 +805,8 @@ func handleChatMode(
|
||||
// 标杆案例独立检索 CLI 处理函数
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
func handleRecommendCases(ctx context.Context, svc bizdev.BizdevService, query string) {
|
||||
ctxMgr := svc.ContextManager()
|
||||
func handleRecommendCases(ctx context.Context, svc *bizdev.Service, query string) {
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
if ctxMgr == nil {
|
||||
fmt.Fprintf(os.Stderr, "错误: 上下文管理器未初始化\n")
|
||||
os.Exit(1)
|
||||
@@ -851,8 +851,8 @@ func handleRecommendCases(ctx context.Context, svc bizdev.BizdevService, query s
|
||||
// 受控文档治理 CLI 处理函数集
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
func handleDocStatus(ctx context.Context, svc bizdev.BizdevService, docID string) {
|
||||
doc, err := svc.DocGov().GetDocument(ctx, docID)
|
||||
func handleDocStatus(ctx context.Context, svc *bizdev.Service, docID string) {
|
||||
doc, err := bizdev.Snapshot(svc).Gov.GetDocument(ctx, docID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "查询受控文档失败 [%s]: %v\n", docID, err)
|
||||
os.Exit(1)
|
||||
@@ -879,7 +879,7 @@ func handleDocStatus(ctx context.Context, svc bizdev.BizdevService, docID string
|
||||
}
|
||||
|
||||
// 打印批注
|
||||
comments, _ := svc.DocGov().ListComments(ctx, docID)
|
||||
comments, _ := bizdev.Snapshot(svc).Gov.ListComments(ctx, docID)
|
||||
if len(comments) > 0 {
|
||||
fmt.Printf("\n批注与修改意见 (%d 条):\n", len(comments))
|
||||
for idx, c := range comments {
|
||||
@@ -888,7 +888,7 @@ func handleDocStatus(ctx context.Context, svc bizdev.BizdevService, docID string
|
||||
}
|
||||
|
||||
// 打印审计记录
|
||||
audits, _ := svc.DocGov().ListAuditLogs(ctx, docID)
|
||||
audits, _ := bizdev.Snapshot(svc).Gov.ListAuditLogs(ctx, docID)
|
||||
if len(audits) > 0 {
|
||||
fmt.Printf("\n全生命周期审计轨迹 (%d 条):\n", len(audits))
|
||||
for idx, a := range audits {
|
||||
@@ -899,9 +899,9 @@ func handleDocStatus(ctx context.Context, svc bizdev.BizdevService, docID string
|
||||
fmt.Println("==================================================================")
|
||||
}
|
||||
|
||||
func handleDocClaim(ctx context.Context, svc bizdev.BizdevService, docID string, userID int64, userRoleStr string, userName string) {
|
||||
func handleDocClaim(ctx context.Context, svc *bizdev.Service, docID string, userID int64, userRoleStr string, userName string) {
|
||||
role := docgov.UserRole(userRoleStr)
|
||||
doc, err := svc.DocGov().RecordDocumentViewed(ctx, docID, userID, role, userName)
|
||||
doc, err := bizdev.Snapshot(svc).Gov.RecordDocumentViewed(ctx, docID, userID, role, userName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "认领/查看文档失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -917,7 +917,7 @@ func handleDocClaim(ctx context.Context, svc bizdev.BizdevService, docID string,
|
||||
}
|
||||
}
|
||||
|
||||
func handleDecideProposal(ctx context.Context, svc bizdev.BizdevService, param string, authorID int64) {
|
||||
func handleDecideProposal(ctx context.Context, svc *bizdev.Service, param string, authorID int64) {
|
||||
// 格式: proposal_id:agree/reject:理由
|
||||
parts := strings.SplitN(param, ":", 3)
|
||||
if len(parts) < 2 {
|
||||
@@ -932,7 +932,7 @@ func handleDecideProposal(ctx context.Context, svc bizdev.BizdevService, param s
|
||||
}
|
||||
|
||||
approve := decision == "agree" || decision == "approve" || decision == "yes" || decision == "true"
|
||||
prop, err := svc.DocGov().DecideModificationProposal(ctx, proposalID, authorID, approve, reason)
|
||||
prop, err := bizdev.Snapshot(svc).Gov.DecideModificationProposal(ctx, proposalID, authorID, approve, reason)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "拍板决策变更提案失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -946,8 +946,8 @@ func handleDecideProposal(ctx context.Context, svc bizdev.BizdevService, param s
|
||||
}
|
||||
}
|
||||
|
||||
func handleDocSubmitReview(ctx context.Context, svc bizdev.BizdevService, docID string, userID int64, userRoleStr string, userName string) {
|
||||
doc, err := svc.DocGov().SubmitForReview(ctx, docID, userID, docgov.RoleAuthor, userName)
|
||||
func handleDocSubmitReview(ctx context.Context, svc *bizdev.Service, docID string, userID int64, userRoleStr string, userName string) {
|
||||
doc, err := bizdev.Snapshot(svc).Gov.SubmitForReview(ctx, docID, userID, docgov.RoleAuthor, userName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "提交审核失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -955,8 +955,8 @@ func handleDocSubmitReview(ctx context.Context, svc bizdev.BizdevService, docID
|
||||
fmt.Printf("✓ 文档 [%s] 编制完成并成功送审!当前状态: %s,文档已进入受控锁定。\n", doc.DocID, doc.CurrentState)
|
||||
}
|
||||
|
||||
func handleDocReviewApprove(ctx context.Context, svc bizdev.BizdevService, docID string, userID int64, userRoleStr string, userName string) {
|
||||
doc, err := svc.DocGov().ReviewApprove(ctx, docID, userID, docgov.RoleReviewer, userName, "审核无误,同意提交批准")
|
||||
func handleDocReviewApprove(ctx context.Context, svc *bizdev.Service, docID string, userID int64, userRoleStr string, userName string) {
|
||||
doc, err := bizdev.Snapshot(svc).Gov.ReviewApprove(ctx, docID, userID, docgov.RoleReviewer, userName, "审核无误,同意提交批准")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "审核通过失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -964,7 +964,7 @@ func handleDocReviewApprove(ctx context.Context, svc bizdev.BizdevService, docID
|
||||
fmt.Printf("✓ 审核人 %s 审核通过!文档 [%s] 流转至批准阶段: %s\n", userName, doc.DocID, doc.CurrentState)
|
||||
}
|
||||
|
||||
func handleDocReviewReject(ctx context.Context, svc bizdev.BizdevService, param string, userID int64, userRoleStr string, userName string) {
|
||||
func handleDocReviewReject(ctx context.Context, svc *bizdev.Service, param string, userID int64, userRoleStr string, userName string) {
|
||||
parts := strings.SplitN(param, ":", 2)
|
||||
if len(parts) < 2 || strings.TrimSpace(parts[1]) == "" {
|
||||
fmt.Fprintf(os.Stderr, "错误: 驳回时必须提供驳回原因!格式: doc_id:驳回原因\n")
|
||||
@@ -973,7 +973,7 @@ func handleDocReviewReject(ctx context.Context, svc bizdev.BizdevService, param
|
||||
docID := strings.TrimSpace(parts[0])
|
||||
reason := strings.TrimSpace(parts[1])
|
||||
|
||||
doc, err := svc.DocGov().ReviewReject(ctx, docID, userID, docgov.RoleReviewer, userName, reason)
|
||||
doc, err := bizdev.Snapshot(svc).Gov.ReviewReject(ctx, docID, userID, docgov.RoleReviewer, userName, reason)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "审核驳回失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -982,8 +982,8 @@ func handleDocReviewReject(ctx context.Context, svc bizdev.BizdevService, param
|
||||
userName, doc.DocID, doc.CurrentState, reason)
|
||||
}
|
||||
|
||||
func handleDocApproverApprove(ctx context.Context, svc bizdev.BizdevService, docID string, userID int64, userRoleStr string, userName string) {
|
||||
doc, err := svc.DocGov().ApproverApprove(ctx, docID, userID, docgov.RoleApprover, userName, "评估完备,批准受控发布")
|
||||
func handleDocApproverApprove(ctx context.Context, svc *bizdev.Service, docID string, userID int64, userRoleStr string, userName string) {
|
||||
doc, err := bizdev.Snapshot(svc).Gov.ApproverApprove(ctx, docID, userID, docgov.RoleApprover, userName, "评估完备,批准受控发布")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "批准失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -991,7 +991,7 @@ func handleDocApproverApprove(ctx context.Context, svc bizdev.BizdevService, doc
|
||||
fmt.Printf("🎉 批准负责人 %s 最终批准文档 [%s]!当前状态: %s,受控终态归档锁定。\n", userName, doc.DocID, doc.CurrentState)
|
||||
}
|
||||
|
||||
func handleDocApproverReject(ctx context.Context, svc bizdev.BizdevService, param string, userID int64, userRoleStr string, userName string) {
|
||||
func handleDocApproverReject(ctx context.Context, svc *bizdev.Service, param string, userID int64, userRoleStr string, userName string) {
|
||||
parts := strings.SplitN(param, ":", 2)
|
||||
if len(parts) < 2 || strings.TrimSpace(parts[1]) == "" {
|
||||
fmt.Fprintf(os.Stderr, "错误: 驳回时必须提供驳回原因!格式: doc_id:驳回原因\n")
|
||||
@@ -1000,7 +1000,7 @@ func handleDocApproverReject(ctx context.Context, svc bizdev.BizdevService, para
|
||||
docID := strings.TrimSpace(parts[0])
|
||||
reason := strings.TrimSpace(parts[1])
|
||||
|
||||
doc, err := svc.DocGov().ApproverReject(ctx, docID, userID, docgov.RoleApprover, userName, reason, docgov.StateDrafting)
|
||||
doc, err := bizdev.Snapshot(svc).Gov.ApproverReject(ctx, docID, userID, docgov.RoleApprover, userName, reason, docgov.StateDrafting)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "批准驳回失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -27,7 +27,7 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "NewService: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
if ctxMgr == nil {
|
||||
fmt.Fprintf(os.Stderr, "ContextManager 未初始化\n")
|
||||
os.Exit(1)
|
||||
|
||||
+37
-15
@@ -19,16 +19,23 @@ import (
|
||||
"chat-agent-system/internal/bizdev"
|
||||
"chat-agent-system/internal/config"
|
||||
"chat-agent-system/internal/coordinator"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/docrevision"
|
||||
"chat-agent-system/internal/events"
|
||||
"chat-agent-system/internal/freedoc"
|
||||
"chat-agent-system/internal/intent"
|
||||
"chat-agent-system/internal/kb"
|
||||
"chat-agent-system/internal/notification"
|
||||
"chat-agent-system/internal/observability"
|
||||
"chat-agent-system/internal/projectinstance"
|
||||
"chat-agent-system/internal/projectworkflow"
|
||||
"chat-agent-system/internal/storage"
|
||||
"chat-agent-system/internal/taskrun"
|
||||
"chat-agent-system/internal/workflowdef"
|
||||
"chat-agent-system/internal/workspace"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
const defaultReminderPollInterval = 15 * time.Minute
|
||||
@@ -56,6 +63,13 @@ type Runtime struct {
|
||||
ChatLog chatflow.ChatLog
|
||||
Assembler chatflow.ContextAssembler
|
||||
Docflow *docflow.Commands
|
||||
Pool *pgxpool.Pool
|
||||
Gov *docgov.GovernanceEngine
|
||||
Rev *docrevision.Service
|
||||
Ctx *ctxmgr.ContextManager
|
||||
CAS *storage.CAS
|
||||
FreeDoc *freedoc.Service
|
||||
KB *kb.Client
|
||||
|
||||
mu sync.Mutex
|
||||
running bool
|
||||
@@ -89,35 +103,36 @@ func Assemble(cfg *config.Config, svc *bizdev.Service) (*Runtime, error) {
|
||||
rec := intent.NewRecognizer()
|
||||
dialogueBackend := agent.NewBackend(cfg.AgentBackend, cfg)
|
||||
coordEngine := coordinator.NewCoordinatorEngineWithDialogue(nil, dialogueBackend, cfg.DataDir)
|
||||
pool := svc.Pool()
|
||||
parts := bizdev.Snapshot(svc)
|
||||
pool := parts.Pool
|
||||
stores, storeErr := auth.NewStores(context.Background(), pool, cfg)
|
||||
if storeErr != nil {
|
||||
return nil, fmt.Errorf("auth store: %w", storeErr)
|
||||
}
|
||||
authSvc := auth.NewService(stores.Auth, stores.Sessions, cfg)
|
||||
pwSvc := svc.ProjectWorkflow()
|
||||
pwSvc := parts.PW
|
||||
if pwSvc == nil {
|
||||
pwSvc = projectworkflow.NewService(projectworkflow.NewStore(nil), nil)
|
||||
}
|
||||
|
||||
authzSvc := authz.NewService(pool)
|
||||
wfDefSvc := workflowdef.NewService(pool, svc.CAS())
|
||||
wfDefSvc := workflowdef.NewService(pool, parts.CAS)
|
||||
instStore := projectinstance.NewStore(pool)
|
||||
var instSvc *projectinstance.Service
|
||||
if wfDefSvc.Available() {
|
||||
docChecker := projectinstance.NewDocGovChecker(svc.DocGov())
|
||||
docChecker := projectinstance.NewDocGovChecker(parts.Gov)
|
||||
instSvc = projectinstance.NewService(instStore, wfDefSvc.Store(), pwSvc.Store(), docChecker)
|
||||
if pool != nil {
|
||||
instSvc.SetActiveMemberLookup(projectinstance.NewPGMemberLookup(pool))
|
||||
}
|
||||
}
|
||||
taskMgr := taskrun.NewManager(pwSvc, svc.ContextManager())
|
||||
taskMgr := taskrun.NewManager(pwSvc, parts.Ctx)
|
||||
if pool != nil {
|
||||
taskMgr.SetStore(taskrun.NewPGStore(pool))
|
||||
}
|
||||
wsCap := workspace.NewCapability(cfg.DataDir, workspace.NewLeaseTable())
|
||||
cmds := &taskrun.Commands{
|
||||
Ctx: svc.ContextManager(),
|
||||
Ctx: parts.Ctx,
|
||||
PW: pwSvc,
|
||||
Env: cfg.Env,
|
||||
StorageIdentity: "env:" + cfg.Env,
|
||||
@@ -130,7 +145,7 @@ func Assemble(cfg *config.Config, svc *bizdev.Service) (*Runtime, error) {
|
||||
|
||||
stages := stageflow.New(svc)
|
||||
coordEngine.SetStageTaskRunner(stages)
|
||||
coordEngine.SetContextServices(svc.ContextManager(), svc.KB())
|
||||
coordEngine.SetContextServices(parts.Ctx, parts.KB)
|
||||
if instSvc != nil && instSvc.Available() {
|
||||
coordEngine.SetInstanceService(instSvc)
|
||||
}
|
||||
@@ -152,8 +167,15 @@ func Assemble(cfg *config.Config, svc *bizdev.Service) (*Runtime, error) {
|
||||
Commands: cmds,
|
||||
Rec: rec,
|
||||
Stageflow: stages,
|
||||
Pool: pool,
|
||||
Gov: parts.Gov,
|
||||
Rev: parts.Rev,
|
||||
Ctx: parts.Ctx,
|
||||
CAS: parts.CAS,
|
||||
FreeDoc: parts.FreeDoc,
|
||||
KB: parts.KB,
|
||||
}
|
||||
rt.DocNotify = &docnotify.Subscriber{Notify: rt.Notify, Docs: svc.DocGov()}
|
||||
rt.DocNotify = &docnotify.Subscriber{Notify: rt.Notify, Docs: parts.Gov}
|
||||
rt.DocNotify.SkipWrite = func(ctx context.Context) bool {
|
||||
return rt.WriterLive(ctx, events.ConsumerNotify)
|
||||
}
|
||||
@@ -162,7 +184,7 @@ func Assemble(cfg *config.Config, svc *bizdev.Service) (*Runtime, error) {
|
||||
envName = cfg.Env
|
||||
}
|
||||
rt.Docflow = &docflow.Commands{
|
||||
Gov: svc.DocGov(), Rev: svc.DocRevision(), Auth: docflow.DutyAuthorizer(),
|
||||
Gov: parts.Gov, Rev: parts.Rev, Auth: docflow.DutyAuthorizer(),
|
||||
Environment: envName,
|
||||
}
|
||||
if pool != nil {
|
||||
@@ -179,8 +201,8 @@ func (r *Runtime) bindDocumentNotify() {
|
||||
if r == nil || r.notifyBound {
|
||||
return
|
||||
}
|
||||
if r.Biz != nil && r.Biz.DocRevision() != nil {
|
||||
if docnotify.Bind(r.Biz.DocRevision(), r.DocNotify) {
|
||||
if r.Biz != nil && r.Rev != nil {
|
||||
if docnotify.Bind(r.Rev, r.DocNotify) {
|
||||
r.subscribers++
|
||||
}
|
||||
}
|
||||
@@ -188,25 +210,25 @@ func (r *Runtime) bindDocumentNotify() {
|
||||
}
|
||||
|
||||
func (r *Runtime) bindWorkflowApprovalHook() {
|
||||
if r == nil || r.hookBound || r.Biz == nil || r.Biz.DocGov() == nil || r.Inst == nil || !r.Inst.Available() {
|
||||
if r == nil || r.hookBound || r.Gov == nil || r.Inst == nil || !r.Inst.Available() {
|
||||
r.hookBound = true
|
||||
return
|
||||
}
|
||||
r.Biz.DocGov().AddOnApprovedHook(r.onWorkflowDocumentApproved)
|
||||
r.Gov.AddOnApprovedHook(r.onWorkflowDocumentApproved)
|
||||
r.hookBound = true
|
||||
r.subscribers++
|
||||
}
|
||||
|
||||
// WriterLive reports whether the durable consumer already owns the visible write.
|
||||
func (r *Runtime) WriterLive(ctx context.Context, consumer string) bool {
|
||||
if r == nil || r.Biz == nil {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
env := ""
|
||||
if r.Cfg != nil {
|
||||
env = r.Cfg.Env
|
||||
}
|
||||
return events.WriterLive(ctx, r.Biz.Pool(), env, consumer)
|
||||
return events.WriterLive(ctx, r.Pool, env, consumer)
|
||||
}
|
||||
|
||||
func (r *Runtime) onWorkflowDocumentApproved(ctx context.Context, doc *docgov.DocumentRecord) {
|
||||
|
||||
@@ -32,7 +32,6 @@ func TestUseCaseTypeIdentity(t *testing.T) {
|
||||
if TaskStatusFailed != "failed" || TaskStatusPendingAuthorConfirmation != "pending_author_confirmation" {
|
||||
t.Fatalf("status tokens drifted")
|
||||
}
|
||||
}
|
||||
biz := &stubBiz{}
|
||||
httpUC := New(biz)
|
||||
cliUC := New(biz)
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package archtest
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -48,3 +53,56 @@ func TestArchitectureBoundaries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var forbiddenBizdevLocators = []string{
|
||||
"Pool", "DocGov", "DocRevision", "ContextManager", "CAS", "ProjectWorkflow", "FreeformDoc", "KB",
|
||||
}
|
||||
|
||||
func TestBizdevNoLocatorGetters(t *testing.T) {
|
||||
root, err := ModuleRoot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dir := filepath.Join(root, "internal", "bizdev")
|
||||
fset := token.NewFileSet()
|
||||
pkgs, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
|
||||
name := info.Name()
|
||||
return strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go")
|
||||
}, parser.SkipObjectResolution)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
forbidden := make(map[string]struct{}, len(forbiddenBizdevLocators))
|
||||
for _, name := range forbiddenBizdevLocators {
|
||||
forbidden[name] = struct{}{}
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
for path, file := range pkg.Files {
|
||||
for _, decl := range file.Decls {
|
||||
fn, ok := decl.(*ast.FuncDecl)
|
||||
if !ok || fn.Recv == nil || fn.Name == nil {
|
||||
continue
|
||||
}
|
||||
if _, hit := forbidden[fn.Name.Name]; !hit {
|
||||
continue
|
||||
}
|
||||
if !recvIsBizdevService(fn.Recv) {
|
||||
continue
|
||||
}
|
||||
t.Errorf("locator getter %s must not exist on *bizdev.Service (%s)", fn.Name.Name, relFile(root, path))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func recvIsBizdevService(recv *ast.FieldList) bool {
|
||||
if recv == nil || len(recv.List) == 0 {
|
||||
return false
|
||||
}
|
||||
expr := recv.List[0].Type
|
||||
if star, ok := expr.(*ast.StarExpr); ok {
|
||||
expr = star.X
|
||||
}
|
||||
ident, ok := expr.(*ast.Ident)
|
||||
return ok && ident.Name == "Service"
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
func activityTitles(t *testing.T, svc *Service, projectID int64) []string {
|
||||
t.Helper()
|
||||
pCtx, err := svc.ContextManager().GetProjectContext(context.Background(), projectID)
|
||||
pCtx, err := svc.ctxMgr.GetProjectContext(context.Background(), projectID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetProjectContext: %v", err)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func activityTitles(t *testing.T, svc *Service, projectID int64) []string {
|
||||
func TestExecuteTask_MaterializeFailure_DoesNotRecordCompletedActivity(t *testing.T) {
|
||||
svc := MustNewService(t, &config.Config{DataDir: t.TempDir(), AgentBackend: "stub"})
|
||||
ctx := context.Background()
|
||||
svc.DocRevision().AgentCommit = false
|
||||
svc.docRev.AgentCommit = false
|
||||
projectID := int64(5601)
|
||||
|
||||
res, err := svc.ExecuteTask(ctx, TaskRunRequest{
|
||||
@@ -60,10 +60,10 @@ func TestExecuteTask_PendingConfirmation_RecordsAwaitingNotCompleted(t *testing.
|
||||
t.Fatal(err)
|
||||
}
|
||||
base := "# base\n\n法规引用 GB9706\n"
|
||||
if _, err := svc.DocRevision().OpenTemplateVersion(ctx, docID, base, docrevision.ActorSnapshot{DisplayName: "t"}, projectID, workDir); err != nil {
|
||||
if _, err := svc.docRev.OpenTemplateVersion(ctx, docID, base, docrevision.ActorSnapshot{DisplayName: "t"}, projectID, workDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := svc.DocGov().CreateOrGetDocument(ctx, docID, projectID, "research", "调研报告", "V0.5", workDir, 1, "a", 2, "r", 3, "p"); err != nil {
|
||||
if _, err := svc.docGov.CreateOrGetDocument(ctx, docID, projectID, "research", "调研报告", "V0.5", workDir, 1, "a", 2, "r", 3, "p"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -40,10 +40,10 @@ func TestService_BlendedContextInjection(t *testing.T) {
|
||||
FocusAreas: []string{"cost_budget", "timeline"},
|
||||
OutputFormatPref: "ppt_bullets",
|
||||
}
|
||||
_ = svc.ContextManager().UpdateUserPreference(ctx, pref)
|
||||
_ = svc.ctxMgr.UpdateUserPreference(ctx, pref)
|
||||
|
||||
// 2. 记录先验里程碑决策
|
||||
_, _ = svc.ContextManager().RecordActivity(ctx, projectID, userID, "项目总监", "decision", "首期控制在50万预算内并优先过型检", nil, 1.0, true)
|
||||
_, _ = svc.ctxMgr.RecordActivity(ctx, projectID, userID, "项目总监", "decision", "首期控制在50万预算内并优先过型检", nil, 1.0, true)
|
||||
|
||||
// 3. 执行任务 (使用 stub 后端进行快速逻辑验证)
|
||||
res, err := svc.ExecuteTask(ctx, TaskRunRequest{
|
||||
@@ -89,7 +89,7 @@ func TestService_BlendedContextInjection(t *testing.T) {
|
||||
}
|
||||
|
||||
// 6. 验证任务完成后自动记录了里程碑活动
|
||||
pCtx, err := svc.ContextManager().GetProjectContext(ctx, projectID)
|
||||
pCtx, err := svc.ctxMgr.GetProjectContext(ctx, projectID)
|
||||
if err != nil {
|
||||
t.Fatalf("get project context failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestBizDev_CaseRecommendation_FullLink_E2E(t *testing.T) {
|
||||
|
||||
_ = os.WriteFile(filepath.Join(workDir, "output.md"), []byte(histOutput), 0644)
|
||||
|
||||
_, err := svc.DocGov().CreateOrGetDocument(
|
||||
_, err := bizdev.Snapshot(svc).Gov.CreateOrGetDocument(
|
||||
ctx, docID, 99, "feasibility_reg", "4K超高清荧光内窥镜系统立项与可行性报告",
|
||||
"V0.5_feasibility", workDir,
|
||||
101, "张工", 201, "李审核", 301, "王总监",
|
||||
@@ -57,12 +57,12 @@ func TestBizDev_CaseRecommendation_FullLink_E2E(t *testing.T) {
|
||||
}
|
||||
|
||||
// 模拟流转至 approved 终态
|
||||
docRecord, _ := svc.DocGov().GetDocument(ctx, docID)
|
||||
docRecord, _ := bizdev.Snapshot(svc).Gov.GetDocument(ctx, docID)
|
||||
docRecord.IsTemplateOnly = false
|
||||
docRecord.WorkDir = workDir
|
||||
_, _ = svc.DocGov().SubmitForReview(ctx, docID, 101, docgov.RoleAuthor, "张工")
|
||||
_, _ = svc.DocGov().ReviewApprove(ctx, docID, 201, docgov.RoleReviewer, "李审核", "无误")
|
||||
docApproved, err := svc.DocGov().ApproverApprove(ctx, docID, 301, docgov.RoleApprover, "王总监", "批准生效")
|
||||
_, _ = bizdev.Snapshot(svc).Gov.SubmitForReview(ctx, docID, 101, docgov.RoleAuthor, "张工")
|
||||
_, _ = bizdev.Snapshot(svc).Gov.ReviewApprove(ctx, docID, 201, docgov.RoleReviewer, "李审核", "无误")
|
||||
docApproved, err := bizdev.Snapshot(svc).Gov.ApproverApprove(ctx, docID, 301, docgov.RoleApprover, "王总监", "批准生效")
|
||||
if err != nil {
|
||||
t.Fatalf("ApproverApprove failed: %v", err)
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func TestBizDev_CaseRecommendation_FullLink_E2E(t *testing.T) {
|
||||
}
|
||||
|
||||
// 主动索引备用保底
|
||||
chunkIDs, err := svc.ContextManager().IndexApprovedDocument(ctx, docApproved, histOutput)
|
||||
chunkIDs, err := bizdev.Snapshot(svc).Ctx.IndexApprovedDocument(ctx, docApproved, histOutput)
|
||||
if err != nil {
|
||||
t.Fatalf("IndexApprovedDocument manual failed: %v", err)
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func TestBizDev_CaseRecommendation_FullLink_E2E(t *testing.T) {
|
||||
|
||||
// 校验自动切片与向量化是否触发入库
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
cases, err := svc.ContextManager().SearchSimilarCases(ctx, bizdev.CaseSearchOptions{
|
||||
cases, err := bizdev.Snapshot(svc).Ctx.SearchSimilarCases(ctx, bizdev.CaseSearchOptions{
|
||||
Query: "4K超高清荧光内窥镜系统立项与可行性报告",
|
||||
Limit: 3,
|
||||
MinScore: 0.10,
|
||||
@@ -101,7 +101,7 @@ func TestBizDev_CaseRecommendation_FullLink_E2E(t *testing.T) {
|
||||
|
||||
rec := intent.NewRecognizer()
|
||||
engine := coordinator.NewCoordinatorEngine(svc)
|
||||
engine.SetContextServices(svc.ContextManager(), svc.KB())
|
||||
engine.SetContextServices(bizdev.Snapshot(svc).Ctx, bizdev.Snapshot(svc).KB)
|
||||
|
||||
userCtx := &coordinator.UserProjectContext{
|
||||
SnapshotID: "ctx_new_consult",
|
||||
@@ -127,7 +127,7 @@ func TestBizDev_CaseRecommendation_FullLink_E2E(t *testing.T) {
|
||||
}
|
||||
|
||||
session := chat.NewSession("sess_consult_e2e", userCtx, rec, engine)
|
||||
session.CtxMgr = svc.ContextManager()
|
||||
session.CtxMgr = bizdev.Snapshot(svc).Ctx
|
||||
|
||||
chatQuery := "我们之前做过类似的硬管内窥镜摄像系统吗?型检和报价大概怎么定的?"
|
||||
resp, err := session.SendMessage(ctx, chatQuery)
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestResolveAndValidateDocID_ExistingDocMismatchRejected(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
docID := "doc_proj56_research"
|
||||
_, err := svc.DocGov().CreateOrGetDocument(
|
||||
_, err := svc.docGov.CreateOrGetDocument(
|
||||
ctx, docID, 56, "research", "调研报告", "V0.5", "",
|
||||
1, "张工", 2, "李主管", 3, "王总监",
|
||||
)
|
||||
@@ -87,7 +87,7 @@ func TestResolveAndValidateDocID_ClientDocIDCannotOverrideTaskKey(t *testing.T)
|
||||
ctx := context.Background()
|
||||
|
||||
docID := "doc_proj6109_intake"
|
||||
_, err := svc.DocGov().CreateOrGetDocument(
|
||||
_, err := svc.docGov.CreateOrGetDocument(
|
||||
ctx, docID, 6109, "intake", "用户初始需求收集表", "V0.1", "",
|
||||
1, "张工", 2, "李主管", 3, "王总监",
|
||||
)
|
||||
@@ -122,7 +122,7 @@ func TestResolveAndValidateDocID_ExistingDocAccepted(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
docID := "doc_proj56_research"
|
||||
_, err := svc.DocGov().CreateOrGetDocument(
|
||||
_, err := svc.docGov.CreateOrGetDocument(
|
||||
ctx, docID, 56, "research", "调研报告", "V0.5", "",
|
||||
1, "张工", 2, "李主管", 3, "王总监",
|
||||
)
|
||||
@@ -154,7 +154,7 @@ func TestCreateOrGetDocument_ConcurrentSameDocID(t *testing.T) {
|
||||
}
|
||||
ch := make(chan outcome, 2)
|
||||
create := func() {
|
||||
doc, err := svc.DocGov().CreateOrGetDocument(
|
||||
doc, err := svc.docGov.CreateOrGetDocument(
|
||||
ctx, docID, 77, "research", "调研报告", "V0.5", "",
|
||||
1, "张工", 2, "李主管", 3, "王总监",
|
||||
)
|
||||
@@ -177,7 +177,7 @@ func TestCreateOrGetDocument_ConcurrentSameDocID(t *testing.T) {
|
||||
if first.id != docID || second.id != docID {
|
||||
t.Fatalf("doc ids mismatch: first=%q second=%q want %q", first.id, second.id, docID)
|
||||
}
|
||||
doc, err := svc.DocGov().GetDocument(ctx, docID)
|
||||
doc, err := svc.docGov.GetDocument(ctx, docID)
|
||||
if err != nil || doc == nil {
|
||||
t.Fatalf("expected single persisted doc %s, got err=%v doc=%v", docID, err, doc)
|
||||
}
|
||||
@@ -262,7 +262,7 @@ func TestExecuteTask_EmptyDocIDSkipsDocRevision(t *testing.T) {
|
||||
if res.DocRecord != nil {
|
||||
t.Fatalf("empty docID must not create a document record, got %#v", res.DocRecord)
|
||||
}
|
||||
if _, stErr := svc.DocRevision().GetRevisionState(ctx, ""); !strings.Contains(stErr.Error(), "invalid doc_id") {
|
||||
if _, stErr := svc.docRev.GetRevisionState(ctx, ""); !strings.Contains(stErr.Error(), "invalid doc_id") {
|
||||
t.Fatalf("empty GetRevisionState should be invalid, got %v", stErr)
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,7 @@ func TestExecuteTask_EmptyDocIDSkipsDocRevision(t *testing.T) {
|
||||
func TestExecuteTask_MaterializeFailureSurfacesInTaskRunResult(t *testing.T) {
|
||||
svc := MustNewService(t, &config.Config{DataDir: t.TempDir(), AgentBackend: "stub"})
|
||||
ctx := context.Background()
|
||||
svc.DocRevision().AgentCommit = false // force MaterializeAgentOutput to fail after squad success
|
||||
svc.docRev.AgentCommit = false // force MaterializeAgentOutput to fail after squad success
|
||||
|
||||
res, err := svc.ExecuteTask(ctx, TaskRunRequest{
|
||||
TaskKey: "research",
|
||||
|
||||
@@ -87,7 +87,7 @@ func TestBizDev_DocGov_ClaimingAndLifecycleE2E(t *testing.T) {
|
||||
// 阶段 3:编制负责人查看已生成的正式交付物
|
||||
// 预期:触发认领机制,author_claimed 变为 true!
|
||||
// -------------------------------------------------------------------------
|
||||
docAfterView, err := svc.DocGov().RecordDocumentViewed(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
docAfterView, err := bizdev.Snapshot(svc).Gov.RecordDocumentViewed(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
if err != nil {
|
||||
t.Fatalf("RecordDocumentViewed failed: %v", err)
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func TestBizDev_DocGov_ClaimingAndLifecycleE2E(t *testing.T) {
|
||||
// 预期:提案变为 approved_by_author,并放行修改
|
||||
// -------------------------------------------------------------------------
|
||||
propID := res3.PendingProposal.ProposalID
|
||||
_, err = svc.DocGov().DecideModificationProposal(ctx, propID, authorID, true, "经论证追加CE市场符合出海战略,同意更新注册可行性报告")
|
||||
_, err = bizdev.Snapshot(svc).Gov.DecideModificationProposal(ctx, propID, authorID, true, "经论证追加CE市场符合出海战略,同意更新注册可行性报告")
|
||||
if err != nil {
|
||||
t.Fatalf("DecideModificationProposal failed: %v", err)
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func TestBizDev_DocGov_ClaimingAndLifecycleE2E(t *testing.T) {
|
||||
// 阶段 6:送审、审核批注、审核驳回(必填原因)退回编制草稿、重新送审通过
|
||||
// -------------------------------------------------------------------------
|
||||
// 6.1 编制人送审
|
||||
docDraft, err := svc.DocGov().SubmitForReview(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
docDraft, err := bizdev.Snapshot(svc).Gov.SubmitForReview(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
if err != nil {
|
||||
t.Fatalf("SubmitForReview failed: %v", err)
|
||||
}
|
||||
@@ -150,25 +150,25 @@ func TestBizDev_DocGov_ClaimingAndLifecycleE2E(t *testing.T) {
|
||||
}
|
||||
|
||||
// 6.2 审核人在锁定状态下严禁直接修改正文
|
||||
_, err = svc.DocGov().RecordDocumentEdited(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName)
|
||||
_, err = bizdev.Snapshot(svc).Gov.RecordDocumentEdited(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName)
|
||||
if !errors.Is(err, docgov.ErrDocumentLocked) {
|
||||
t.Fatalf("reviewer modifying locked document should return ErrDocumentLocked, got: %v", err)
|
||||
}
|
||||
|
||||
// 6.3 审核人添加修改建议
|
||||
_, err = svc.DocGov().AddComment(ctx, docID, reviewerID, reviewerName, docgov.RoleReviewer, "## 临床评价路径", docgov.CommentSuggestion, "请增加与同类同品种比对器械的产品注册证号")
|
||||
_, err = bizdev.Snapshot(svc).Gov.AddComment(ctx, docID, reviewerID, reviewerName, docgov.RoleReviewer, "## 临床评价路径", docgov.CommentSuggestion, "请增加与同类同品种比对器械的产品注册证号")
|
||||
if err != nil {
|
||||
t.Fatalf("AddComment failed: %v", err)
|
||||
}
|
||||
|
||||
// 6.4 审核人驳回 (验证原因必填)
|
||||
_, err = svc.DocGov().ReviewReject(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName, "")
|
||||
_, err = bizdev.Snapshot(svc).Gov.ReviewReject(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName, "")
|
||||
if err != docgov.ErrRejectReasonRequired {
|
||||
t.Fatalf("ReviewReject without reason should fail with ErrRejectReasonRequired")
|
||||
}
|
||||
|
||||
// 审核人提供原因驳回 -> 退回编制草稿并解锁
|
||||
docRejected, err := svc.DocGov().ReviewReject(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName, "同品种比对缺少实质等同临床文献支持,请补充")
|
||||
docRejected, err := bizdev.Snapshot(svc).Gov.ReviewReject(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName, "同品种比对缺少实质等同临床文献支持,请补充")
|
||||
if err != nil {
|
||||
t.Fatalf("ReviewReject with reason failed: %v", err)
|
||||
}
|
||||
@@ -177,8 +177,8 @@ func TestBizDev_DocGov_ClaimingAndLifecycleE2E(t *testing.T) {
|
||||
}
|
||||
|
||||
// 6.5 编制人修订后重新送审
|
||||
_, _ = svc.DocGov().RecordDocumentEdited(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
docResubmitted, err := svc.DocGov().SubmitForReview(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
_, _ = bizdev.Snapshot(svc).Gov.RecordDocumentEdited(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
docResubmitted, err := bizdev.Snapshot(svc).Gov.SubmitForReview(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
if err != nil {
|
||||
t.Fatalf("resubmit review failed: %v", err)
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func TestBizDev_DocGov_ClaimingAndLifecycleE2E(t *testing.T) {
|
||||
}
|
||||
|
||||
// 6.6 审核人审核通过 -> 进入 approving
|
||||
docReviewApproved, err := svc.DocGov().ReviewApprove(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName, "临床比对资料已补充完备,同意转交批准")
|
||||
docReviewApproved, err := bizdev.Snapshot(svc).Gov.ReviewApprove(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName, "临床比对资料已补充完备,同意转交批准")
|
||||
if err != nil {
|
||||
t.Fatalf("ReviewApprove failed: %v", err)
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func TestBizDev_DocGov_ClaimingAndLifecycleE2E(t *testing.T) {
|
||||
// -------------------------------------------------------------------------
|
||||
// 阶段 7:批准人最终批准 -> approved 终态归档锁定
|
||||
// -------------------------------------------------------------------------
|
||||
docFinalApproved, err := svc.DocGov().ApproverApprove(ctx, docID, approverID, docgov.RoleApprover, approverName, "可行性评估与合规路径严谨可靠,准予受控生效")
|
||||
docFinalApproved, err := bizdev.Snapshot(svc).Gov.ApproverApprove(ctx, docID, approverID, docgov.RoleApprover, approverName, "可行性评估与合规路径严谨可靠,准予受控生效")
|
||||
if err != nil {
|
||||
t.Fatalf("ApproverApprove failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -52,22 +52,22 @@ func executeStageDrainingPending(
|
||||
t.Fatalf("pending stage missing DocRecord: %+v", res)
|
||||
}
|
||||
docID := pending.DocRecord.DocID
|
||||
st, err := svc.DocRevision().GetRevisionState(ctx, docID)
|
||||
st, err := bizdev.Snapshot(svc).Rev.GetRevisionState(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRevisionState(%s): %v", docID, err)
|
||||
}
|
||||
st2, err := svc.DocRevision().CommitAgentVersion(ctx, docID, pending.PendingAgentCandidateID, docrevision.CommitAgentDecision{
|
||||
st2, err := bizdev.Snapshot(svc).Rev.CommitAgentVersion(ctx, docID, pending.PendingAgentCandidateID, docrevision.CommitAgentDecision{
|
||||
Decision: "commit", Confirmed: true, ActorUserID: authorID,
|
||||
ExpectedInputSHA: st.HeadSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CommitAgentVersion(%s,#%d): %v", docID, pending.PendingAgentCandidateID, err)
|
||||
}
|
||||
if err := svc.DocRevision().ProjectOutputMarkdown(docID, dataRoot); err != nil {
|
||||
if err := bizdev.Snapshot(svc).Rev.ProjectOutputMarkdown(docID, dataRoot); err != nil {
|
||||
t.Fatalf("ProjectOutputMarkdown(%s): %v", docID, err)
|
||||
}
|
||||
if st2 != nil && st2.HeadSHA256 != "" {
|
||||
if err := svc.DocGov().MarkDocumentContentUpdated(ctx, docID, st2.HeadSHA256, false); err != nil {
|
||||
if err := bizdev.Snapshot(svc).Gov.MarkDocumentContentUpdated(ctx, docID, st2.HeadSHA256, false); err != nil {
|
||||
t.Fatalf("MarkDocumentContentUpdated: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
resV01.StageTitle, resV01.AdvisorBrief.MaturityHeadline, resV01.AdvisorBrief.MaturityScore)
|
||||
|
||||
// 验证项目事实黑板已沉淀 V0.1 提取事实
|
||||
pCtx, err := svc.ContextManager().GetProjectContext(ctx, projectID)
|
||||
pCtx, err := bizdev.Snapshot(svc).Ctx.GetProjectContext(ctx, projectID)
|
||||
if err != nil || pCtx == nil {
|
||||
t.Fatalf("get project context failed: %v", err)
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
|
||||
// 验证 feasibility_reg 受控文档已建立
|
||||
docID := "doc_proj1001_feasibility_reg"
|
||||
docRec, err := svc.DocGov().GetDocument(ctx, docID)
|
||||
docRec, err := bizdev.Snapshot(svc).Gov.GetDocument(ctx, docID)
|
||||
if err != nil || docRec == nil {
|
||||
t.Fatalf("get docgov record failed: %v", err)
|
||||
}
|
||||
@@ -213,7 +213,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
t.Log(">>> 阶段 3: 编制负责人查阅正文触发认领 (Author Claims Document) <<<")
|
||||
t.Log("================================================================================")
|
||||
|
||||
docClaimed, err := svc.DocGov().RecordDocumentViewed(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
docClaimed, err := bizdev.Snapshot(svc).Gov.RecordDocumentViewed(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
if err != nil {
|
||||
t.Fatalf("RecordDocumentViewed failed: %v", err)
|
||||
}
|
||||
@@ -254,7 +254,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
t.Log(">>> 阶段 5: 编制负责人拍板同意变更提案 (Author Approves Proposal) <<<")
|
||||
t.Log("================================================================================")
|
||||
|
||||
proposalDecided, err := svc.DocGov().DecideModificationProposal(
|
||||
proposalDecided, err := bizdev.Snapshot(svc).Gov.DecideModificationProposal(
|
||||
ctx,
|
||||
propID,
|
||||
authorID,
|
||||
@@ -283,7 +283,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
}
|
||||
for _, art := range resV05Updated.SubArtifacts {
|
||||
if art.TaskKey == "feasibility_reg" {
|
||||
_ = svc.DocGov().SetDocumentWorkDir(ctx, docID, art.WorkDir)
|
||||
_ = bizdev.Snapshot(svc).Gov.SetDocumentWorkDir(ctx, docID, art.WorkDir)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -293,7 +293,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
t.Log("================================================================================")
|
||||
|
||||
// 1. 编制人送审
|
||||
docReviewing, err := svc.DocGov().SubmitForReview(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
docReviewing, err := bizdev.Snapshot(svc).Gov.SubmitForReview(ctx, docID, authorID, docgov.RoleAuthor, authorName)
|
||||
if err != nil {
|
||||
t.Fatalf("SubmitForReview failed: %v", err)
|
||||
}
|
||||
@@ -303,7 +303,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
t.Logf("✓ 编制人提交送审: doc_state=%s, is_locked=%v", docReviewing.CurrentState, docReviewing.IsLocked)
|
||||
|
||||
// 2. 审核人添加批注意见
|
||||
comm, err := svc.DocGov().AddComment(
|
||||
comm, err := bizdev.Snapshot(svc).Gov.AddComment(
|
||||
ctx, docID, reviewerID, reviewerName, docgov.RoleReviewer,
|
||||
"## 关键性能与型检预估", docgov.CommentSuggestion,
|
||||
"针对荧光双波长光学滤光衰减特性,建议在送检前增加YY 0068.1硬管内窥镜机械强度摸底",
|
||||
@@ -314,7 +314,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
t.Logf("✓ 审核人添加章节批注: comment_id=%d, type=%s, section=%s", comm.ID, comm.CommentType, comm.TargetSection)
|
||||
|
||||
// 3. 审核人审核通过
|
||||
docApproving, err := svc.DocGov().ReviewApprove(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName, "经审阅合规策略严密,准予进入总监批准")
|
||||
docApproving, err := bizdev.Snapshot(svc).Gov.ReviewApprove(ctx, docID, reviewerID, docgov.RoleReviewer, reviewerName, "经审阅合规策略严密,准予进入总监批准")
|
||||
if err != nil {
|
||||
t.Fatalf("ReviewApprove failed: %v", err)
|
||||
}
|
||||
@@ -324,7 +324,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
t.Logf("✓ 审核人审核通过: doc_state=%s, is_locked=%v", docApproving.CurrentState, docApproving.IsLocked)
|
||||
|
||||
// 4. 批准人终态批准归档 (自动触发 onApprovedHook 切片向量化沉淀为标杆案例)
|
||||
docApproved, err := svc.DocGov().ApproverApprove(ctx, docID, approverID, docgov.RoleApprover, approverName, "符合转化中心立项及CDO报价基线标准,正式批准生效")
|
||||
docApproved, err := bizdev.Snapshot(svc).Gov.ApproverApprove(ctx, docID, approverID, docgov.RoleApprover, approverName, "符合转化中心立项及CDO报价基线标准,正式批准生效")
|
||||
if err != nil {
|
||||
t.Fatalf("ApproverApprove failed: %v", err)
|
||||
}
|
||||
@@ -425,7 +425,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
t.Log(">>> 阶段 10: 验证受控批准交付物沉淀为标杆案例,并在后续跨项目咨询中自动召回 <<<")
|
||||
t.Log("================================================================================")
|
||||
|
||||
recCases, err := svc.ContextManager().SearchSimilarCases(ctx, bizdev.CaseSearchOptions{
|
||||
recCases, err := bizdev.Snapshot(svc).Ctx.SearchSimilarCases(ctx, bizdev.CaseSearchOptions{
|
||||
Query: "4K超高清荧光硬管内窥镜摄像系统",
|
||||
Limit: 3,
|
||||
MinScore: 0.05,
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestResolveMaterials_RejectEmptyBinary(t *testing.T) {
|
||||
func TestResolveMaterials_ProjectAssetPool(t *testing.T) {
|
||||
cfg := &config.Config{DataDir: t.TempDir()}
|
||||
svc := bizdev.MustNewService(t, cfg)
|
||||
cas := svc.CAS()
|
||||
cas := bizdev.Snapshot(svc).CAS
|
||||
ref, err := cas.StoreFile(context.Background(), 42, 1, "pool.txt", "material", []byte("pool asset body"), "text/plain")
|
||||
if err != nil {
|
||||
t.Fatalf("StoreFile failed: %v", err)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package bizdev
|
||||
|
||||
import (
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/docrevision"
|
||||
"chat-agent-system/internal/freedoc"
|
||||
"chat-agent-system/internal/kb"
|
||||
"chat-agent-system/internal/projectworkflow"
|
||||
"chat-agent-system/internal/storage"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// Parts is the construction-time collaborator snapshot for app/cmd wiring.
|
||||
// Domain and HTTP handlers must not call this on every request; composition
|
||||
// roots copy the fields onto Runtime once.
|
||||
type Parts struct {
|
||||
Pool *pgxpool.Pool
|
||||
Gov *docgov.GovernanceEngine
|
||||
Rev *docrevision.Service
|
||||
Ctx *ctxmgr.ContextManager
|
||||
CAS *storage.CAS
|
||||
PW *projectworkflow.Service
|
||||
FreeDoc *freedoc.Service
|
||||
KB *kb.Client
|
||||
}
|
||||
|
||||
// Snapshot returns collaborators captured when the service was constructed.
|
||||
func Snapshot(s *Service) Parts {
|
||||
if s == nil {
|
||||
return Parts{}
|
||||
}
|
||||
return s.parts
|
||||
}
|
||||
@@ -55,18 +55,18 @@ func TestFinalizeSquadDeliverable_QualityFailed_NoDocProjectionSideEffects(t *te
|
||||
t.Fatal(err)
|
||||
}
|
||||
base := "# 正式受控 head\n\n本正文为账本事实源,质量失败后不得被未过门禁稿覆盖。\n"
|
||||
if _, err := svc.DocRevision().OpenTemplateVersion(ctx, docID, base, docrevision.ActorSnapshot{DisplayName: "baseline"}, projectID, officialWD); err != nil {
|
||||
if _, err := svc.docRev.OpenTemplateVersion(ctx, docID, base, docrevision.ActorSnapshot{DisplayName: "baseline"}, projectID, officialWD); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
docRec, err := svc.DocGov().CreateOrGetDocument(ctx, docID, projectID, "intake", "用户初始需求收集表", "V0.5", officialWD, 1, "a", 2, "r", 3, "p")
|
||||
docRec, err := svc.docGov.CreateOrGetDocument(ctx, docID, projectID, "intake", "用户初始需求收集表", "V0.5", officialWD, 1, "a", 2, "r", 3, "p")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stBefore, err := svc.DocRevision().GetRevisionState(ctx, docID)
|
||||
stBefore, err := svc.docRev.GetRevisionState(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
versBefore, err := svc.DocRevision().ListVersions(ctx, docID)
|
||||
versBefore, err := svc.docRev.ListVersions(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func TestFinalizeSquadDeliverable_QualityFailed_NoDocProjectionSideEffects(t *te
|
||||
t.Fatal("failure summary body must remain visible")
|
||||
}
|
||||
|
||||
afterDoc, err := svc.DocGov().GetDocument(ctx, docID)
|
||||
afterDoc, err := svc.docGov.GetDocument(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func TestFinalizeSquadDeliverable_QualityFailed_NoDocProjectionSideEffects(t *te
|
||||
if afterDoc.ContentSHA256 != beforeSHA {
|
||||
t.Fatalf("formal content hash mutated: before=%q after=%q", beforeSHA, afterDoc.ContentSHA256)
|
||||
}
|
||||
stAfter, err := svc.DocRevision().GetRevisionState(ctx, docID)
|
||||
stAfter, err := svc.docRev.GetRevisionState(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func TestFinalizeSquadDeliverable_QualityFailed_NoDocProjectionSideEffects(t *te
|
||||
if stAfter.PendingCandidateID != nil {
|
||||
t.Fatalf("quality_failed must not create pending candidate: %v", *stAfter.PendingCandidateID)
|
||||
}
|
||||
versAfter, err := svc.DocRevision().ListVersions(ctx, docID)
|
||||
versAfter, err := svc.docRev.ListVersions(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+11
-35
@@ -46,6 +46,7 @@ type Service struct {
|
||||
schemaVersion int64
|
||||
stageGate StageAccessGate
|
||||
materializer AgentOutputMaterializer
|
||||
parts Parts
|
||||
// stageTaskHook 仅测试注入:覆盖 ExecuteStage 子任务执行,验证链中止边界。
|
||||
stageTaskHook func(context.Context, TaskRunRequest) (*TaskRunResult, error)
|
||||
// createDocumentHook 仅测试注入:覆盖账本创建失败路径。
|
||||
@@ -151,6 +152,16 @@ func NewService(cfg *config.Config) (*Service, error) {
|
||||
pool: pool,
|
||||
schemaVersion: schemaVersion,
|
||||
}
|
||||
svc.parts = Parts{
|
||||
Pool: pool,
|
||||
Gov: docGov,
|
||||
Rev: docRev,
|
||||
Ctx: ctxMgr,
|
||||
CAS: casStore,
|
||||
PW: pwSvc,
|
||||
FreeDoc: freeDoc,
|
||||
KB: svc.kb,
|
||||
}
|
||||
|
||||
// 注册终审批准回调:自动切片向量化沉淀为往期标杆案例
|
||||
docGov.SetOnApprovedHook(func(ctx context.Context, doc *docgov.DocumentRecord) {
|
||||
@@ -227,25 +238,6 @@ func (s *Service) DatabaseReady() bool {
|
||||
return s != nil && s.pool != nil
|
||||
}
|
||||
|
||||
func (s *Service) ContextManager() *ctxmgr.ContextManager {
|
||||
return s.ctxMgr
|
||||
}
|
||||
|
||||
func (s *Service) CAS() *storage.CAS {
|
||||
return s.cas
|
||||
}
|
||||
|
||||
func (s *Service) DocGov() *docgov.GovernanceEngine {
|
||||
return s.docGov
|
||||
}
|
||||
|
||||
func (s *Service) DocRevision() *docrevision.Service {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return s.docRev
|
||||
}
|
||||
|
||||
// AgentOutputMaterializer is the production materialize path. Runtime injects
|
||||
// *docflow.Commands so candidate-ready/commit envelopes share the unique UoW.
|
||||
type AgentOutputMaterializer interface {
|
||||
@@ -268,22 +260,6 @@ func (s *Service) materializeAgentOutput(ctx context.Context, req docrevision.Ag
|
||||
return s.docRev.MaterializeAgentOutput(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Service) ProjectWorkflow() *projectworkflow.Service {
|
||||
return s.projectWorkflow
|
||||
}
|
||||
|
||||
func (s *Service) Pool() *pgxpool.Pool {
|
||||
return s.pool
|
||||
}
|
||||
|
||||
func (s *Service) FreeformDoc() *freedoc.Service {
|
||||
return s.freeDoc
|
||||
}
|
||||
|
||||
func (s *Service) KB() *kb.Client {
|
||||
return s.kb
|
||||
}
|
||||
|
||||
var SupportedTasks = squad.SupportedTasks
|
||||
|
||||
var SupportedStages = []StageMeta{
|
||||
|
||||
@@ -25,10 +25,10 @@ func TestExecuteStage_PendingAgentCandidate_StopsChainWithoutAbsorbingFacts(t *t
|
||||
}
|
||||
|
||||
base := "# research base\n\n法规引用 GB9706\n敏感验收标准 A\n"
|
||||
if _, err := svc.DocRevision().OpenTemplateVersion(ctx, docID, base, docrevision.ActorSnapshot{DisplayName: "t"}, projectID, researchWork); err != nil {
|
||||
if _, err := svc.docRev.OpenTemplateVersion(ctx, docID, base, docrevision.ActorSnapshot{DisplayName: "t"}, projectID, researchWork); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := svc.DocGov().CreateOrGetDocument(ctx, docID, projectID, "research", "调研报告", "V0.5", researchWork, 1, "a", 2, "r", 3, "p"); err != nil {
|
||||
if _, err := svc.docGov.CreateOrGetDocument(ctx, docID, projectID, "research", "调研报告", "V0.5", researchWork, 1, "a", 2, "r", 3, "p"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func TestExecuteStage_PendingAgentCandidate_StopsChainWithoutAbsorbingFacts(t *t
|
||||
}
|
||||
|
||||
// Blackboard / stage milestone must not treat this as a completed stage advance.
|
||||
pCtx, err := svc.ContextManager().GetProjectContext(ctx, projectID)
|
||||
pCtx, err := svc.ctxMgr.GetProjectContext(ctx, projectID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetProjectContext: %v", err)
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func TestExecuteStage_QualityFailed_StopsChainWithoutAbsorbingFactsOrPriorDoc(t
|
||||
t.Fatal("quality stop must not write completed stage workspace.json")
|
||||
}
|
||||
|
||||
pCtx, err := svc.ContextManager().GetProjectContext(ctx, projectID)
|
||||
pCtx, err := svc.ctxMgr.GetProjectContext(ctx, projectID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetProjectContext: %v", err)
|
||||
}
|
||||
|
||||
@@ -12,17 +12,17 @@ func TestExecuteTask_TestManagerAfterApprovedUpstream(t *testing.T) {
|
||||
svc := MustNewService(t, &config.Config{DataDir: t.TempDir(), AgentBackend: "stub"})
|
||||
ctx := context.Background()
|
||||
srDocID := "doc_proj1001_system_reqs"
|
||||
_, err := svc.DocGov().CreateOrGetDocument(
|
||||
_, err := svc.docGov.CreateOrGetDocument(
|
||||
ctx, srDocID, 1001, "system_reqs", "系统需求规格书", "V0.5", "",
|
||||
2, "张工", 3, "李主管", 4, "王总监",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateOrGetDocument: %v", err)
|
||||
}
|
||||
_ = svc.DocGov().MarkDocumentContentUpdated(ctx, srDocID, "hash_v1", false)
|
||||
_, _ = svc.DocGov().SubmitForReview(ctx, srDocID, 2, "author", "张工")
|
||||
_, _ = svc.DocGov().ReviewApprove(ctx, srDocID, 3, "reviewer", "李主管", "同意")
|
||||
_, err = svc.DocGov().ApproverApprove(ctx, srDocID, 4, "approver", "王总监", "批准")
|
||||
_ = svc.docGov.MarkDocumentContentUpdated(ctx, srDocID, "hash_v1", false)
|
||||
_, _ = svc.docGov.SubmitForReview(ctx, srDocID, 2, "author", "张工")
|
||||
_, _ = svc.docGov.ReviewApprove(ctx, srDocID, 3, "reviewer", "李主管", "同意")
|
||||
_, err = svc.docGov.ApproverApprove(ctx, srDocID, 4, "approver", "王总监", "批准")
|
||||
if err != nil {
|
||||
t.Fatalf("ApproverApprove: %v", err)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ import (
|
||||
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/kb"
|
||||
"chat-agent-system/internal/maturity"
|
||||
"chat-agent-system/internal/squad"
|
||||
"chat-agent-system/internal/storage"
|
||||
)
|
||||
|
||||
type TaskMeta = squad.TaskMeta
|
||||
@@ -157,8 +155,4 @@ type BizdevService interface {
|
||||
ExecuteStage(ctx context.Context, req StageRunRequest) (*StageRunResult, error)
|
||||
InspectWorkspace(ctx context.Context, workDir string) (*squad.WorkspaceState, error)
|
||||
ResumeOrModifyWorkspace(ctx context.Context, req squad.ModifyWorkflowRequest) (*squad.SquadRunResult, error)
|
||||
ContextManager() *ctxmgr.ContextManager
|
||||
CAS() *storage.CAS
|
||||
DocGov() *docgov.GovernanceEngine
|
||||
KB() *kb.Client
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
func TestExecuteAction_DocGovReviewApprove(t *testing.T) {
|
||||
cfg := &config.Config{DataDir: t.TempDir()}
|
||||
svc := bizdev.MustNewService(t, cfg)
|
||||
docGov := svc.DocGov()
|
||||
docGov := bizdev.Snapshot(svc).Gov
|
||||
ctx := context.Background()
|
||||
|
||||
docID := "doc_proj1_research"
|
||||
|
||||
@@ -101,7 +101,7 @@ func TestLongTaskGovernance_QualityE2E(t *testing.T) {
|
||||
svc := bizdev.MustNewService(t, cfg)
|
||||
dialogueBackend := agent.NewBackend("pi-agent", cfg)
|
||||
engine := coordinator.NewCoordinatorEngineWithDialogue(svc, dialogueBackend, cfg.DataDir)
|
||||
engine.SetContextServices(svc.ContextManager(), svc.KB())
|
||||
engine.SetContextServices(bizdev.Snapshot(svc).Ctx, bizdev.Snapshot(svc).KB)
|
||||
rec := intent.NewRecognizer()
|
||||
reportRoot := qualityReportDir(t)
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -270,7 +270,7 @@ func TestChatSession_AdvisorCaseRecommendation(t *testing.T) {
|
||||
## 关键性能与型检预估
|
||||
重点满足 GB 9706.1-2020 及 YY 0644 专用安全规范,型检周期约 5 个月。`
|
||||
|
||||
_, err := svc.ContextManager().IndexApprovedDocument(ctx, doc, body)
|
||||
_, err := bizdev.Snapshot(svc).Ctx.IndexApprovedDocument(ctx, doc, body)
|
||||
if err != nil {
|
||||
t.Fatalf("IndexApprovedDocument failed: %v", err)
|
||||
}
|
||||
@@ -278,7 +278,7 @@ func TestChatSession_AdvisorCaseRecommendation(t *testing.T) {
|
||||
// 2. 发起新会话咨询
|
||||
rec := intent.NewRecognizer()
|
||||
engine := coordinator.NewCoordinatorEngine(svc)
|
||||
engine.SetContextServices(svc.ContextManager(), svc.KB())
|
||||
engine.SetContextServices(bizdev.Snapshot(svc).Ctx, bizdev.Snapshot(svc).KB)
|
||||
userCtx := &coordinator.UserProjectContext{
|
||||
Project: coordinator.ProjectInfo{
|
||||
Name: "超声软组织刀研发咨询",
|
||||
@@ -294,7 +294,7 @@ func TestChatSession_AdvisorCaseRecommendation(t *testing.T) {
|
||||
}
|
||||
|
||||
session := chat.NewSession("sess_case_recommend", userCtx, rec, engine)
|
||||
session.CtxMgr = svc.ContextManager()
|
||||
session.CtxMgr = bizdev.Snapshot(svc).Ctx
|
||||
|
||||
resp, err := session.SendMessage(ctx, "我们之前做过类似的超声手术设备吗?注册和型检要注意什么?")
|
||||
if err != nil {
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestChatSession_AssistantMetadataTurnIntentAndReplySource(t *testing.T) {
|
||||
|
||||
cfg := &config.Config{DataDir: t.TempDir()}
|
||||
svc := bizdev.MustNewService(t, cfg)
|
||||
mgr := svc.ContextManager()
|
||||
mgr := bizdev.Snapshot(svc).Ctx
|
||||
|
||||
sess := chat.NewSession("sess_test_turn_intent", userCtx, rec, engine)
|
||||
sess.CtxMgr = mgr
|
||||
|
||||
@@ -5,14 +5,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"chat-agent-system/internal/app/stageflow"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/intent"
|
||||
"chat-agent-system/internal/kb"
|
||||
"chat-agent-system/internal/maturity"
|
||||
"chat-agent-system/internal/projectinstance"
|
||||
"chat-agent-system/internal/squad"
|
||||
"chat-agent-system/internal/storage"
|
||||
)
|
||||
|
||||
type countingBizdev struct {
|
||||
@@ -34,10 +30,6 @@ func (c *countingBizdev) InspectWorkspace(_ context.Context, _ string) (*squad.W
|
||||
func (c *countingBizdev) ResumeOrModifyWorkspace(_ context.Context, _ squad.ModifyWorkflowRequest) (*squad.SquadRunResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *countingBizdev) ContextManager() *ctxmgr.ContextManager { return nil }
|
||||
func (c *countingBizdev) CAS() *storage.CAS { return nil }
|
||||
func (c *countingBizdev) DocGov() *docgov.GovernanceEngine { return nil }
|
||||
func (c *countingBizdev) KB() *kb.Client { return nil }
|
||||
|
||||
func TestDialogueAdvanceStage_LiveInstanceDoesNotUseBizdev(t *testing.T) {
|
||||
store := projectinstance.NewMemoryStore()
|
||||
|
||||
@@ -7,13 +7,10 @@ import (
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/app/stageflow"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/intent"
|
||||
"chat-agent-system/internal/kb"
|
||||
"chat-agent-system/internal/maturity"
|
||||
"chat-agent-system/internal/squad"
|
||||
"chat-agent-system/internal/storage"
|
||||
)
|
||||
|
||||
type pendingReplyBizdev struct {
|
||||
@@ -34,10 +31,6 @@ func (p *pendingReplyBizdev) InspectWorkspace(_ context.Context, _ string) (*squ
|
||||
func (p *pendingReplyBizdev) ResumeOrModifyWorkspace(_ context.Context, _ squad.ModifyWorkflowRequest) (*squad.SquadRunResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (p *pendingReplyBizdev) ContextManager() *ctxmgr.ContextManager { return nil }
|
||||
func (p *pendingReplyBizdev) CAS() *storage.CAS { return nil }
|
||||
func (p *pendingReplyBizdev) DocGov() *docgov.GovernanceEngine { return nil }
|
||||
func (p *pendingReplyBizdev) KB() *kb.Client { return nil }
|
||||
|
||||
func TestSyncTriggerTask_PendingConfirmation_NotSavedCompleted(t *testing.T) {
|
||||
pending := &stageflow.TaskRunResult{
|
||||
|
||||
@@ -66,7 +66,7 @@ func (s *Server) handleUploadProjectAsset(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
cas := s.svc.CAS()
|
||||
cas := s.cas
|
||||
if cas == nil {
|
||||
http.Error(w, "storage unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
@@ -96,7 +96,7 @@ func (s *Server) handleListProjectAssets(w http.ResponseWriter, r *http.Request)
|
||||
if _, ok := s.requireProjectAccess(w, r, projectID); !ok {
|
||||
return
|
||||
}
|
||||
cas := s.svc.CAS()
|
||||
cas := s.cas
|
||||
if cas == nil {
|
||||
writeJSONResponse(w, http.StatusOK, []projectAssetResponse{})
|
||||
return
|
||||
|
||||
@@ -76,11 +76,11 @@ func (s *Server) requireAuthenticatedUser(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// assertSessionOwnedByUser 校验会话归属当前用户;会话不存在或归属他人返回 false。
|
||||
func (s *Server) assertSessionOwnedByUser(w http.ResponseWriter, r *http.Request, sessionID string, userID int64) bool {
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return false
|
||||
}
|
||||
ownerID, err := s.svc.ContextManager().GetSessionOwnerID(r.Context(), sessionID)
|
||||
ownerID, err := s.ctxMgr.GetSessionOwnerID(r.Context(), sessionID)
|
||||
if err != nil || ownerID <= 0 {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]string{
|
||||
"error_code": "SESSION_NOT_FOUND",
|
||||
|
||||
@@ -24,7 +24,7 @@ func (s *Server) bindSessionDocumentSummaryReader(sess *chat.Session, user *auth
|
||||
if user != nil {
|
||||
userID = user.ID
|
||||
}
|
||||
if projectID <= 0 || userID <= 0 || s.svc == nil || s.svc.DocGov() == nil {
|
||||
if projectID <= 0 || userID <= 0 || s.gov == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func (s *Server) bindSessionDocumentSummaryReader(sess *chat.Session, user *auth
|
||||
if err := s.authorizer.RequireProjectAccessOrAdmin(ctx, projectID, userID, s.isSystemAdminUser(ctx, user)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docs, err := s.svc.DocGov().ListWorkflowDocumentsByProject(ctx, projectID)
|
||||
docs, err := s.gov.ListWorkflowDocumentsByProject(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -33,13 +33,13 @@ func TestServer_ChatHistoryAndCaseRecommendationAPIs(t *testing.T) {
|
||||
sessID := "sess_api_history_001"
|
||||
userID := adminID
|
||||
|
||||
if _, err := svc.ContextManager().EnsureChatSession(ctx, sessID, userID, 0, "历史会话"); err != nil {
|
||||
if _, err := bizdev.Snapshot(svc).Ctx.EnsureChatSession(ctx, sessID, userID, 0, "历史会话"); err != nil {
|
||||
t.Fatalf("ensure session: %v", err)
|
||||
}
|
||||
|
||||
// 1. 模拟写入对话消息
|
||||
_ = svc.ContextManager().RecordUserMessage(ctx, sessID, userID, "user", "我们准备立项一款便携无线掌上超声,预算大约150万", "SUPPLEMENT_INFO", "EXPECTATION_HIT", nil)
|
||||
_ = svc.ContextManager().RecordUserMessage(ctx, sessID, 0, "assistant", "已为您建立超声设备立项基准,预估研发周期6个月", "ADVANCE_STAGE", "EXPECTATION_HIT", nil)
|
||||
_ = bizdev.Snapshot(svc).Ctx.RecordUserMessage(ctx, sessID, userID, "user", "我们准备立项一款便携无线掌上超声,预算大约150万", "SUPPLEMENT_INFO", "EXPECTATION_HIT", nil)
|
||||
_ = bizdev.Snapshot(svc).Ctx.RecordUserMessage(ctx, sessID, 0, "assistant", "已为您建立超声设备立项基准,预估研发周期6个月", "ADVANCE_STAGE", "EXPECTATION_HIT", nil)
|
||||
|
||||
// 2. 测试 GET /api/v1/chat/sessions
|
||||
reqSessions := httptest.NewRequest("GET", "/api/v1/chat/sessions?user_id="+strconv.FormatInt(userID, 10), nil)
|
||||
@@ -123,7 +123,7 @@ func TestServer_ChatHistoryAndCaseRecommendationAPIs(t *testing.T) {
|
||||
## 关键性能与型检预估
|
||||
符合 GB 9706.1-2020 和 YY 0644 标准要求,探头无线射频满足 SRRC 与无线电合规要求。`
|
||||
|
||||
_, err := svc.ContextManager().IndexApprovedDocument(ctx, doc, docBody)
|
||||
_, err := bizdev.Snapshot(svc).Ctx.IndexApprovedDocument(ctx, doc, docBody)
|
||||
if err != nil {
|
||||
t.Fatalf("IndexApprovedDocument failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ func (s *Server) authorizeSessionRead(
|
||||
return nil, false
|
||||
}
|
||||
sessionOwnerID := user.ID
|
||||
if s.svc != nil && s.svc.ContextManager() != nil {
|
||||
if ownerID, err := s.svc.ContextManager().GetSessionOwnerID(r.Context(), sessionID); err == nil && ownerID > 0 {
|
||||
if s.ctxMgr != nil {
|
||||
if ownerID, err := s.ctxMgr.GetSessionOwnerID(r.Context(), sessionID); err == nil && ownerID > 0 {
|
||||
sessionOwnerID = ownerID
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,8 @@ func (s *Server) authorizeSessionRead(
|
||||
SessionOwnerID: sessionOwnerID,
|
||||
ViewerUserID: user.ID,
|
||||
}
|
||||
if s.svc != nil && s.svc.ContextManager() != nil {
|
||||
if projectID, err := s.svc.ContextManager().GetSessionProjectID(r.Context(), sessionID); err == nil {
|
||||
if s.ctxMgr != nil {
|
||||
if projectID, err := s.ctxMgr.GetSessionProjectID(r.Context(), sessionID); err == nil {
|
||||
authCtx.ProjectID = projectID
|
||||
}
|
||||
}
|
||||
@@ -200,10 +200,10 @@ func aggregateCategoryStats(msgs []ctxmgr.ChatMessageRecord) []ctxmgr.ChatCatego
|
||||
}
|
||||
|
||||
func (s *Server) listVisibleSessionMessages(ctx context.Context, authCtx *chatHistoryAuth) ([]ctxmgr.ChatMessageRecord, error) {
|
||||
if s.svc == nil || s.svc.ContextManager() == nil || authCtx == nil {
|
||||
if s.ctxMgr == nil || authCtx == nil {
|
||||
return nil, nil
|
||||
}
|
||||
msgs, err := s.svc.ContextManager().ListSessionMessagesForAuth(ctx, authCtx.SessionID)
|
||||
msgs, err := s.ctxMgr.ListSessionMessagesForAuth(ctx, authCtx.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/bizdev"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/projectinstance"
|
||||
"chat-agent-system/internal/workflowdef"
|
||||
@@ -24,7 +25,7 @@ func TestChatHistoryRefresh_PG_PreActivationAndTaskScope(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
handler, srv := setupPGGovernanceServer(t, pool)
|
||||
ctxMgr := srv.BizdevService().ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(srv.BizdevService()).Ctx
|
||||
if ctxMgr == nil {
|
||||
t.Fatal("context manager required")
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ type recategorizeReq struct {
|
||||
}
|
||||
|
||||
func (s *Server) handleChatRecategorize(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func (s *Server) handleChatRecategorize(w http.ResponseWriter, r *http.Request)
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
}
|
||||
|
||||
res, err := s.svc.ContextManager().RecategorizeMessages(r.Context(), req.SessionID)
|
||||
res, err := s.ctxMgr.RecategorizeMessages(r.Context(), req.SessionID)
|
||||
if err != nil {
|
||||
writeJSONResponse(w, http.StatusInternalServerError, map[string]any{
|
||||
"success": false,
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestServer_ChatRecategorizeAPI(t *testing.T) {
|
||||
|
||||
sessID := "sess_recategorize_api_001"
|
||||
ctx := t.Context()
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
|
||||
// 注入种子消息
|
||||
_, _ = ctxMgr.RecordUserMessageWithID(ctx, sessID, 1, "user", "请问二类注册审批周期?", "QUERY_REGULATIONS", "", nil)
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestServer_ChatRestoreAfterRestart(t *testing.T) {
|
||||
t.Logf("✓ Turn 1 完成: %s", reply1)
|
||||
|
||||
// 验证消息已被持久化至 ContextManager
|
||||
msgs, err := svc.ContextManager().GetChatHistory(context.Background(), sessID, 10, 0)
|
||||
msgs, err := bizdev.Snapshot(svc).Ctx.GetChatHistory(context.Background(), sessID, 10, 0)
|
||||
if err != nil || len(msgs) != 2 {
|
||||
t.Fatalf("expected 2 messages in ContextManager, got %d (err: %v)", len(msgs), err)
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func TestServer_ChatRestoreAfterRestart(t *testing.T) {
|
||||
t.Logf("✓ Turn 2 完成: %s", reply2)
|
||||
|
||||
// 验证完整历史记录包含全部 4 条消息 (Turn 1 user+asst, Turn 2 user+asst)
|
||||
allMsgs, err := svc.ContextManager().GetChatHistory(context.Background(), sessID, 10, 0)
|
||||
allMsgs, err := bizdev.Snapshot(svc).Ctx.GetChatHistory(context.Background(), sessID, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query chat history: %v", err)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestServer_ChatSearchAndDigestAPIs(t *testing.T) {
|
||||
|
||||
// 注入种子消息
|
||||
ctx := t.Context()
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
_, _ = ctxMgr.RecordUserMessageWithID(ctx, sessID, 1, "user", "我们启动超声探头研发预算,计划投入150万元", "TRIGGER_TASK", "", map[string]any{
|
||||
"day_key": dayKey,
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@ func (s *Server) handleGetChatTurn(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctxMgr := s.svc.ContextManager()
|
||||
ctxMgr := s.ctxMgr
|
||||
if ctxMgr == nil {
|
||||
http.Error(w, "context manager unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestServer_ChatUnreadAndExportAPIs(t *testing.T) {
|
||||
dayKey2 := "2026-09-09"
|
||||
|
||||
ctx := t.Context()
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
token := loginTestAdmin(t, handler, cfg)
|
||||
if _, err := ctxMgr.EnsureChatSession(ctx, sessID, 1, 0, "未读导出测试"); err != nil {
|
||||
t.Fatalf("EnsureChatSession failed: %v", err)
|
||||
|
||||
@@ -162,7 +162,7 @@ func (s *Server) handleListChatCategories(w http.ResponseWriter, r *http.Request
|
||||
// handleImportChatMessages 一次性批量回填历史消息 (仅允许在 dev/demo 环境下调用)
|
||||
// POST /api/v1/chat/messages/import
|
||||
func (s *Server) handleImportChatMessages(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func (s *Server) handleImportChatMessages(w http.ResponseWriter, r *http.Request
|
||||
item.Metadata["created_at"] = item.CreatedAt
|
||||
}
|
||||
|
||||
_, err := s.svc.ContextManager().RecordUserMessageWithID(
|
||||
_, err := s.ctxMgr.RecordUserMessageWithID(
|
||||
r.Context(),
|
||||
item.SessionID,
|
||||
item.UserID,
|
||||
|
||||
@@ -29,13 +29,13 @@ func (s *Server) resolveDocRevisionRef(ctx context.Context, user *auth.UserAccou
|
||||
if strings.TrimSpace(docID) == "" {
|
||||
return nil, docrevision.ErrInvalidDocID
|
||||
}
|
||||
if s.svc != nil && s.svc.DocGov() != nil {
|
||||
if doc, err := s.svc.DocGov().GetDocument(ctx, docID); err == nil && doc != nil {
|
||||
if s.gov != nil {
|
||||
if doc, err := s.gov.GetDocument(ctx, docID); err == nil && doc != nil {
|
||||
ref := &docRevisionRef{
|
||||
Kind: "controlled", ProjectID: doc.ProjectID, WorkDir: doc.WorkDir,
|
||||
LifecycleState: string(doc.CurrentState), AuthorID: doc.AuthorID,
|
||||
}
|
||||
if content, cErr := s.svc.DocGov().ReadDocumentContent(ctx, docID, s.dataRoot()); cErr == nil && content != nil {
|
||||
if content, cErr := s.gov.ReadDocumentContent(ctx, docID, s.dataRoot()); cErr == nil && content != nil {
|
||||
ref.Markdown = content.Markdown
|
||||
} else if doc.WorkDir != "" {
|
||||
md, _ := readOutputMarkdown(s.dataRoot(), doc.WorkDir)
|
||||
@@ -44,8 +44,8 @@ func (s *Server) resolveDocRevisionRef(ctx context.Context, user *auth.UserAccou
|
||||
return ref, nil
|
||||
}
|
||||
}
|
||||
if s.svc != nil && s.svc.FreeformDoc() != nil && user != nil {
|
||||
if detail, err := s.svc.FreeformDoc().GetDocument(ctx, int(user.ID), docID); err == nil && detail != nil {
|
||||
if s.freeDoc != nil && user != nil {
|
||||
if detail, err := s.freeDoc.GetDocument(ctx, int(user.ID), docID); err == nil && detail != nil {
|
||||
return &docRevisionRef{
|
||||
Kind: "freeform", ProjectID: int64(detail.Document.ProjectID),
|
||||
LifecycleState: "drafting", Markdown: detail.Content, Title: detail.Document.Title,
|
||||
|
||||
@@ -76,10 +76,10 @@ func TestDocRevision_ControlledDocumentRoutes(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
ctx := context.Background()
|
||||
docID := "doc_proj1_intake"
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(ctx, docID, 1, "intake", "摸底表", "V0.1", "", 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
if _, err := srv.gov.CreateOrGetDocument(ctx, docID, 1, "intake", "摸底表", "V0.1", "", 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := srv.svc.DocRevision().OpenTemplateVersion(ctx, docID, "# 摸底\n\n正文\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, ""); err != nil {
|
||||
if _, err := srv.rev.OpenTemplateVersion(ctx, docID, "# 摸底\n\n正文\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token := loginTestAdmin(t, srv)
|
||||
@@ -102,14 +102,14 @@ func TestDocRevision_PendingCandidateExposedAndDecisionRequiresAuthor(t *testing
|
||||
srv := newTestServer(t)
|
||||
ctx := context.Background()
|
||||
docID := "doc_proj1_pending"
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", "", 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
if _, err := srv.gov.CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", "", 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := srv.svc.DocRevision().OpenTemplateVersion(ctx, docID, "# base\n\n法规 GB9706\n", docrevision.ActorSnapshot{}, 1, "")
|
||||
st, err := srv.rev.OpenTemplateVersion(ctx, docID, "# base\n\n法规 GB9706\n", docrevision.ActorSnapshot{}, 1, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cand, _, err := srv.svc.DocRevision().CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
||||
cand, _, err := srv.rev.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
||||
DocID: docID, RunID: "high-1", InputSnapshotID: st.HeadSnapshotID,
|
||||
OutputMarkdown: "# base\n\n", ActorKind: docrevision.ActorAgentSquad,
|
||||
})
|
||||
@@ -141,14 +141,14 @@ func TestDocRevision_PendingCandidateExposedAndDecisionRequiresAuthor(t *testing
|
||||
|
||||
// Non-author cannot decide (author is 1=admin in this fixture — use author 99 doc).
|
||||
docID2 := "doc_proj1_pending_na"
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(ctx, docID2, 1, "research", "研", "V0.5", "", 99, "other", 2, "r", 3, "a"); err != nil {
|
||||
if _, err := srv.gov.CreateOrGetDocument(ctx, docID2, 1, "research", "研", "V0.5", "", 99, "other", 2, "r", 3, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st2, err := srv.svc.DocRevision().OpenTemplateVersion(ctx, docID2, "# x\n\n法规\n", docrevision.ActorSnapshot{}, 1, "")
|
||||
st2, err := srv.rev.OpenTemplateVersion(ctx, docID2, "# x\n\n法规\n", docrevision.ActorSnapshot{}, 1, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cand2, _, err := srv.svc.DocRevision().CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
||||
cand2, _, err := srv.rev.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
||||
DocID: docID2, RunID: "high-2", InputSnapshotID: st2.HeadSnapshotID,
|
||||
OutputMarkdown: "# x\n\n", ActorKind: docrevision.ActorAgentSquad,
|
||||
})
|
||||
@@ -191,15 +191,15 @@ func TestDocRevision_CommitWithMissingProposalReturnsGovernanceError(t *testing.
|
||||
srv := newTestServer(t)
|
||||
ctx := context.Background()
|
||||
docID := "doc_proj1_gov_prop"
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", "", 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
if _, err := srv.gov.CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", "", 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := srv.svc.DocRevision().OpenTemplateVersion(ctx, docID, "# base\n\n法规 GB9706\n", docrevision.ActorSnapshot{}, 1, "")
|
||||
st, err := srv.rev.OpenTemplateVersion(ctx, docID, "# base\n\n法规 GB9706\n", docrevision.ActorSnapshot{}, 1, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ghostProp := "prop_does_not_exist"
|
||||
cand, _, err := srv.svc.DocRevision().CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
||||
cand, _, err := srv.rev.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
||||
DocID: docID, RunID: "gov-miss-1", ProposalID: ghostProp,
|
||||
InputSnapshotID: st.HeadSnapshotID,
|
||||
OutputMarkdown: "# base\n\n", ActorKind: docrevision.ActorAgentSquad,
|
||||
@@ -230,7 +230,7 @@ func TestDocRevision_CommitWithMissingProposalReturnsGovernanceError(t *testing.
|
||||
t.Fatalf("code=%v body=%#v", body["code"], body)
|
||||
}
|
||||
|
||||
gotCand, err := srv.svc.DocRevision().GetCandidate(ctx, docID, cand.ID)
|
||||
gotCand, err := srv.rev.GetCandidate(ctx, docID, cand.ID)
|
||||
if err != nil || gotCand.Status != docrevision.CandidateCommitted {
|
||||
t.Fatalf("ledger must stay committed after governance failure: %+v err=%v", gotCand, err)
|
||||
}
|
||||
@@ -257,21 +257,21 @@ func TestDocRevision_DecisionIdempotentWithRealProposal(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
ctx := context.Background()
|
||||
docID := "doc_proj1_gov_prop_ok"
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", "", 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
if _, err := srv.gov.CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", "", 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prop, err := srv.svc.DocGov().ProposeModification(ctx, docID, "agent", "改", "影响", "风险")
|
||||
prop, err := srv.gov.ProposeModification(ctx, docID, "agent", "改", "影响", "风险")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := srv.svc.DocGov().DecideModificationProposal(ctx, prop.ProposalID, 1, true, "同意"); err != nil {
|
||||
if _, err := srv.gov.DecideModificationProposal(ctx, prop.ProposalID, 1, true, "同意"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := srv.svc.DocRevision().OpenTemplateVersion(ctx, docID, "# base\n\n法规 GB9706\n", docrevision.ActorSnapshot{}, 1, "")
|
||||
st, err := srv.rev.OpenTemplateVersion(ctx, docID, "# base\n\n法规 GB9706\n", docrevision.ActorSnapshot{}, 1, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cand, _, err := srv.svc.DocRevision().CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
||||
cand, _, err := srv.rev.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
||||
DocID: docID, RunID: "gov-ok-1", ProposalID: prop.ProposalID,
|
||||
InputSnapshotID: st.HeadSnapshotID,
|
||||
OutputMarkdown: "# base\n\n", ActorKind: docrevision.ActorAgentSquad,
|
||||
@@ -295,7 +295,7 @@ func TestDocRevision_DecisionIdempotentWithRealProposal(t *testing.T) {
|
||||
t.Fatalf("%s decision want 200 got %d %s", label, w.Code, w.Body.String())
|
||||
}
|
||||
if i == 0 {
|
||||
got, err := srv.svc.DocRevision().GetCandidate(ctx, docID, cand.ID)
|
||||
got, err := srv.rev.GetCandidate(ctx, docID, cand.ID)
|
||||
if err != nil || got.Status != docrevision.CandidateCommitted {
|
||||
t.Fatalf("after first: %+v err=%v", got, err)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestAuthorizeDocRevision_ReviewerAndNonMemberCannotWrite(t *testing.T) {
|
||||
strangerID int64 = 99
|
||||
docID = "doc_proj_duty_write"
|
||||
)
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(
|
||||
if _, err := srv.gov.CreateOrGetDocument(
|
||||
ctx, docID, 6145, "intake", "摸底", "V0.1", "",
|
||||
authorID, "编制人A", reviewerID, "审核人B", 0, "",
|
||||
); err != nil {
|
||||
|
||||
@@ -40,7 +40,7 @@ func (s *Server) docRev() *docrevision.Service {
|
||||
if s == nil || s.svc == nil {
|
||||
return nil
|
||||
}
|
||||
return s.svc.DocRevision()
|
||||
return s.rev
|
||||
}
|
||||
|
||||
func (s *Server) handleDocRevisionState(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -211,8 +211,8 @@ func (s *Server) handleDocRevisionPatchContent(w http.ResponseWriter, r *http.Re
|
||||
// 1) Controlled: author duty + lease WITHOUT writing body (fail-closed before ledger).
|
||||
// This UI has no edit-session heartbeat: release the request-scoped human lease on every exit
|
||||
// (success, 409, projection failure) so a 15m residual cannot block a later squad run.
|
||||
if kind == "controlled" && s.svc != nil && s.svc.DocGov() != nil {
|
||||
if err := s.svc.DocGov().AssertHumanCanEdit(r.Context(), docID, user.ID, docgov.RoleAuthor); err != nil {
|
||||
if kind == "controlled" && s.gov != nil {
|
||||
if err := s.gov.AssertHumanCanEdit(r.Context(), docID, user.ID, docgov.RoleAuthor); err != nil {
|
||||
writeDocGovError(w, err)
|
||||
return
|
||||
}
|
||||
@@ -221,7 +221,7 @@ func (s *Server) handleDocRevisionPatchContent(w http.ResponseWriter, r *http.Re
|
||||
holderID = "human_anon"
|
||||
}
|
||||
defer func() {
|
||||
_, _ = s.svc.DocGov().ReleaseLease(context.Background(), docID, string(docgov.LeaseHolderHuman), holderID)
|
||||
_, _ = s.gov.ReleaseLease(context.Background(), docID, string(docgov.LeaseHolderHuman), holderID)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -273,8 +273,8 @@ func (s *Server) handleDocRevisionPatchContent(w http.ResponseWriter, r *http.Re
|
||||
// 3) Project fact-source AFTER ledger commit. Controlled docs use SaveDocumentContent so
|
||||
// author_last_edited / audit trail stay consistent; failure → DOCUMENT_PROJECTION_FAILED
|
||||
// (ledger remains authoritative; same Idempotency-Key retry re-projects).
|
||||
if kind == "controlled" && s.svc != nil && s.svc.DocGov() != nil {
|
||||
if _, err := s.svc.DocGov().SaveDocumentContent(r.Context(), docgov.ContentSaveRequest{
|
||||
if kind == "controlled" && s.gov != nil {
|
||||
if _, err := s.gov.SaveDocumentContent(r.Context(), docgov.ContentSaveRequest{
|
||||
DocID: docID,
|
||||
ActorID: user.ID,
|
||||
ActorRole: docgov.RoleAuthor,
|
||||
@@ -300,9 +300,9 @@ func (s *Server) handleDocRevisionPatchContent(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
}
|
||||
if kind == "freeform" && s.svc != nil && s.svc.FreeformDoc() != nil {
|
||||
if kind == "freeform" && s.freeDoc != nil {
|
||||
md := body.Markdown
|
||||
if _, err := s.svc.FreeformDoc().UpdateDocument(r.Context(), int(user.ID), docID, freedoc.UpdateFreeformDocRequest{
|
||||
if _, err := s.freeDoc.UpdateDocument(r.Context(), int(user.ID), docID, freedoc.UpdateFreeformDocRequest{
|
||||
ContentMarkdown: &md,
|
||||
ChangeSummary: "revision ledger human save",
|
||||
}); err != nil {
|
||||
@@ -390,8 +390,8 @@ func (s *Server) handleDocRevisionCandidateDecision(w http.ResponseWriter, r *ht
|
||||
})
|
||||
return
|
||||
}
|
||||
if s.svc != nil && s.svc.DocGov() != nil && st != nil {
|
||||
if err := s.svc.DocGov().MarkDocumentContentUpdated(r.Context(), docID, st.HeadSHA256, false); err != nil {
|
||||
if s.gov != nil && st != nil {
|
||||
if err := s.gov.MarkDocumentContentUpdated(r.Context(), docID, st.HeadSHA256, false); err != nil {
|
||||
writeJSONResponse(w, http.StatusInternalServerError, map[string]any{
|
||||
"code": "DOCUMENT_PROJECTION_FAILED",
|
||||
"error": err.Error(),
|
||||
@@ -401,7 +401,7 @@ func (s *Server) handleDocRevisionCandidateDecision(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
if body.Decision == "commit" && cand != nil && strings.TrimSpace(cand.ProposalID) != "" {
|
||||
if err := s.svc.DocGov().MarkProposalApplied(r.Context(), cand.ProposalID); err != nil {
|
||||
if err := s.gov.MarkProposalApplied(r.Context(), cand.ProposalID); err != nil {
|
||||
writeJSONResponse(w, http.StatusInternalServerError, map[string]any{
|
||||
"code": "DOCUMENT_GOVERNANCE_FAILED",
|
||||
"error": err.Error(),
|
||||
|
||||
@@ -29,10 +29,10 @@ func TestDocRevision_PatchOrder_AssertBeforeLedger_ProjectionFailureKeepsLedger(
|
||||
workDir := filepath.Join(blocker, "ws-proj")
|
||||
docID := "doc_proj1_patch_order"
|
||||
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", workDir, 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
if _, err := srv.gov.CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", workDir, 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := srv.svc.DocRevision().OpenTemplateVersion(ctx, docID, "# base\n\nbody\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, workDir)
|
||||
st, err := srv.rev.OpenTemplateVersion(ctx, docID, "# base\n\nbody\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, workDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func TestDocRevision_PatchOrder_AssertBeforeLedger_ProjectionFailureKeepsLedger(
|
||||
if payload["code"] != "DOCUMENT_PROJECTION_FAILED" {
|
||||
t.Fatalf("code want DOCUMENT_PROJECTION_FAILED got %#v", payload["code"])
|
||||
}
|
||||
after, err := srv.svc.DocRevision().GetRevisionState(ctx, docID)
|
||||
after, err := srv.rev.GetRevisionState(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -76,10 +76,10 @@ func TestDocRevision_PatchOrder_LeaseDeniedDoesNotAdvanceLedger(t *testing.T) {
|
||||
_ = os.MkdirAll(workDir, 0o755)
|
||||
|
||||
// Author is user 99; login admin may bypass route author gate but AssertHumanCanEdit (RoleAuthor) still fails.
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", workDir, 99, "other", 2, "r", 3, "a"); err != nil {
|
||||
if _, err := srv.gov.CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", workDir, 99, "other", 2, "r", 3, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := srv.svc.DocRevision().OpenTemplateVersion(ctx, docID, "# L\n\nx\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, workDir)
|
||||
st, err := srv.rev.OpenTemplateVersion(ctx, docID, "# L\n\nx\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, workDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -96,14 +96,14 @@ func TestDocRevision_PatchOrder_LeaseDeniedDoesNotAdvanceLedger(t *testing.T) {
|
||||
if w.Code == http.StatusOK {
|
||||
t.Fatalf("non-author must not save successfully: %s", w.Body.String())
|
||||
}
|
||||
after, err := srv.svc.DocRevision().GetRevisionState(ctx, docID)
|
||||
after, err := srv.rev.GetRevisionState(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if after.HeadRevisionNo != before {
|
||||
t.Fatalf("ledger must not advance when AssertHumanCanEdit fails: before=%d after=%d", before, after.HeadRevisionNo)
|
||||
}
|
||||
doc, err := srv.svc.DocGov().GetDocument(ctx, docID)
|
||||
doc, err := srv.gov.GetDocument(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -119,10 +119,10 @@ func TestDocRevision_PatchOrder_SaveDocumentContentSetsAuthorAudit(t *testing.T)
|
||||
_ = os.MkdirAll(workDir, 0o755)
|
||||
docID := "doc_proj1_patch_audit"
|
||||
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", workDir, 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
if _, err := srv.gov.CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", workDir, 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := srv.svc.DocRevision().OpenTemplateVersion(ctx, docID, "# base\n\nbody\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, workDir)
|
||||
st, err := srv.rev.OpenTemplateVersion(ctx, docID, "# base\n\nbody\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, workDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func TestDocRevision_PatchOrder_SaveDocumentContentSetsAuthorAudit(t *testing.T)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("want 200 got %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
doc, err := srv.svc.DocGov().GetDocument(ctx, docID)
|
||||
doc, err := srv.gov.GetDocument(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -162,10 +162,10 @@ func TestDocRevision_PatchReleasesHumanLeaseOnHeadConflict(t *testing.T) {
|
||||
_ = os.MkdirAll(workDir, 0o755)
|
||||
docID := "doc_proj1_patch_conflict_lease"
|
||||
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", workDir, 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
if _, err := srv.gov.CreateOrGetDocument(ctx, docID, 1, "intake", "摸底", "V0.1", workDir, 1, "admin", 2, "r", 3, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := srv.svc.DocRevision().OpenTemplateVersion(ctx, docID, "# base\n\nbody\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, workDir)
|
||||
st, err := srv.rev.OpenTemplateVersion(ctx, docID, "# base\n\nbody\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, workDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -199,14 +199,14 @@ func TestDocRevision_PatchReleasesHumanLeaseOnHeadConflict(t *testing.T) {
|
||||
func assertHumanLeaseReleasedSquadCanAcquire(t *testing.T, srv *Server, docID string) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
doc, err := srv.svc.DocGov().GetDocument(ctx, docID)
|
||||
doc, err := srv.gov.GetDocument(ctx, docID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if doc.HasActiveLease(time.Now()) {
|
||||
t.Fatalf("human lease must be released after PATCH, holder=%s/%s", doc.LeaseHolderType, doc.LeaseHolderID)
|
||||
}
|
||||
leased, err := srv.svc.DocGov().AcquireLease(ctx, docgov.LeaseAcquireRequest{
|
||||
leased, err := srv.gov.AcquireLease(ctx, docgov.LeaseAcquireRequest{
|
||||
DocID: docID,
|
||||
HolderType: docgov.LeaseHolderSquad,
|
||||
HolderID: "run_after_human_save",
|
||||
@@ -218,7 +218,7 @@ func assertHumanLeaseReleasedSquadCanAcquire(t *testing.T, srv *Server, docID st
|
||||
if leased.LeaseHolderType != docgov.LeaseHolderSquad {
|
||||
t.Fatalf("want squad holder, got %s", leased.LeaseHolderType)
|
||||
}
|
||||
if _, err := srv.svc.DocGov().ReleaseLease(ctx, docID, string(docgov.LeaseHolderSquad), "run_after_human_save"); err != nil {
|
||||
if _, err := srv.gov.ReleaseLease(ctx, docID, string(docgov.LeaseHolderSquad), "run_after_human_save"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/bizdev"
|
||||
"chat-agent-system/internal/db"
|
||||
"chat-agent-system/internal/projectinstance"
|
||||
"chat-agent-system/internal/workflowdef"
|
||||
@@ -96,7 +97,7 @@ func TestE2E_WorkflowGovernance_CompleteAndReplan(t *testing.T) {
|
||||
_ = json.Unmarshal(recActivate.Body.Bytes(), &activated)
|
||||
|
||||
docID := fmt.Sprintf("doc_proj%d_test_manager", projectID)
|
||||
docEngine := srv.BizdevService().DocGov()
|
||||
docEngine := bizdev.Snapshot(srv.BizdevService()).Gov
|
||||
_, err = docEngine.CreateOrGetDocument(ctx, docID, projectID, "test_manager", "系统验证方案及计划", "system_verification_planning", "",
|
||||
creatorID, "Author", reviewerID, "Reviewer", approverID, "Approver")
|
||||
if err != nil {
|
||||
|
||||
@@ -36,7 +36,7 @@ func extractDocIDFromPath(r *http.Request, actionSuffix string) string {
|
||||
|
||||
// POST /api/v1/freeform-documents — 直接创建(上传附件注册服务端事实源,仅 my_private)
|
||||
func (s *Server) handleCreateFreeformDoc(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.FreeformDoc() == nil {
|
||||
if s.freeDoc == nil {
|
||||
http.Error(w, "freeform doc service not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func (s *Server) handleCreateFreeformDoc(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
detail, err := s.svc.FreeformDoc().CreateDocument(r.Context(), int(user.ID), req)
|
||||
detail, err := s.freeDoc.CreateDocument(r.Context(), int(user.ID), req)
|
||||
if err != nil {
|
||||
if errors.Is(err, freedoc.ErrInvalidParam) {
|
||||
writeJSONResponse(w, http.StatusBadRequest, map[string]any{
|
||||
@@ -106,7 +106,7 @@ func (s *Server) handleCreateFreeformDoc(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// POST /api/v1/freeform-documents/from-message
|
||||
func (s *Server) handleCreateFreeformDocFromMessage(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.FreeformDoc() == nil {
|
||||
if s.freeDoc == nil {
|
||||
http.Error(w, "freeform doc service not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -124,7 +124,7 @@ func (s *Server) handleCreateFreeformDocFromMessage(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
detail, isExisting, err := s.svc.FreeformDoc().CreateFromMessage(r.Context(), userID, req)
|
||||
detail, isExisting, err := s.freeDoc.CreateFromMessage(r.Context(), userID, req)
|
||||
if err != nil {
|
||||
if errors.Is(err, freedoc.ErrMessageNotExportable) {
|
||||
writeJSONResponse(w, http.StatusForbidden, map[string]any{
|
||||
@@ -168,7 +168,7 @@ func (s *Server) handleCreateFreeformDocFromMessage(w http.ResponseWriter, r *ht
|
||||
|
||||
// GET /api/v1/freeform-documents
|
||||
func (s *Server) handleListFreeformDocs(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.FreeformDoc() == nil {
|
||||
if s.freeDoc == nil {
|
||||
http.Error(w, "freeform doc service not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -186,7 +186,7 @@ func (s *Server) handleListFreeformDocs(w http.ResponseWriter, r *http.Request)
|
||||
projectID, _ = strconv.Atoi(pStr)
|
||||
}
|
||||
|
||||
list, err := s.svc.FreeformDoc().ListDocuments(r.Context(), userID, projectID, space)
|
||||
list, err := s.freeDoc.ListDocuments(r.Context(), userID, projectID, space)
|
||||
if err != nil {
|
||||
writeJSONResponse(w, http.StatusInternalServerError, map[string]any{
|
||||
"error_code": "LIST_FAILED",
|
||||
@@ -206,7 +206,7 @@ func (s *Server) handleListFreeformDocs(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// GET /api/v1/freeform-documents/{doc_id}
|
||||
func (s *Server) handleGetFreeformDoc(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.FreeformDoc() == nil {
|
||||
if s.freeDoc == nil {
|
||||
http.Error(w, "freeform doc service not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -224,7 +224,7 @@ func (s *Server) handleGetFreeformDoc(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
detail, err := s.svc.FreeformDoc().GetDocument(r.Context(), userID, docID)
|
||||
detail, err := s.freeDoc.GetDocument(r.Context(), userID, docID)
|
||||
if err != nil {
|
||||
if errors.Is(err, freedoc.ErrNotFound) {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]any{
|
||||
@@ -255,7 +255,7 @@ func (s *Server) handleGetFreeformDoc(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// PUT /api/v1/freeform-documents/{doc_id}
|
||||
func (s *Server) handleUpdateFreeformDoc(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.FreeformDoc() == nil {
|
||||
if s.freeDoc == nil {
|
||||
http.Error(w, "freeform doc service not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -292,7 +292,7 @@ func (s *Server) handleUpdateFreeformDoc(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
|
||||
detail, err := s.svc.FreeformDoc().UpdateDocument(r.Context(), userID, docID, req)
|
||||
detail, err := s.freeDoc.UpdateDocument(r.Context(), userID, docID, req)
|
||||
if err != nil {
|
||||
if errors.Is(err, freedoc.ErrNotFound) {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]any{
|
||||
@@ -331,7 +331,7 @@ func (s *Server) handleUpdateFreeformDoc(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// POST /api/v1/freeform-documents/{doc_id}/share
|
||||
func (s *Server) handleShareFreeformDoc(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.FreeformDoc() == nil {
|
||||
if s.freeDoc == nil {
|
||||
http.Error(w, "freeform doc service not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -349,7 +349,7 @@ func (s *Server) handleShareFreeformDoc(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := s.svc.FreeformDoc().ShareDocument(r.Context(), userID, docID)
|
||||
doc, err := s.freeDoc.ShareDocument(r.Context(), userID, docID)
|
||||
if err != nil {
|
||||
if errors.Is(err, freedoc.ErrNotFound) {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]any{"error_code": "DOC_NOT_FOUND", "message": "文档不存在"})
|
||||
@@ -372,7 +372,7 @@ func (s *Server) handleShareFreeformDoc(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// POST /api/v1/freeform-documents/{doc_id}/unshare
|
||||
func (s *Server) handleUnshareFreeformDoc(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.FreeformDoc() == nil {
|
||||
if s.freeDoc == nil {
|
||||
http.Error(w, "freeform doc service not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -390,7 +390,7 @@ func (s *Server) handleUnshareFreeformDoc(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := s.svc.FreeformDoc().UnshareDocument(r.Context(), userID, docID)
|
||||
doc, err := s.freeDoc.UnshareDocument(r.Context(), userID, docID)
|
||||
if err != nil {
|
||||
if errors.Is(err, freedoc.ErrNotFound) {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]any{"error_code": "DOC_NOT_FOUND", "message": "文档不存在"})
|
||||
@@ -413,7 +413,7 @@ func (s *Server) handleUnshareFreeformDoc(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// DELETE /api/v1/freeform-documents/{doc_id}
|
||||
func (s *Server) handleDeleteFreeformDoc(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.FreeformDoc() == nil {
|
||||
if s.freeDoc == nil {
|
||||
http.Error(w, "freeform doc service not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -431,7 +431,7 @@ func (s *Server) handleDeleteFreeformDoc(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
err := s.svc.FreeformDoc().DeleteDocument(r.Context(), userID, docID)
|
||||
err := s.freeDoc.DeleteDocument(r.Context(), userID, docID)
|
||||
if err != nil {
|
||||
if errors.Is(err, freedoc.ErrNotFound) {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]any{"error_code": "DOC_NOT_FOUND", "message": "文档不存在"})
|
||||
|
||||
@@ -25,7 +25,7 @@ func setupTestServerForFreeformDoc(t *testing.T) (*server.Server, http.Handler,
|
||||
handler := srv.Routes()
|
||||
|
||||
ctx := context.Background()
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
|
||||
validMsgID, err := ctxMgr.RecordUserMessageWithID(ctx, "sess-test", 1, "assistant", "# 临床安全评估\n\n评估合格。", "consult", "main", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
+40
-40
@@ -102,7 +102,7 @@ func (s *Server) buildHealthResponse(strict bool) (HealthResponse, int) {
|
||||
}
|
||||
}
|
||||
schemaReady := schemaVersion > 0 || (s.cfg != nil && s.cfg.AllowInMemory)
|
||||
taskStoreReady := s.svc != nil && taskrun.TablesReady(context.Background(), s.svc.Pool())
|
||||
taskStoreReady := taskrun.TablesReady(context.Background(), s.pool)
|
||||
|
||||
status := "ok"
|
||||
code := http.StatusOK
|
||||
@@ -415,12 +415,12 @@ func (s *Server) handleGetUserContext(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
projectID, _ := strconv.ParseInt(projectIDStr, 10, 64)
|
||||
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
uCtx, err := s.svc.ContextManager().GetUserContext(r.Context(), projectID, userID, sessionID)
|
||||
uCtx, err := s.ctxMgr.GetUserContext(r.Context(), projectID, userID, sessionID)
|
||||
if err != nil {
|
||||
http.Error(w, "get user context failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -450,7 +450,7 @@ func (s *Server) handleUpdateUserPreference(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -463,7 +463,7 @@ func (s *Server) handleUpdateUserPreference(w http.ResponseWriter, r *http.Reque
|
||||
CustomInstructions: req.CustomInstructions,
|
||||
}
|
||||
|
||||
if err := s.svc.ContextManager().UpdateUserPreference(r.Context(), pref); err != nil {
|
||||
if err := s.ctxMgr.UpdateUserPreference(r.Context(), pref); err != nil {
|
||||
http.Error(w, "save user preference failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -490,12 +490,12 @@ func (s *Server) handleGetProjectContext(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
pCtx, err := s.svc.ContextManager().GetProjectContext(r.Context(), projectID)
|
||||
pCtx, err := s.ctxMgr.GetProjectContext(r.Context(), projectID)
|
||||
if err != nil {
|
||||
http.Error(w, "get project context failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -516,12 +516,12 @@ func (s *Server) handleDocGovGetDocument(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := s.svc.DocGov().GetDocument(r.Context(), docID)
|
||||
doc, err := s.gov.GetDocument(r.Context(), docID)
|
||||
if err != nil {
|
||||
http.Error(w, "get document failed: "+err.Error(), http.StatusNotFound)
|
||||
return
|
||||
@@ -555,12 +555,12 @@ func (s *Server) handleDocGovClaim(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := s.svc.DocGov().RecordDocumentViewed(r.Context(), req.DocID, user.ID, docgov.UserRole(req.UserRole), user.Name)
|
||||
doc, err := s.gov.RecordDocumentViewed(r.Context(), req.DocID, user.ID, docgov.UserRole(req.UserRole), user.Name)
|
||||
if err != nil {
|
||||
http.Error(w, "claim document failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -589,7 +589,7 @@ func (s *Server) handleDocGovSubmitReview(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -597,7 +597,7 @@ func (s *Server) handleDocGovSubmitReview(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := s.svc.DocGov().SubmitForReview(r.Context(), req.DocID, user.ID, docgov.RoleAuthor, user.Name)
|
||||
doc, err := s.gov.SubmitForReview(r.Context(), req.DocID, user.ID, docgov.RoleAuthor, user.Name)
|
||||
if err != nil {
|
||||
http.Error(w, "submit review failed: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -630,7 +630,7 @@ func (s *Server) handleDocGovReviewDecision(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -643,13 +643,13 @@ func (s *Server) handleDocGovReviewDecision(w http.ResponseWriter, r *http.Reque
|
||||
var err error
|
||||
|
||||
if action == "approve" {
|
||||
doc, err = s.svc.DocGov().ReviewApprove(r.Context(), req.DocID, user.ID, docgov.RoleReviewer, user.Name, req.Comment)
|
||||
doc, err = s.gov.ReviewApprove(r.Context(), req.DocID, user.ID, docgov.RoleReviewer, user.Name, req.Comment)
|
||||
} else if action == "reject" {
|
||||
reason := req.Reason
|
||||
if reason == "" {
|
||||
reason = req.Comment
|
||||
}
|
||||
doc, err = s.svc.DocGov().ReviewReject(r.Context(), req.DocID, user.ID, docgov.RoleReviewer, user.Name, reason)
|
||||
doc, err = s.gov.ReviewReject(r.Context(), req.DocID, user.ID, docgov.RoleReviewer, user.Name, reason)
|
||||
} else {
|
||||
http.Error(w, "invalid action: must be approve or reject", http.StatusBadRequest)
|
||||
return
|
||||
@@ -676,7 +676,7 @@ func (s *Server) handleDocGovApproverDecision(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -746,12 +746,12 @@ func (s *Server) handleDocGovDecideProposal(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
proposal, err := s.svc.DocGov().DecideModificationProposal(r.Context(), req.ProposalID, user.ID, req.Approve, req.Reason)
|
||||
proposal, err := s.gov.DecideModificationProposal(r.Context(), req.ProposalID, user.ID, req.Approve, req.Reason)
|
||||
if err != nil {
|
||||
http.Error(w, "decide proposal failed: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -768,12 +768,12 @@ func (s *Server) handleDocGovListComments(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
comments, err := s.svc.DocGov().ListComments(r.Context(), docID)
|
||||
comments, err := s.gov.ListComments(r.Context(), docID)
|
||||
if err != nil {
|
||||
http.Error(w, "list comments failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -801,7 +801,7 @@ func (s *Server) handleDocGovAddComment(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -811,7 +811,7 @@ func (s *Server) handleDocGovAddComment(w http.ResponseWriter, r *http.Request)
|
||||
cType = docgov.CommentGeneral
|
||||
}
|
||||
|
||||
comment, err := s.svc.DocGov().AddComment(
|
||||
comment, err := s.gov.AddComment(
|
||||
r.Context(), req.DocID, req.UserID, req.UserName,
|
||||
docgov.UserRole(req.UserRole), req.TargetSection, cType, req.Content,
|
||||
)
|
||||
@@ -831,12 +831,12 @@ func (s *Server) handleDocGovListAuditLogs(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := s.svc.DocGov().ListAuditLogs(r.Context(), docID)
|
||||
logs, err := s.gov.ListAuditLogs(r.Context(), docID)
|
||||
if err != nil {
|
||||
http.Error(w, "list audit logs failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -851,7 +851,7 @@ func (s *Server) handleDocGovListAuditLogs(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
// handleListChatSessions 分页查询用户/项目关联的历史会话列表
|
||||
func (s *Server) handleListChatSessions(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -872,7 +872,7 @@ func (s *Server) handleListChatSessions(w http.ResponseWriter, r *http.Request)
|
||||
offset = 0
|
||||
}
|
||||
|
||||
sessions, err := s.svc.ContextManager().ListChatSessions(r.Context(), ctxmgr.SessionFilter{
|
||||
sessions, err := s.ctxMgr.ListChatSessions(r.Context(), ctxmgr.SessionFilter{
|
||||
UserID: userID,
|
||||
ProjectID: projectID,
|
||||
Limit: limit,
|
||||
@@ -895,7 +895,7 @@ type upsertChatSessionRequest struct {
|
||||
|
||||
// handleUpsertChatSession 注册或更新会话元数据(立项/成员加入时同步)。
|
||||
func (s *Server) handleUpsertChatSession(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -910,7 +910,7 @@ func (s *Server) handleUpsertChatSession(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
summary, err := s.svc.ContextManager().EnsureChatSession(r.Context(), req.SessionID, req.UserID, req.ProjectID, req.Title)
|
||||
summary, err := s.ctxMgr.EnsureChatSession(r.Context(), req.SessionID, req.UserID, req.ProjectID, req.Title)
|
||||
if err != nil {
|
||||
http.Error(w, "upsert chat session failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -921,7 +921,7 @@ func (s *Server) handleUpsertChatSession(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// handleGetChatMessages 分页获取指定会话的历史多轮消息记录
|
||||
func (s *Server) handleGetChatMessages(w http.ResponseWriter, r *http.Request) {
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -972,7 +972,7 @@ func (s *Server) handleRecommendCases(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -998,7 +998,7 @@ func (s *Server) handleRecommendCases(w http.ResponseWriter, r *http.Request) {
|
||||
minScore = 0.50
|
||||
}
|
||||
|
||||
cases, err := s.svc.ContextManager().SearchSimilarCases(r.Context(), ctxmgr.CaseSearchOptions{
|
||||
cases, err := s.ctxMgr.SearchSimilarCases(r.Context(), ctxmgr.CaseSearchOptions{
|
||||
Query: query,
|
||||
TaskKey: taskKey,
|
||||
ExcludeDoc: excludeDoc,
|
||||
@@ -1043,12 +1043,12 @@ func (s *Server) handleDocGovSaveContent(w http.ResponseWriter, r *http.Request)
|
||||
http.Error(w, "invalid request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
role := docgov.RoleAuthor
|
||||
res, err := s.svc.DocGov().SaveDocumentContent(r.Context(), docgov.ContentSaveRequest{
|
||||
res, err := s.gov.SaveDocumentContent(r.Context(), docgov.ContentSaveRequest{
|
||||
DocID: req.DocID,
|
||||
ActorID: user.ID,
|
||||
ActorRole: role,
|
||||
@@ -1079,7 +1079,7 @@ func (s *Server) handleDocGovAcquireLease(w http.ResponseWriter, r *http.Request
|
||||
http.Error(w, "invalid request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -1088,7 +1088,7 @@ func (s *Server) handleDocGovAcquireLease(w http.ResponseWriter, r *http.Request
|
||||
workDir = ""
|
||||
}
|
||||
ttl := time.Duration(req.TTLSeconds) * time.Second
|
||||
doc, err := s.svc.DocGov().AcquireLease(r.Context(), docgov.LeaseAcquireRequest{
|
||||
doc, err := s.gov.AcquireLease(r.Context(), docgov.LeaseAcquireRequest{
|
||||
DocID: req.DocID,
|
||||
HolderType: docgov.LeaseHolderType(req.HolderType),
|
||||
HolderID: req.HolderID,
|
||||
@@ -1111,12 +1111,12 @@ func (s *Server) handleDocGovHeartbeatLease(w http.ResponseWriter, r *http.Reque
|
||||
http.Error(w, "invalid request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
ttl := time.Duration(req.TTLSeconds) * time.Second
|
||||
doc, err := s.svc.DocGov().HeartbeatLease(r.Context(), req.DocID, req.HolderType, req.HolderID, ttl)
|
||||
doc, err := s.gov.HeartbeatLease(r.Context(), req.DocID, req.HolderType, req.HolderID, ttl)
|
||||
if err != nil {
|
||||
writeDocGovError(w, err)
|
||||
return
|
||||
@@ -1133,11 +1133,11 @@ func (s *Server) handleDocGovReleaseLease(w http.ResponseWriter, r *http.Request
|
||||
http.Error(w, "invalid request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
doc, err := s.svc.DocGov().ReleaseLease(r.Context(), req.DocID, req.HolderType, req.HolderID)
|
||||
doc, err := s.gov.ReleaseLease(r.Context(), req.DocID, req.HolderType, req.HolderID)
|
||||
if err != nil {
|
||||
writeDocGovError(w, err)
|
||||
return
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestDocEditLease_HTTPConcurrencyGates(t *testing.T) {
|
||||
|
||||
docID := fmt.Sprintf("doc_lease_http_concurrency_%d_a", time.Now().UnixNano())
|
||||
ctx := t.Context()
|
||||
_, err := svc.DocGov().CreateOrGetDocument(ctx, docID, 91001, "intake", "并发互斥测试文档", "V0.1", "",
|
||||
_, err := bizdev.Snapshot(svc).Gov.CreateOrGetDocument(ctx, docID, 91001, "intake", "并发互斥测试文档", "V0.1", "",
|
||||
1, "作者", 2, "审核", 3, "批准")
|
||||
if err != nil {
|
||||
t.Fatalf("create doc: %v", err)
|
||||
@@ -113,7 +113,7 @@ func TestDocEditLease_HTTPConcurrencyGates(t *testing.T) {
|
||||
|
||||
// 6. 不同文档可并行持有租约
|
||||
doc2 := fmt.Sprintf("doc_lease_http_concurrency_%d_b", time.Now().UnixNano())
|
||||
_, _ = svc.DocGov().CreateOrGetDocument(ctx, doc2, 91001, "research", "并行文档B", "V0.5", "",
|
||||
_, _ = bizdev.Snapshot(svc).Gov.CreateOrGetDocument(ctx, doc2, 91001, "research", "并行文档B", "V0.5", "",
|
||||
1, "作者", 2, "审核", 3, "批准")
|
||||
acqA, _ := json.Marshal(map[string]any{
|
||||
"doc_id": docID, "holder_type": "squad", "holder_id": "run_a", "ttl_seconds": 60,
|
||||
|
||||
@@ -108,7 +108,7 @@ func setupPGProjectReadyToAdvance(t *testing.T, pool *pgxpool.Pool, handler http
|
||||
configRevision = activated.Instance.ConfigRevision
|
||||
|
||||
docID := fmt.Sprintf("doc_p5_fault_%d", projectID)
|
||||
docEngine := srv.BizdevService().DocGov()
|
||||
docEngine := bizdev.Snapshot(srv.BizdevService()).Gov
|
||||
_, _ = docEngine.CreateOrGetDocument(ctx, docID, projectID, "intake", "Intake", "V0.1", "", creatorID, "A", reviewerID, "R", approverID, "P")
|
||||
_ = docEngine.MarkDocumentContentUpdated(ctx, docID, "hash_p5_fault", false)
|
||||
_, _ = docEngine.SubmitForReview(ctx, docID, creatorID, "author", "Author")
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestPhase5_RollingRestartReplay_PG(t *testing.T) {
|
||||
|
||||
// 模拟三次进程重启:新 Manager 仅挂载 Store
|
||||
for i := 0; i < 3; i++ {
|
||||
restarted := taskrun.NewManager(srv.ProjectWorkflowService(), srv.BizdevService().ContextManager())
|
||||
restarted := taskrun.NewManager(srv.ProjectWorkflowService(), bizdev.Snapshot(srv.BizdevService()).Ctx)
|
||||
restarted.SetStore(store)
|
||||
rec, err := restarted.GetRun(runID)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,7 +19,7 @@ func (s *Server) docflowQuery() *docflow.Query {
|
||||
return nil
|
||||
}
|
||||
return &docflow.Query{Docs: &docquery.Service{
|
||||
Gov: s.svc.DocGov(),
|
||||
Gov: s.gov,
|
||||
Rev: s.docRev(),
|
||||
RepoRoot: s.repoRoot(),
|
||||
Authorize: func(ctx context.Context, p docquery.Principal, rec *docgov.DocumentRecord) error {
|
||||
@@ -78,12 +78,12 @@ func (s *Server) handleListProjectWorkflowDocuments(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s.gov == nil {
|
||||
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
docs, err := s.svc.DocGov().ListWorkflowDocumentsByProject(r.Context(), projectID)
|
||||
docs, err := s.gov.ListWorkflowDocumentsByProject(r.Context(), projectID)
|
||||
if err != nil {
|
||||
http.Error(w, "list project documents failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -134,10 +134,10 @@ func (s *Server) handleDocGovGetDocumentContent(w http.ResponseWriter, r *http.R
|
||||
}
|
||||
|
||||
func (s *Server) loadControlledDocumentContent(ctx context.Context, docID string) (*docgov.DocumentContent, error) {
|
||||
if s == nil || s.svc == nil || s.svc.DocGov() == nil {
|
||||
if s == nil || s.gov == nil {
|
||||
return nil, docgov.ErrDocumentNotFound
|
||||
}
|
||||
svc := &docquery.Service{Gov: s.svc.DocGov(), Rev: s.docRev(), RepoRoot: s.repoRoot()}
|
||||
svc := &docquery.Service{Gov: s.gov, Rev: s.docRev(), RepoRoot: s.repoRoot()}
|
||||
view, err := svc.Get(ctx, docquery.Principal{UserID: 1}, docquery.DocRef{DocID: docID})
|
||||
if err != nil && !errors.Is(err, docquery.ErrLedgerMissing) {
|
||||
return nil, err
|
||||
@@ -145,7 +145,7 @@ func (s *Server) loadControlledDocumentContent(ctx context.Context, docID string
|
||||
if view != nil && view.Markdown != "" {
|
||||
return view.ToDocumentContent(), nil
|
||||
}
|
||||
return s.svc.DocGov().ReadDocumentContent(ctx, docID, s.repoRoot())
|
||||
return s.gov.ReadDocumentContent(ctx, docID, s.repoRoot())
|
||||
}
|
||||
|
||||
// serveLedgerHeadOrConflict keeps the historical helper name. Projection is a
|
||||
|
||||
@@ -62,17 +62,17 @@ func TestHandleRunProjectWorkflow_DecodesBodyWithoutContentLength(t *testing.T)
|
||||
ctx := t.Context()
|
||||
|
||||
srDocID := "doc_proj1001_system_reqs"
|
||||
_, err := svc.DocGov().CreateOrGetDocument(
|
||||
_, err := bizdev.Snapshot(svc).Gov.CreateOrGetDocument(
|
||||
ctx, srDocID, 1001, "system_reqs", "系统需求规格书", "V0.5", "",
|
||||
2, "张工", 3, "李主管", 4, "王总监",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateOrGetDocument: %v", err)
|
||||
}
|
||||
_ = svc.DocGov().MarkDocumentContentUpdated(ctx, srDocID, "hash_v1", false)
|
||||
_, _ = svc.DocGov().SubmitForReview(ctx, srDocID, 2, "author", "张工")
|
||||
_, _ = svc.DocGov().ReviewApprove(ctx, srDocID, 3, "reviewer", "李主管", "同意")
|
||||
_, err = svc.DocGov().ApproverApprove(ctx, srDocID, 4, "approver", "王总监", "批准")
|
||||
_ = bizdev.Snapshot(svc).Gov.MarkDocumentContentUpdated(ctx, srDocID, "hash_v1", false)
|
||||
_, _ = bizdev.Snapshot(svc).Gov.SubmitForReview(ctx, srDocID, 2, "author", "张工")
|
||||
_, _ = bizdev.Snapshot(svc).Gov.ReviewApprove(ctx, srDocID, 3, "reviewer", "李主管", "同意")
|
||||
_, err = bizdev.Snapshot(svc).Gov.ApproverApprove(ctx, srDocID, 4, "approver", "王总监", "批准")
|
||||
if err != nil {
|
||||
t.Fatalf("ApproverApprove: %v", err)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (s *Server) healProjectDuties(ctx context.Context, projectID int64) error {
|
||||
if s == nil || s.svc == nil || projectID <= 0 {
|
||||
return nil
|
||||
}
|
||||
pool := s.svc.Pool()
|
||||
pool := s.pool
|
||||
if pool == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -203,16 +203,16 @@ func TestProjectWorkflow_TestManagerAndLineageStale(t *testing.T) {
|
||||
|
||||
// 3. 在受控治理中创建并批准系统需求规格书
|
||||
srDocID := fmt.Sprintf("doc_proj%d_system_reqs", projectID)
|
||||
_, err := srv.BizdevService().DocGov().CreateOrGetDocument(
|
||||
_, err := bizdev.Snapshot(srv.BizdevService()).Gov.CreateOrGetDocument(
|
||||
nil, srDocID, projectID, "system_reqs", "系统需求规格书",
|
||||
"V0.5_feasibility", "",
|
||||
2, "张工", 3, "李主管", 4, "王总监",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_ = srv.BizdevService().DocGov().MarkDocumentContentUpdated(nil, srDocID, "hash_v1", false)
|
||||
_, _ = srv.BizdevService().DocGov().SubmitForReview(nil, srDocID, 2, "author", "张工")
|
||||
_, _ = srv.BizdevService().DocGov().ReviewApprove(nil, srDocID, 3, "reviewer", "李主管", "同意")
|
||||
approvedDoc, err := srv.BizdevService().DocGov().ApproverApprove(nil, srDocID, 4, "approver", "王总监", "批准生效")
|
||||
_ = bizdev.Snapshot(srv.BizdevService()).Gov.MarkDocumentContentUpdated(nil, srDocID, "hash_v1", false)
|
||||
_, _ = bizdev.Snapshot(srv.BizdevService()).Gov.SubmitForReview(nil, srDocID, 2, "author", "张工")
|
||||
_, _ = bizdev.Snapshot(srv.BizdevService()).Gov.ReviewApprove(nil, srDocID, 3, "reviewer", "李主管", "同意")
|
||||
approvedDoc, err := bizdev.Snapshot(srv.BizdevService()).Gov.ApproverApprove(nil, srDocID, 4, "approver", "王总监", "批准生效")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "approved", string(approvedDoc.CurrentState))
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ func (s *Server) handleGetExportCapability(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -31,7 +31,7 @@ func (s *Server) handleGetExportCapability(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := s.svc.ContextManager().GetMessageByID(r.Context(), msgID)
|
||||
msg, err := s.ctxMgr.GetMessageByID(r.Context(), msgID)
|
||||
if err != nil || msg == nil {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]string{
|
||||
"error_code": "MESSAGE_NOT_FOUND",
|
||||
@@ -84,7 +84,7 @@ func (s *Server) handleGetExportSource(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if s.svc == nil || s.svc.ContextManager() == nil {
|
||||
if s.ctxMgr == nil {
|
||||
http.Error(w, "context manager not initialized", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func (s *Server) handleGetExportSource(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := s.svc.ContextManager().GetMessageByID(r.Context(), msgID)
|
||||
msg, err := s.ctxMgr.GetMessageByID(r.Context(), msgID)
|
||||
if err != nil || msg == nil {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]string{
|
||||
"error_code": "MESSAGE_NOT_FOUND",
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestServer_ReplyExportAPI(t *testing.T) {
|
||||
handler := srv.Routes()
|
||||
|
||||
ctx := t.Context()
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
sessID := "sess_export_api_test"
|
||||
token := loginTestAdmin(t, handler, cfg)
|
||||
if _, err := ctxMgr.EnsureChatSession(ctx, sessID, 1, 0, "导出测试会话"); err != nil {
|
||||
|
||||
@@ -20,13 +20,20 @@ import (
|
||||
"chat-agent-system/internal/config"
|
||||
"chat-agent-system/internal/coordinator"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/docrevision"
|
||||
"chat-agent-system/internal/freedoc"
|
||||
"chat-agent-system/internal/intent"
|
||||
"chat-agent-system/internal/kb"
|
||||
"chat-agent-system/internal/notification"
|
||||
"chat-agent-system/internal/projectinstance"
|
||||
"chat-agent-system/internal/projectworkflow"
|
||||
"chat-agent-system/internal/storage"
|
||||
"chat-agent-system/internal/taskrun"
|
||||
"chat-agent-system/internal/workflowdef"
|
||||
"chat-agent-system/internal/workspace"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// Server 提供基于 HTTP RESTful 与 SSE 的商务与研发参谋接口服务
|
||||
@@ -52,6 +59,13 @@ type Server struct {
|
||||
chatLog chatflow.ChatLog
|
||||
assembler chatflow.ContextAssembler
|
||||
stageflow *stageflow.UseCase
|
||||
gov *docgov.GovernanceEngine
|
||||
rev *docrevision.Service
|
||||
ctxMgr *ctxmgr.ContextManager
|
||||
cas *storage.CAS
|
||||
pool *pgxpool.Pool
|
||||
freeDoc *freedoc.Service
|
||||
kb *kb.Client
|
||||
httpServer *http.Server
|
||||
}
|
||||
|
||||
@@ -79,8 +93,15 @@ func NewFromRuntime(rt *app.Runtime) *Server {
|
||||
wsCap: rt.WSCap,
|
||||
cmds: rt.Commands,
|
||||
stageflow: rt.Stageflow,
|
||||
gov: rt.Gov,
|
||||
rev: rt.Rev,
|
||||
ctxMgr: rt.Ctx,
|
||||
cas: rt.CAS,
|
||||
pool: rt.Pool,
|
||||
freeDoc: rt.FreeDoc,
|
||||
kb: rt.KB,
|
||||
}
|
||||
s.chatLog = chatflow.NewAdapter(s.svc.ContextManager(), s.resolveStageAccess, s.failClosedAuthz())
|
||||
s.chatLog = chatflow.NewAdapter(s.ctxMgr, s.resolveStageAccess, s.failClosedAuthz())
|
||||
s.assembler = chatflow.NewAssembler(coordinator.NewContextAssembler(), s.chatLog)
|
||||
rt.ChatLog = s.chatLog
|
||||
rt.Assembler = s.assembler
|
||||
@@ -412,8 +433,8 @@ func (s *Server) getOrCreateSession(
|
||||
|
||||
// 尝试从持久层恢复会话上下文 (服务重启后跨进程保持 G3)
|
||||
var restored *ctxmgr.RestoredSessionContext
|
||||
if s.svc != nil && s.svc.ContextManager() != nil {
|
||||
if r, err := s.svc.ContextManager().RestoreSessionContext(context.Background(), sessionID); err == nil && r != nil && len(r.Messages) > 0 {
|
||||
if s.ctxMgr != nil {
|
||||
if r, err := s.ctxMgr.RestoreSessionContext(context.Background(), sessionID); err == nil && r != nil && len(r.Messages) > 0 {
|
||||
restored = r
|
||||
}
|
||||
}
|
||||
@@ -480,9 +501,9 @@ func (s *Server) getOrCreateSession(
|
||||
sess := chat.NewSession(sessionID, userCtx, s.rec, s.engine)
|
||||
sess.ChatLog = s.chatLog
|
||||
if s.svc != nil {
|
||||
sess.CtxMgr = s.svc.ContextManager()
|
||||
sess.CtxMgr = s.ctxMgr
|
||||
sess.Actions = &chat.ActionServices{
|
||||
DocGov: s.svc.DocGov(),
|
||||
DocGov: s.gov,
|
||||
Bizdev: s.svc,
|
||||
TaskRuns: s.taskRuns,
|
||||
}
|
||||
|
||||
@@ -93,14 +93,14 @@ func TestServer_ChatDocumentSummaryReadsExistingDocumentWithoutWriteAction(t *te
|
||||
if err := os.WriteFile(filepath.Join(workDir, "output.md"), markdown, 0o644); err != nil {
|
||||
t.Fatalf("write output.md: %v", err)
|
||||
}
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(
|
||||
if _, err := srv.gov.CreateOrGetDocument(
|
||||
ctx, "doc_proj143_intake", 143, "intake", "用户初始需求收集表", "V0.1_triage", workDir,
|
||||
1, "编制人", 1, "审核人", 1, "批准人",
|
||||
); err != nil {
|
||||
t.Fatalf("create document: %v", err)
|
||||
}
|
||||
hash := fmt.Sprintf("%x", sha256.Sum256(markdown))
|
||||
if err := srv.svc.DocGov().MarkDocumentContentUpdated(ctx, "doc_proj143_intake", hash, false); err != nil {
|
||||
if err := srv.gov.MarkDocumentContentUpdated(ctx, "doc_proj143_intake", hash, false); err != nil {
|
||||
t.Fatalf("mark content: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@ func (s *Server) enforceDocGovWorkflowAction(w http.ResponseWriter, ctx context.
|
||||
if docID == "" {
|
||||
return true
|
||||
}
|
||||
if s.svc != nil && s.svc.DocGov() != nil {
|
||||
doc, err := s.svc.DocGov().GetDocument(ctx, docID)
|
||||
if s.gov != nil {
|
||||
doc, err := s.gov.GetDocument(ctx, docID)
|
||||
if err == nil && doc != nil && doc.ProjectID > 0 && doc.TaskKey != "" {
|
||||
return s.enforceWorkflowDocTaskAction(w, ctx, doc.ProjectID, userID, doc.TaskKey, action)
|
||||
}
|
||||
|
||||
@@ -30,9 +30,7 @@ func (s *Server) finalizeTaskRun(ctx context.Context, record *taskrun.RunRecord,
|
||||
cmds := s.taskCommands()
|
||||
if s != nil {
|
||||
cmds.PW = s.pwSvc
|
||||
if s.svc != nil {
|
||||
cmds.Ctx = s.svc.ContextManager()
|
||||
}
|
||||
cmds.Ctx = s.ctxMgr
|
||||
}
|
||||
return cmds.Finalize(ctx, record, result)
|
||||
}
|
||||
@@ -49,8 +47,8 @@ func (s *Server) taskCommands() *taskrun.Commands {
|
||||
cmds.Env = s.cfg.Env
|
||||
cmds.StorageIdentity = "env:" + s.cfg.Env
|
||||
}
|
||||
if s != nil && s.svc != nil {
|
||||
cmds.Ctx = s.svc.ContextManager()
|
||||
if s != nil {
|
||||
cmds.Ctx = s.ctxMgr
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestFinalizeTaskRun_ReplacesPlaceholderContent(t *testing.T) {
|
||||
workDir := t.TempDir()
|
||||
_ = os.WriteFile(filepath.Join(workDir, "output.md"), []byte("# 最终交付\n\n正文内容。"), 0o644)
|
||||
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
asstID, err := ctxMgr.RecordUserMessageWithID(context.Background(), "sess-finalize", 0, "assistant", "任务执行中…", "TRIGGER_TASK", "", map[string]any{
|
||||
"task_status": "running",
|
||||
})
|
||||
@@ -106,7 +106,7 @@ func TestFinalizeTaskRun_PendingAuthorConfirmation_NotCompleted(t *testing.T) {
|
||||
workDir := t.TempDir()
|
||||
_ = os.WriteFile(filepath.Join(workDir, "output.md"), []byte("# base head\n"), 0o644)
|
||||
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
asstID, err := ctxMgr.RecordUserMessageWithID(context.Background(), "sess-pending", 0, "assistant", "任务执行中…", "TRIGGER_TASK", "", map[string]any{
|
||||
"task_status": "running",
|
||||
})
|
||||
@@ -180,12 +180,12 @@ func TestAsyncTaskRun_FinalizesAfterWorkerCompletes(t *testing.T) {
|
||||
cfg := &config.Config{Env: "test", DataDir: t.TempDir(), AgentBackend: "stub"}
|
||||
svc := bizdev.MustNewService(t, cfg)
|
||||
pwSvc := projectworkflow.NewService(projectworkflow.NewStore(nil), nil)
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
|
||||
workDir := t.TempDir()
|
||||
_ = os.WriteFile(filepath.Join(workDir, "output.md"), []byte("# async finalize\n"), 0o644)
|
||||
|
||||
srv := &Server{cfg: cfg, svc: svc, pwSvc: pwSvc, taskRuns: taskrun.NewManager(pwSvc, ctxMgr)}
|
||||
srv := &Server{cfg: cfg, svc: svc, pwSvc: pwSvc, ctxMgr: ctxMgr, taskRuns: taskrun.NewManager(pwSvc, ctxMgr)}
|
||||
srv.taskRuns.SetExecutor(
|
||||
func(_ context.Context, _ string, _ bizdev.TaskRunRequest, _ *stream.Streamer) (*bizdev.TaskRunResult, error) {
|
||||
return &bizdev.TaskRunResult{
|
||||
@@ -237,12 +237,12 @@ func TestAsyncTaskRun_PendingConfirmation_FinalizesAsPending(t *testing.T) {
|
||||
cfg := &config.Config{Env: "test", DataDir: t.TempDir(), AgentBackend: "stub"}
|
||||
svc := bizdev.MustNewService(t, cfg)
|
||||
pwSvc := projectworkflow.NewService(projectworkflow.NewStore(nil), nil)
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
|
||||
workDir := t.TempDir()
|
||||
_ = os.WriteFile(filepath.Join(workDir, "output.md"), []byte("# head\n"), 0o644)
|
||||
|
||||
srv := &Server{cfg: cfg, svc: svc, pwSvc: pwSvc, taskRuns: taskrun.NewManager(pwSvc, ctxMgr)}
|
||||
srv := &Server{cfg: cfg, svc: svc, pwSvc: pwSvc, ctxMgr: ctxMgr, taskRuns: taskrun.NewManager(pwSvc, ctxMgr)}
|
||||
srv.taskRuns.SetExecutor(
|
||||
func(_ context.Context, _ string, _ bizdev.TaskRunRequest, _ *stream.Streamer) (*bizdev.TaskRunResult, error) {
|
||||
return &bizdev.TaskRunResult{
|
||||
@@ -315,7 +315,7 @@ func TestFinalizeTaskRun_HashesViaWorkspaceCapability(t *testing.T) {
|
||||
if err := srv.wsCap.WriteArtifact(ctx, ref, workspace.DeliverableKey, body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctxMgr := svc.ContextManager()
|
||||
ctxMgr := bizdev.Snapshot(svc).Ctx
|
||||
asstID, err := ctxMgr.RecordUserMessageWithID(ctx, "sess-hash", 0, "assistant", "任务执行中…", "TRIGGER_TASK", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -17,19 +17,19 @@ func TestCreateTaskRunBusyPreflightDoesNotPersistRunOrMessages(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
ctx := t.Context()
|
||||
docID := "doc_proj143_intake"
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(
|
||||
if _, err := srv.gov.CreateOrGetDocument(
|
||||
ctx, docID, 143, "intake", "用户初始需求收集表", "V0.1_triage", "",
|
||||
1, "编制人", 1, "审核人", 1, "批准人",
|
||||
); err != nil {
|
||||
t.Fatalf("create document: %v", err)
|
||||
}
|
||||
if _, err := srv.svc.DocGov().AcquireLease(ctx, docgov.LeaseAcquireRequest{
|
||||
if _, err := srv.gov.AcquireLease(ctx, docgov.LeaseAcquireRequest{
|
||||
DocID: docID, HolderType: docgov.LeaseHolderHuman, HolderID: "editor-9", TTL: time.Minute,
|
||||
}); err != nil {
|
||||
t.Fatalf("acquire fixture lease: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_, _ = srv.svc.DocGov().ReleaseLease(t.Context(), docID, string(docgov.LeaseHolderHuman), "editor-9")
|
||||
_, _ = srv.gov.ReleaseLease(t.Context(), docID, string(docgov.LeaseHolderHuman), "editor-9")
|
||||
}()
|
||||
|
||||
requestBody, _ := json.Marshal(createTaskRunRequest{
|
||||
@@ -53,7 +53,7 @@ func TestCreateTaskRunBusyPreflightDoesNotPersistRunOrMessages(t *testing.T) {
|
||||
if _, exists := payload["run_id"]; exists {
|
||||
t.Fatalf("busy preflight must not return run_id: %+v", payload)
|
||||
}
|
||||
history, err := srv.svc.ContextManager().GetChatHistory(ctx, "fixture-busy-preflight", 20, 0)
|
||||
history, err := srv.ctxMgr.GetChatHistory(ctx, "fixture-busy-preflight", 20, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("get busy history: %v", err)
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func TestCreateTaskRun_PreActivationAcceptedNot409(t *testing.T) {
|
||||
if rec, err := srv.taskRuns.GetRun(payload.RunID); err == nil && rec != nil && rec.Status == "running" {
|
||||
t.Fatalf("pre-activation run must not start a live worker: %+v", rec)
|
||||
}
|
||||
history, err := srv.svc.ContextManager().GetChatHistory(t.Context(), "sess-p9001-u1", 20, 0)
|
||||
history, err := srv.ctxMgr.GetChatHistory(t.Context(), "sess-p9001-u1", 20, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("chat history: %v", err)
|
||||
}
|
||||
@@ -133,7 +133,7 @@ func TestDocGovActions_PreActivationRejected(t *testing.T) {
|
||||
srv.instSvc = projectinstance.NewService(store, projectinstance.TestResolver(), nil, nil)
|
||||
|
||||
docID := "doc_proj9002_intake"
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(
|
||||
if _, err := srv.gov.CreateOrGetDocument(
|
||||
t.Context(), docID, 9002, "intake", "立项文档", "V0.1_triage", "",
|
||||
1, "编制人", 2, "审核人", 3, "批准人",
|
||||
); err != nil {
|
||||
|
||||
@@ -104,7 +104,7 @@ func (s *Server) handleCreateTaskRun(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
ctxMgr := s.svc.ContextManager()
|
||||
ctxMgr := s.ctxMgr
|
||||
if ctxMgr != nil {
|
||||
_, _ = ctxMgr.EnsureChatSession(r.Context(), req.SessionID, req.UserID, req.ProjectID, req.ProjectName)
|
||||
}
|
||||
@@ -248,7 +248,7 @@ func (s *Server) createBlockedGovernanceRun(w http.ResponseWriter, r *http.Reque
|
||||
})
|
||||
return
|
||||
}
|
||||
ctxMgr := s.svc.ContextManager()
|
||||
ctxMgr := s.ctxMgr
|
||||
if ctxMgr != nil && req.UserMessage != "" {
|
||||
userMeta := map[string]any{
|
||||
"project_id": req.ProjectID,
|
||||
@@ -338,7 +338,7 @@ func (s *Server) recordBusyPreflightMessages(
|
||||
// preflightTaskDocumentBusy 在写入聊天占位与 task_runs 之前拦截已知租约占用。
|
||||
// 真正的租约获取仍由 docgov 原子执行;本检查用于避免用户重复点击产生必然失败的 run。
|
||||
func (s *Server) preflightTaskDocumentBusy(ctx context.Context, req createTaskRunRequest) (*docgov.DocumentBusyError, error) {
|
||||
if req.Stage != "" || req.ProjectID <= 0 || req.TaskKey == "" || s.svc == nil || s.svc.DocGov() == nil {
|
||||
if req.Stage != "" || req.ProjectID <= 0 || req.TaskKey == "" || s.gov == nil {
|
||||
return nil, nil
|
||||
}
|
||||
taskReq := s.buildBizdevTaskRequest(req.TaskRunRequestDTO, req.SessionID, req.ClientTurnID, "")
|
||||
@@ -349,7 +349,7 @@ func (s *Server) preflightTaskDocumentBusy(ctx context.Context, req createTaskRu
|
||||
if docID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
doc, err := s.svc.DocGov().GetDocument(ctx, docID)
|
||||
doc, err := s.gov.GetDocument(ctx, docID)
|
||||
if errors.Is(err, docgov.ErrDocumentNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestCreateTaskRun_SameTurnIdempotentReturnsExistingDespiteBusyLease(t *test
|
||||
)
|
||||
|
||||
docID := "doc_proj143_intake"
|
||||
if _, err := srv.svc.DocGov().CreateOrGetDocument(
|
||||
if _, err := srv.gov.CreateOrGetDocument(
|
||||
ctx, docID, 143, "intake", "用户初始需求收集表", "V0.1_triage", "",
|
||||
userID, "编制人", userID, "审核人", userID, "批准人",
|
||||
); err != nil {
|
||||
@@ -71,7 +71,7 @@ func TestCreateTaskRun_SameTurnIdempotentReturnsExistingDespiteBusyLease(t *test
|
||||
t.Fatal("missing run_id")
|
||||
}
|
||||
|
||||
hist1, err := srv.svc.ContextManager().GetChatHistory(ctx, "sess-idem-busy", 50, 0)
|
||||
hist1, err := srv.ctxMgr.GetChatHistory(ctx, "sess-idem-busy", 50, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("history1: %v", err)
|
||||
}
|
||||
@@ -80,13 +80,13 @@ func TestCreateTaskRun_SameTurnIdempotentReturnsExistingDespiteBusyLease(t *test
|
||||
}
|
||||
|
||||
// 活跃小队租约:模拟长任务仍占文档(旧路径会 409)
|
||||
if _, err := srv.svc.DocGov().AcquireLease(ctx, docgov.LeaseAcquireRequest{
|
||||
if _, err := srv.gov.AcquireLease(ctx, docgov.LeaseAcquireRequest{
|
||||
DocID: docID, HolderType: docgov.LeaseHolderSquad, HolderID: first.RunID, TTL: time.Minute,
|
||||
}); err != nil {
|
||||
t.Fatalf("acquire squad lease: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_, _ = srv.svc.DocGov().ReleaseLease(context.Background(), docID, string(docgov.LeaseHolderSquad), first.RunID)
|
||||
_, _ = srv.gov.ReleaseLease(context.Background(), docID, string(docgov.LeaseHolderSquad), first.RunID)
|
||||
})
|
||||
|
||||
body2, _ := json.Marshal(createTaskRunRequest{
|
||||
@@ -108,7 +108,7 @@ func TestCreateTaskRun_SameTurnIdempotentReturnsExistingDespiteBusyLease(t *test
|
||||
t.Fatalf("want same run %s, got %s", first.RunID, second.RunID)
|
||||
}
|
||||
|
||||
hist2, err := srv.svc.ContextManager().GetChatHistory(ctx, "sess-idem-busy", 50, 0)
|
||||
hist2, err := srv.ctxMgr.GetChatHistory(ctx, "sess-idem-busy", 50, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("history2: %v", err)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func TestTaskRun_PersistenceAndReplayAfterRestart_PG(t *testing.T) {
|
||||
}
|
||||
|
||||
// 模拟进程重启:新 Manager 仅挂载 Store
|
||||
restarted := taskrun.NewManager(srv.ProjectWorkflowService(), srv.BizdevService().ContextManager())
|
||||
restarted := taskrun.NewManager(srv.ProjectWorkflowService(), bizdev.Snapshot(srv.BizdevService()).Ctx)
|
||||
restarted.SetStore(store)
|
||||
|
||||
reloaded, err := restarted.GetRun(runID)
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
)
|
||||
|
||||
func (s *Server) registerWorkflowApprovalHook() {
|
||||
if s == nil || s.svc == nil || s.svc.DocGov() == nil || s.instSvc == nil || !s.instSvc.Available() {
|
||||
if s == nil || s.gov == nil || s.instSvc == nil || !s.instSvc.Available() {
|
||||
return
|
||||
}
|
||||
s.svc.DocGov().AddOnApprovedHook(s.onWorkflowDocumentApproved)
|
||||
s.gov.AddOnApprovedHook(s.onWorkflowDocumentApproved)
|
||||
}
|
||||
|
||||
func (s *Server) onWorkflowDocumentApproved(ctx context.Context, doc *docgov.DocumentRecord) {
|
||||
|
||||
@@ -84,7 +84,7 @@ func setupPGGovernanceServer(t *testing.T, pool *pgxpool.Pool) (http.Handler, *s
|
||||
t.Helper()
|
||||
cfg := configFromTestDatabaseURL(t)
|
||||
svc := bizdev.MustNewService(t, cfg)
|
||||
if svc.Pool() == nil {
|
||||
if bizdev.Snapshot(svc).Pool == nil {
|
||||
t.Skip("bizdev service not connected to postgres")
|
||||
}
|
||||
srv := server.NewServer(cfg, svc)
|
||||
@@ -194,7 +194,7 @@ func TestWorkflowGovernance_PG_ActivateApproveAdvanceChain(t *testing.T) {
|
||||
|
||||
// 4. 文档批准链(经 bizdev DocGov 引擎,触发 onWorkflowDocumentApproved hook)
|
||||
docID := fmt.Sprintf("doc_pg_p5_intake_%d", projectID)
|
||||
docEngine := srv.BizdevService().DocGov()
|
||||
docEngine := bizdev.Snapshot(srv.BizdevService()).Gov
|
||||
_, err = docEngine.CreateOrGetDocument(ctx, docID, projectID, "intake", "Intake Doc", "V0.1", "", creatorID, "Author", reviewerID, "Reviewer", approverID, "Approver")
|
||||
if err != nil {
|
||||
t.Fatalf("create doc: %v", err)
|
||||
|
||||
@@ -45,7 +45,7 @@ func setupPGPiAgentServer(t *testing.T, pool *pgxpool.Pool) (http.Handler, *serv
|
||||
}
|
||||
mergePiAgentE2EKbConfig(cfg)
|
||||
svc := bizdev.MustNewService(t, cfg)
|
||||
if svc.Pool() == nil {
|
||||
if bizdev.Snapshot(svc).Pool == nil {
|
||||
t.Skip("bizdev service not connected to postgres")
|
||||
}
|
||||
srv := server.NewServer(cfg, svc)
|
||||
@@ -207,7 +207,7 @@ func runPiAgentGovernanceE2E(t *testing.T, spec piAgentGovernanceCase) {
|
||||
}
|
||||
t.Logf("✓ [3. pi-agent %s] doc=%s title=%s elapsed=%s", spec.TaskKey, docID, taskRes.ArtifactTitle, time.Since(taskStart).Round(time.Second))
|
||||
|
||||
docEngine := srv.BizdevService().DocGov()
|
||||
docEngine := bizdev.Snapshot(srv.BizdevService()).Gov
|
||||
_ = docEngine.MarkDocumentContentUpdated(ctx, docID, "hash_pi_pg", false)
|
||||
_, _ = docEngine.SubmitForReview(ctx, docID, creatorID, "author", "Author")
|
||||
_, _ = docEngine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "Reviewer", "ok")
|
||||
|
||||
@@ -44,7 +44,7 @@ func main() {
|
||||
srv := server.NewServer(cfg, svc)
|
||||
ctx := context.Background()
|
||||
|
||||
pwSvc := svc.ProjectWorkflow()
|
||||
pwSvc := bizdev.Snapshot(svc).PW
|
||||
if pwSvc == nil {
|
||||
log.Fatal("project workflow service unavailable")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user