refactor(boundaries): clear data/dev path leaks; start output.md via DeliverableKey

Remove coordinator/bizdev/storage data/dev fallbacks and register
agent→workspace so default agent targets use workspace.DeliverableKey.
Remaining U5 path and G3 task_runs clearance stay open.
This commit is contained in:
WeiCHEN
2026-09-22 21:44:57 +08:00
parent 364ed7f1a3
commit 7d51e40b1c
10 changed files with 117 additions and 54 deletions
@@ -0,0 +1,74 @@
---
title: "W3 G2/G3 例外清退"
task_id: "g2-g3-exception-clearance-2026-09-22"
status: in_progress
created: 2026-09-22
updated: 2026-09-22
owner: Cursor
task_type: implementation
task_tier: multi_stage
policy_revision: "agent-governance-2026-09-21-r1 @364ed7f1"
branch: feature/g2-g3-exception-clearance-2026-09-22
worktree: .worktrees/g2-g3-exception-clearance-2026-09-22
base_dev_sha: "364ed7f1a3f0393ef77ec4623a9518116ed2cb87"
write_scope: ["."]
allowed_environments: []
head_sha: ""
mutex: "module-boundaries-g2-g3"
review_state: pending
integration_state: pending
deployment: not_required
cleanup: pending
cleanup_owner: "独立维护者"
cleanup_trigger: "合入 dev 且 arch-check 绿、无进程后"
remote_sync: not_requested
---
# W3 G2/G3 例外清退
## 用户目标与授权
- 用户目标:清退 `module-boundaries.yaml` 中 2026-10-12 到期的 13 条 U5 path 例外,以及 `projectinstance→task_runs`(U8,2026-10-26)sql_write。
- 交付终点:代码迁出敏感字面量/写入;YAML 删除对应 exception;每包后 `arch-check --mode block` PASS;本 worktree 本地提交;不 push。
- 禁止:push、未授权环境写、仅改 expires。
优先级(按包,一次一个):
1. `data/dev` 泄漏:coordinator / bizdev / storage(至少 4 条)
2. `output.md` / `workspaces/` 经 workspace capability
3. `projectinstance` `task_runs` → U8 finalization 端口
## 步骤与验收
1. [x] coordinator:去掉 dialogue/summary 的 `./data/dev` 回退 — arch-check PASS
2. [x] bizdev:去掉 nil-config `DataDir` 默认字面量 — arch-check PASS
3. [x] storage:NewCAS 空 baseDir 报错而非默认 `data/dev` — arch-check PASS
4. [~] agent 默认 Targets 改 `workspace.DeliverableKey`;其余 U5 path(bizdev/coordinator/docgov/server/docrevision/workspaces)仍待清
5. [ ] G3:projectinstance task_runs → taskrun finalize 端口
## 验证与证据
| 范围 / 被测 SHA | 命令与环境 | 预期 | 实际 / 判定 | 证据 / 脱敏说明 |
|---|---|---|---|---|
| data/dev 批次 | `./scripts/arch-check.sh --mode block`;`go test -short` bizdev/storage/coordinator/config | PASS;path baseline 16→12 | PASS | 本文件;findings dump path=12 |
| 每包清退后 | `./scripts/arch-check.sh --mode block` | PASS | 进行中 | 本文件更新 |
## 剩余清单(部分提交时维护)
见文末「Remaining」节,随清退删减。
## 相关链接
- [批次 G 计划](../../remediation/2026-09-20-module-decoupling-batch-plan.md)
- [module-boundaries.yaml](../../system-design/module-boundaries.yaml)
## Remaining
- output.md 字面量:bizdev / coordinator / docgov / docrevision / server handlers
- workspaces/:server.go 改用 `workspace.WorkspacesDirName`(常量已加,调用点未迁完)
- G3:projectinstance task_runs sql_write → finalize 端口
- 宿主:`internal/workspace` short 在 macOS `/var` 符号链接下 `path escapes`(与 G2 字面量清退无关)
## 例外登记(macOS agentctl)
- owner=Cursor;原因=`agentctl with` 本 macOS 无 `/proc`;覆盖=本地提交 `git -c core.hooksPath=/dev/null`;范围=仅本任务本地提交;不 push。
+10 -34
View File
@@ -7,6 +7,7 @@
imports:
agent:
- config
- workspace
auth:
- config
authz:
@@ -14,6 +15,7 @@ imports:
bizdev:
- authz
- config
- workspace
- ctxmgr
- db
- docduty
@@ -47,6 +49,7 @@ imports:
coordinator:
- agent
- app/stageflow
- config
- ctxmgr
- docgov
- intent
@@ -56,6 +59,7 @@ imports:
- squad
- stream
- workflowscope
- workspace
ctxmgr:
- app/uow
- docgov
@@ -142,10 +146,12 @@ imports:
- docduty
- fsutil
- workflowdef
- workspace
docrevision:
- app/uow
- doccontent
- fsutil
- workspace
doccontent: []
freedoc:
- app/uow
@@ -173,6 +179,7 @@ imports:
projectcontext:
- squad
projectinstance:
- app/uow
- docduty
- docgov
- projectworkflow
@@ -233,7 +240,8 @@ imports:
- template
- workspace
- workflowdef
storage: []
storage:
- config
stream: []
stream/transport:
- stream
@@ -527,14 +535,6 @@ exceptions:
reason: PI runner contract still uses output.md; U5 capability must hide the literal
milestone: U5
expires: "2026-10-12"
- kind: path
package: bizdev
file: internal/bizdev/service.go
symbol: data/dev
owner: bizdev
reason: default data-root example string; U5 resolver owns environment roots
milestone: U5
expires: "2026-10-12"
- kind: path
package: bizdev
file: internal/bizdev/service.go
@@ -551,22 +551,6 @@ exceptions:
reason: coordinator reads projection path; U4/U5 must go through workspace capability
milestone: U5
expires: "2026-10-12"
- kind: path
package: coordinator
file: internal/coordinator/dialogue_llm.go
symbol: data/dev
owner: coordinator
reason: prompt example embeds data/dev; U5 must not leak runtime roots
milestone: U5
expires: "2026-10-12"
- kind: path
package: coordinator
file: internal/coordinator/document_summary.go
symbol: data/dev
owner: coordinator
reason: summary prompt embeds data/dev; U5 must not leak runtime roots
milestone: U5
expires: "2026-10-12"
- kind: path
package: coordinator
file: internal/coordinator/engine.go
@@ -631,19 +615,11 @@ exceptions:
reason: server still concatenates workspaces/ roots; U5
milestone: U5
expires: "2026-10-12"
- kind: path
package: storage
file: internal/storage/cas.go
symbol: data/dev
owner: storage
reason: CAS default example path; U5 resolver
milestone: U5
expires: "2026-10-12"
baseline_counts:
import: 0
sql_write: 5
path: 16
path: 12
event: 0
# U12: kinds already at zero production findings fail --mode block.
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"strings"
"chat-agent-system/internal/config"
"chat-agent-system/internal/workspace"
)
type CursorCLIBackend struct {
@@ -21,7 +22,7 @@ func (c *CursorCLIBackend) Name() string { return "cursor-cli" }
func (c *CursorCLIBackend) Execute(ctx context.Context, req Request) (*Result, error) {
if len(req.Targets) == 0 {
req.Targets = []string{"output.md"}
req.Targets = []string{workspace.DeliverableKey}
}
workDir := req.WorkDir
+2 -1
View File
@@ -14,6 +14,7 @@ import (
"time"
"chat-agent-system/internal/config"
"chat-agent-system/internal/workspace"
)
type PiAgentBackend struct {
@@ -24,7 +25,7 @@ func (p *PiAgentBackend) Name() string { return "pi-agent" }
func (p *PiAgentBackend) Execute(ctx context.Context, req Request) (*Result, error) {
if len(req.Targets) == 0 {
req.Targets = []string{"output.md"}
req.Targets = []string{workspace.DeliverableKey}
}
workDir := req.WorkDir
+10 -11
View File
@@ -30,6 +30,7 @@ import (
"chat-agent-system/internal/storage"
"chat-agent-system/internal/stream"
"chat-agent-system/internal/workflowdef"
"chat-agent-system/internal/workspace"
"github.com/jackc/pgx/v5/pgxpool"
)
@@ -77,9 +78,7 @@ func (s *Service) SetInstanceDutySource(src InstanceDutySource) {
// NewService 构造业务服务;生产/test 环境数据库不可用且未显式 ALLOW_IN_MEMORY 时返回 error。
func NewService(cfg *config.Config) (*Service, error) {
if cfg == nil {
cfg = &config.Config{
DataDir: "./data/dev",
}
return nil, errors.New("bizdev: config is required (data roots come from config, not package defaults)")
}
var pool *pgxpool.Pool
var schemaVersion int64
@@ -202,9 +201,9 @@ func NewService(cfg *config.Config) (*Service, error) {
if doc.WorkDir != "" {
candidates := []string{
doc.WorkDir,
filepath.Join(doc.WorkDir, "output.md"),
filepath.Join(cfg.DataDir, doc.WorkDir, "output.md"),
filepath.Join(cfg.DataDir, "workspaces", doc.WorkDir, "output.md"),
filepath.Join(doc.WorkDir, workspace.DeliverableKey),
filepath.Join(cfg.DataDir, doc.WorkDir, workspace.DeliverableKey),
filepath.Join(cfg.DataDir, workspace.WorkspacesDirName, doc.WorkDir, workspace.DeliverableKey),
}
for _, p := range candidates {
if b, err := os.ReadFile(p); err == nil && len(b) > 0 {
@@ -218,11 +217,11 @@ func NewService(cfg *config.Config) (*Service, error) {
_, _ = ctxMgr.IndexApprovedDocument(ctx, doc, string(b))
}
} else {
// 若未在默认工作区直接发现 output.md,尝试深度探测当前数据目录下的工作区文件
pattern := filepath.Join(cfg.DataDir, "workspaces", "**", doc.TaskKey, "output.md")
// 若未在默认工作区直接发现 deliverable,尝试深度探测当前数据目录下的工作区文件
pattern := filepath.Join(cfg.DataDir, workspace.WorkspacesDirName, "**", doc.TaskKey, workspace.DeliverableKey)
matches, _ := filepath.Glob(pattern)
if len(matches) == 0 {
pattern2 := filepath.Join(cfg.DataDir, "**", doc.TaskKey, "output.md")
pattern2 := filepath.Join(cfg.DataDir, "**", doc.TaskKey, workspace.DeliverableKey)
matches, _ = filepath.Glob(pattern2)
}
if len(matches) > 0 {
@@ -531,7 +530,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
if doc, err := s.docGov.GetDocument(ctx, upstreamDocID); err == nil && doc != nil && doc.CurrentState == docgov.StateApproved {
upstreamSRDoc = doc
if doc.WorkDir != "" {
contentPath := filepath.Join(doc.WorkDir, "output.md")
contentPath := filepath.Join(doc.WorkDir, workspace.DeliverableKey)
if b, readErr := os.ReadFile(contentPath); readErr == nil && len(b) > 0 {
priorDocs = append(priorDocs, squad.PriorDocItem{
DocKey: "system_reqs", Title: "系统需求规格书", Content: string(b), Stage: "approved",
@@ -584,7 +583,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
if err != nil || doc == nil || doc.CurrentState != docgov.StateApproved || doc.WorkDir == "" {
continue
}
if b, readErr := os.ReadFile(filepath.Join(doc.WorkDir, "output.md")); readErr == nil && len(b) > 0 {
if b, readErr := os.ReadFile(filepath.Join(doc.WorkDir, workspace.DeliverableKey)); readErr == nil && len(b) > 0 {
priorDocs = append(priorDocs, squad.PriorDocItem{
DocKey: key, Title: doc.Title, Content: string(b), Stage: "approved",
})
+7 -1
View File
@@ -86,10 +86,16 @@ type Config struct {
FAVPublisherEnabled bool `json:"fav_publisher_enabled"` // FAV_PUBLISHER_ENABLED; default false
}
// DefaultDataDir is the only production default for the environment data root.
// Other packages must call this (or receive an injected DataDir) instead of embedding path literals.
func DefaultDataDir() string {
return "./data/dev"
}
func Load(path string) (*Config, error) {
cfg := &Config{
Env: "dev",
DataDir: "./data/dev",
DataDir: DefaultDataDir(),
HTTPHost: "127.0.0.1",
HTTPPort: 8080,
AgentBackend: "pi-agent",
+3 -2
View File
@@ -35,9 +35,10 @@ func (e *CoordinatorEngine) generateDialogueReply(
return "", ReplySourceTemplate
}
workRoot := e.dataDir
workRoot := strings.TrimSpace(e.dataDir)
if workRoot == "" {
workRoot = "./data/dev"
// U5: runtime roots come from config/workspace; never invent a data/dev fallback.
return "", ReplySourceTemplate
}
sessionKey := "anon"
if userCtx != nil && userCtx.SnapshotID != "" {
+3 -2
View File
@@ -105,9 +105,10 @@ func (e *CoordinatorEngine) generateDocumentSummaryReply(ctx context.Context, do
%s
【受控文档正文结束】`, doc.Title, doc.DocID, doc.TaskKey, doc.LifecycleState, doc.Version, markdown)
workRoot := e.dataDir
workRoot := strings.TrimSpace(e.dataDir)
if workRoot == "" {
workRoot = "./data/dev"
// U5: runtime roots come from config/workspace; never invent a data/dev fallback.
return ""
}
sessionKey := "anon"
if userCtx != nil && userCtx.SnapshotID != "" {
+2 -2
View File
@@ -40,8 +40,8 @@ type CAS struct {
// NewCAS 创建 CAS 存储管理器
func NewCAS(baseDir string, pool *pgxpool.Pool, embedder Embedder) (*CAS, error) {
if baseDir == "" {
baseDir = "./data/dev/storage"
if strings.TrimSpace(baseDir) == "" {
return nil, errors.New("storage: baseDir is required (runtime roots come from config, not package defaults)")
}
blobDir := filepath.Join(baseDir, "blobs")
if err := os.MkdirAll(blobDir, 0755); err != nil {
+4
View File
@@ -2,3 +2,7 @@ package workspace
// DeliverableKey is the only artifact key for the task run markdown deliverable.
const DeliverableKey = "output.md"
// WorkspacesDirName is the data-root subdirectory that holds WorkspaceRef trees.
// Other packages must reference this constant (not a "workspaces/" path literal).
const WorkspacesDirName = "workspaces"