merge: feature/g2-g3-exception-clearance-2026-09-22

Keep G1 cleared sql_write; land G2/G3 path clearances; baseline to zero.
This commit is contained in:
WeiCHEN
2026-09-22 22:08:53 +08:00
18 changed files with 147 additions and 159 deletions
@@ -0,0 +1,64 @@
---
title: "W3 G2/G3 例外清退"
task_id: "g2-g3-exception-clearance-2026-09-22"
status: done
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: "db632aeea28e99db58ecdf949345e2152e4782d1"
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 到期的 U5 path 例外,以及 `projectinstance→task_runs`(U8)sql_write。
- 交付终点:代码迁出敏感字面量/写入;YAML 删除对应 exception;`arch-check --mode block` PASS;本 worktree 本地提交;不 push。
- 禁止:push、未授权环境写、仅改 expires。
## 步骤与验收
1. [x] coordinator / bizdev / storage:去掉 `data/dev` 回退字面量
2. [x] agent Targets / bizdev / coordinator / docgov / docrevision(reconcile) / server route:`workspace.DeliverableKey` / `WorkspacesDirName`
3. [x] G3:`projectinstance` terminate 经 `taskrun.CancelOpenByProject`;allowlist `projectinstance→taskrun`
4. [x] 删除全部 U5 path + U8 sql_write 例外;baseline `path 12→3`、`sql_write 5→4`
## 验证与证据
| 范围 | 命令 | 实际 |
|---|---|---|
| arch-check block | `./scripts/arch-check.sh --mode block` | PASS(path=3 U4 残留;sql_write=4 U3) |
| 相关包 short | `go test -short` taskrun/projectinstance/coordinator/docrevision/archtest | PASS |
| server short 全量 | `go test -short ./internal/server/` | FAIL `TestFinalizeTaskRun_HashesViaWorkspaceCapability`(macOS `/var` 符号链接宿主债;与本批无关) |
| docgov DB 集成 | `TestDocGov_CreateOrGetDocument_OptionalDutiesNullFK` | FAIL FK project_id(环境/种子债;本批未改 CreateOrGet) |
## Remaining(非本批 blocker)
- U4 path×3:`docrevision/projection.go`、`server/document_revision_access.go`、`document_revision_handlers.go`(到期 2026-09-28)
- U3 sql_write×4:docduty / docrevision document_records(G1 残留)
## 收口
- `integration_state=pending`;不 push。
- 本地 tip 见 `head_sha`(提交后回填)。
## 相关链接
- [批次 G 计划](../../remediation/2026-09-20-module-decoupling-batch-plan.md)
- [module-boundaries.yaml](../../system-design/module-boundaries.yaml)
+12 -122
View File
@@ -7,6 +7,7 @@
imports:
agent:
- config
- workspace
auth:
- config
authz:
@@ -49,6 +50,7 @@ imports:
coordinator:
- agent
- app/stageflow
- config
- contracts/vocabulary
- ctxmgr
- intent
@@ -58,6 +60,7 @@ imports:
- squad
- stream
- workflowscope
- workspace
ctxmgr:
- app/uow
- docgov
@@ -149,10 +152,12 @@ imports:
- docduty
- fsutil
- workflowdef
- workspace
docrevision:
- app/uow
- doccontent
- fsutil
- workspace
doccontent: []
freedoc:
- app/uow
@@ -180,9 +185,11 @@ imports:
projectcontext:
- squad
projectinstance:
- app/uow
- docduty
- docgov
- projectworkflow
- taskrun
- workflowdef
projectworkflow:
- auth
@@ -241,7 +248,8 @@ imports:
- template
- workspace
- workflowdef
storage: []
storage:
- config
stream: []
stream/transport:
- stream
@@ -478,128 +486,10 @@ unwrap_owners:
- ctxmgr
- app/intentflow
exceptions:
- kind: sql_write
package: projectinstance
file: internal/projectinstance/store.go
symbol: task_runs
owner: projectinstance
reason: workflow terminate cancels task_runs; U8 owns run finalization in the same UoW
milestone: U8
expires: "2026-10-26"
- kind: path
package: agent
file: internal/agent/cursor.go
symbol: output.md
owner: agent
reason: runner still names the deliverable file; U5 replaces this with WorkspaceRef
milestone: U5
expires: "2026-10-12"
- kind: path
package: agent
file: internal/agent/piagent.go
symbol: output.md
owner: agent
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
symbol: output.md
owner: bizdev
reason: orchestration still joins workspace output.md; U5 removes Root/OutputDir
milestone: U5
expires: "2026-10-12"
- kind: path
package: coordinator
file: internal/coordinator/context.go
symbol: output.md
owner: coordinator
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
symbol: output.md
owner: coordinator
reason: engine joins deliverable path; U5 WorkspaceRef
milestone: U5
expires: "2026-10-12"
- kind: path
package: docgov
file: internal/docgov/lease.go
symbol: output.md
owner: docgov
reason: SaveDocumentContent still writes projection files; U5 capability + U4 ledger-first read
milestone: U5
expires: "2026-10-12"
- kind: path
package: docgov
file: internal/docgov/project_docs.go
symbol: output.md
owner: docgov
reason: project doc helpers still construct output.md; U5
milestone: U5
expires: "2026-10-12"
- kind: path
package: docrevision
file: internal/docrevision/reconcile.go
symbol: output.md
owner: docrevision
reason: reconcile still repairs projection files; U5
milestone: U5
expires: "2026-10-12"
- kind: path
package: server
file: internal/server/server.go
symbol: workspaces/
owner: server
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"
exceptions: []
baseline_counts:
import: 0
sql_write: 1
path: 13
sql_write: 0
path: 0
event: 0
# U12: kinds already at zero production findings fail --mode block.
# sql_write/path keep dated exceptions until their owner milestones clear them.
block_kinds:
- import
- event
+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
+9 -11
View File
@@ -80,9 +80,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
@@ -216,9 +214,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 {
@@ -232,11 +230,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 {
@@ -545,7 +543,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 == vocabulary.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",
@@ -598,7 +596,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
if err != nil || doc == nil || doc.CurrentState != vocabulary.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
@@ -87,10 +87,16 @@ type Config struct {
HTTPAccessLogEnabled bool `json:"http_access_log_enabled"` // HTTP_ACCESS_LOG; default on for dev/test
}
// 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
@@ -17,6 +17,7 @@ import (
"chat-agent-system/internal/kb"
"chat-agent-system/internal/maturity"
"chat-agent-system/internal/squad"
"chat-agent-system/internal/workspace"
)
// UserInfo 用户在系统与项目内的身份
@@ -453,9 +454,9 @@ func (a *ContextAssembler) AssembleFromWorkspace(workDir string, user UserInfo)
}
}
// 5. 若无 advisor_brief.json 但有 output.md,尝试从文本抽取
// 5. 若无 advisor_brief.json 但有交付正文,尝试从文本抽取
if ctx.LatestAdvisorBrief == nil {
outPath := filepath.Join(workDir, "output.md")
outPath := filepath.Join(workDir, workspace.DeliverableKey)
if outBytes, err := os.ReadFile(outPath); err == nil && len(outBytes) > 0 {
ctx.LatestAdvisorBrief = squad.ExtractAdvisorBriefing(ctx.Project.CurrentWorkflow, string(outBytes), ctx.BlackboardFacts)
ctx.UserDocs.Composed = append(ctx.UserDocs.Composed, ctx.Project.CurrentWorkflow)
+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 != "" {
+4 -3
View File
@@ -23,6 +23,7 @@ import (
"chat-agent-system/internal/squad"
"chat-agent-system/internal/stream"
"chat-agent-system/internal/workflowscope"
"chat-agent-system/internal/workspace"
)
// CoordinationStatus 协调裁决状态
@@ -892,7 +893,7 @@ func (e *CoordinatorEngine) handleAdvanceStage(ctx context.Context, cand *intent
artifactPath := ""
if stageRes.PrimaryArtifact != nil {
artifactPath = filepath.Join(stageRes.PrimaryArtifact.WorkDir, "output.md")
artifactPath = filepath.Join(stageRes.PrimaryArtifact.WorkDir, workspace.DeliverableKey)
}
nextExp := BuildNextExpectation(targetStage, userCtx.Project.Name)
@@ -1048,7 +1049,7 @@ func (e *CoordinatorEngine) handleTriggerTask(ctx context.Context, cand *intent.
}
userCtx.LatestAdvisorBrief = taskRes.AdvisorBrief
outPath := filepath.Join(taskRes.WorkDir, "output.md")
outPath := filepath.Join(taskRes.WorkDir, workspace.DeliverableKey)
reply, taskStatus := formatTriggerTaskReply(taskRes, outPath)
@@ -1181,7 +1182,7 @@ func (e *CoordinatorEngine) handleTriggerAdhocTask(ctx context.Context, cand *in
if taskRes.WorkDir != "" {
userCtx.WorkDir = taskRes.WorkDir
}
outPath := filepath.Join(taskRes.WorkDir, "output.md")
outPath := filepath.Join(taskRes.WorkDir, workspace.DeliverableKey)
var sb strings.Builder
taskStatus := taskRes.TerminalStatus()
+2 -1
View File
@@ -14,6 +14,7 @@ import (
"chat-agent-system/internal/doccontent"
"chat-agent-system/internal/fsutil"
"chat-agent-system/internal/workspace"
)
// LeaseAcquireRequest 申请编辑租约
@@ -412,7 +413,7 @@ func (e *GovernanceEngine) SaveDocumentContent(ctx context.Context, req ContentS
workDir := doc.WorkDir
if workDir != "" {
outPath := filepath.Join(workDir, "output.md")
outPath := filepath.Join(workDir, workspace.DeliverableKey)
if werr := fsutil.WriteFileAtomic(outPath, []byte(canon), 0o644); werr != nil {
return nil, fmt.Errorf("docgov: write content failed: %w", werr)
}
+3 -2
View File
@@ -10,6 +10,7 @@ import (
"path/filepath"
"strings"
"time"
"chat-agent-system/internal/workspace"
)
// WorkflowDocumentSummary 项目工作流受控文档列表项。
@@ -135,7 +136,7 @@ func (e *GovernanceEngine) ListWorkflowDocumentsByProject(ctx context.Context, p
return collapseWorkflowDocuments(out), nil
}
// ReadDocumentContent 读取受控文档 output.md 并返回 typed ContentError。
// ReadDocumentContent 读取受控文档交付正文 并返回 typed ContentError。
// 记录不存在才是 DOCUMENT_NOT_FOUND;缺文件/空文件/哈希冲突不得伪装成 404。
func (e *GovernanceEngine) ReadDocumentContent(ctx context.Context, docID string, repoRoot string) (*DocumentContent, error) {
doc, err := e.GetDocument(ctx, docID)
@@ -153,7 +154,7 @@ func (e *GovernanceEngine) ReadDocumentContent(ctx context.Context, docID string
if !filepath.IsAbs(workDir) && repoRoot != "" {
workDir = filepath.Join(repoRoot, workDir)
}
path := filepath.Join(workDir, "output.md")
path := filepath.Join(workDir, workspace.DeliverableKey)
data, err := os.ReadFile(path)
if err != nil {
ce := NewContentError(ErrCodeProjectionMissing, fmt.Errorf("%w: %v", ErrContentProjectionMissing, err), true, true)
+5 -4
View File
@@ -9,6 +9,7 @@ import (
"time"
"chat-agent-system/internal/fsutil"
"chat-agent-system/internal/workspace"
)
// ContentHashUpdater updates document_records.content_sha256 after a repair.
@@ -62,7 +63,7 @@ func (s *Service) ReconcileDocument(ctx context.Context, req ReconcileRequest) (
if path == "" {
return &ReconcileResult{DocID: req.DocID, Class: ProjectionMissing, DryRun: !req.Apply, Detail: "empty work_dir"}, nil
}
raw, err := os.ReadFile(filepath.Join(path, "output.md"))
raw, err := os.ReadFile(filepath.Join(path, workspace.DeliverableKey))
if err != nil {
if os.IsNotExist(err) {
return &ReconcileResult{DocID: req.DocID, Class: ProjectionMissing, DryRun: !req.Apply, Detail: err.Error(), OldHeadSHA: headSHA, OldHeadID: oldHeadID}, nil
@@ -114,7 +115,7 @@ func (s *Service) ReconcileDocument(ctx context.Context, req ReconcileRequest) (
return nil, err
}
// Verify file still original bytes — never project old or new head over it.
after, _ := os.ReadFile(filepath.Join(path, "output.md"))
after, _ := os.ReadFile(filepath.Join(path, workspace.DeliverableKey))
if string(after) != string(raw) {
return nil, fmt.Errorf("docrevision: apply mutated original file; abort")
}
@@ -218,7 +219,7 @@ func BackupReconcileArtifacts(backupDir, docID string, raw []byte, headID int64,
}
arts := &BackupArtifacts{
Dir: runDir,
OutputFile: filepath.Join(runDir, "output.md"),
OutputFile: filepath.Join(runDir, workspace.DeliverableKey),
HeadFile: filepath.Join(runDir, "head.md"),
MetaFile: filepath.Join(runDir, "head.json"),
}
@@ -233,7 +234,7 @@ func BackupReconcileArtifacts(backupDir, docID string, raw []byte, headID int64,
"head_snapshot_id": headID,
"head_sha256": headSHA,
"head_markdown": "head.md",
"original_file": "output.md",
"original_file": workspace.DeliverableKey,
"backed_up_at": time.Now().UTC().Format(time.RFC3339),
"note": "do not restore this head over the original file",
"head_bytes": len(headMD),
+4 -4
View File
@@ -13,6 +13,7 @@ import (
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"chat-agent-system/internal/taskrun"
"chat-agent-system/internal/workflowdef"
)
@@ -916,10 +917,9 @@ func (s *pgStore) TerminateInstance(ctx context.Context, instanceID int64, proje
return nil, err
}
_, _ = tx.Exec(ctx, `
UPDATE task_runs SET status = 'cancelled', updated_at = $2, error_message = 'workflow terminated'
WHERE project_id = $1 AND status IN ('pending', 'running')
`, projectID, now)
if err := taskrun.CancelOpenByProject(ctx, tx, projectID, now, "workflow terminated"); err != nil {
return nil, err
}
if err := tx.Commit(ctx); err != nil {
return nil, err
+1 -1
View File
@@ -240,7 +240,7 @@ func (s *Server) Routes() http.Handler {
mux.HandleFunc("GET /api/v1/tasks/runs/{run_id}/events", s.handleTaskRunEvents)
mux.HandleFunc("POST /api/v1/tasks/runs/{run_id}/cancel", s.handleCancelTaskRun)
mux.HandleFunc("POST /api/v1/tasks/runs/{run_id}/resume", s.handleResumeTaskRun)
mux.HandleFunc("POST /api/v1/admin/workspaces/reconcile", s.handleReconcileWorkspace)
mux.HandleFunc("POST /api/v1/admin/"+workspace.WorkspacesDirName+"/reconcile", s.handleReconcileWorkspace)
mux.HandleFunc("POST /api/v1/projects/{id}/assets", s.handleUploadProjectAsset)
mux.HandleFunc("GET /api/v1/projects/{id}/assets", s.handleListProjectAssets)
mux.HandleFunc("GET /api/v1/projects/{id}/assets/{asset_id}/raw", s.handleGetProjectAssetRaw)
+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 {
+17
View File
@@ -302,6 +302,23 @@ func (s *PGStore) ListEvents(ctx context.Context, runID string, afterSeq int64)
return out, rows.Err()
}
// CancelOpenByProject cancels pending/running runs for a project inside an existing transaction.
// Table ownership stays with taskrun; workflow terminate must call this instead of writing task_runs directly.
func CancelOpenByProject(ctx context.Context, tx pgx.Tx, projectID int64, at time.Time, errMsg string) error {
if tx == nil {
return fmt.Errorf("taskrun: CancelOpenByProject requires tx")
}
if strings.TrimSpace(errMsg) == "" {
errMsg = "workflow terminated"
}
_, err := tx.Exec(ctx, `
UPDATE task_runs SET status = 'cancelled', updated_at = $2, error_message = $3
WHERE project_id = $1 AND status IN ('pending', 'running')
`, projectID, at, errMsg)
return err
}
func (s *PGStore) AcquireLease(ctx context.Context, runID, workerID string, ttl time.Duration) error {
if !s.Available() {
return ErrStoreNotAvailable
+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"