372 lines
25 KiB
Go
372 lines
25 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
agentdto "github.com/kandev/kandev/internal/agent/dto"
|
|
"github.com/kandev/kandev/internal/task/models"
|
|
"github.com/kandev/kandev/internal/task/repository/repoerrors"
|
|
v1 "github.com/kandev/kandev/pkg/api/v1"
|
|
)
|
|
|
|
var ErrWorkspaceNameMismatch = repoerrors.ErrWorkspaceNameMismatch
|
|
var ErrWorkspaceNotFound = repoerrors.ErrWorkspaceNotFound
|
|
var ErrTaskNotFound = repoerrors.ErrTaskNotFound
|
|
var ErrTaskPlanNotFound = repoerrors.ErrTaskPlanNotFound
|
|
var ErrRepositoryNotFound = repoerrors.ErrRepositoryNotFound
|
|
|
|
// WorkspaceRepository handles workspace CRUD.
|
|
type WorkspaceRepository interface {
|
|
CreateWorkspace(ctx context.Context, workspace *models.Workspace) error
|
|
GetWorkspace(ctx context.Context, id string) (*models.Workspace, error)
|
|
UpdateWorkspace(ctx context.Context, workspace *models.Workspace) error
|
|
DeleteWorkspace(ctx context.Context, id string) error
|
|
DeleteWorkspaceCascade(ctx context.Context, id string) ([]*models.Task, []*models.Workflow, error)
|
|
DeleteWorkspaceCascadeWithName(ctx context.Context, id, name string) ([]*models.Task, []*models.Workflow, error)
|
|
ListWorkspaces(ctx context.Context) ([]*models.Workspace, error)
|
|
}
|
|
|
|
// TaskRepository handles task CRUD and workflow placement.
|
|
// Note: models.TaskRepository is a struct in internal/task/models; no Go conflict exists.
|
|
type TaskRepository interface {
|
|
CreateTask(ctx context.Context, task *models.Task) error
|
|
GetTask(ctx context.Context, id string) (*models.Task, error)
|
|
GetTasksByIDs(ctx context.Context, ids []string) ([]*models.Task, error)
|
|
UpdateTask(ctx context.Context, task *models.Task) error
|
|
DeleteTask(ctx context.Context, id string) error
|
|
ListTasks(ctx context.Context, workflowID string) ([]*models.Task, error)
|
|
ListTasksByWorkspace(ctx context.Context, workspaceID, workflowID, repositoryID, query string, page, pageSize int, sort string, includeArchived, includeEphemeral, onlyEphemeral, excludeConfig bool) ([]*models.Task, int, error)
|
|
ListTasksByWorkflowStep(ctx context.Context, workflowStepID string) ([]*models.Task, error)
|
|
ArchiveTask(ctx context.Context, id string) error
|
|
// ArchiveTaskIfActive is the CAS variant used by office task-handoffs
|
|
// cascade archives. Returns whether the row was updated.
|
|
ArchiveTaskIfActive(ctx context.Context, id, cascadeID string) (bool, error)
|
|
// UnarchiveTaskByCascade clears archived_at only when the task was
|
|
// archived by the named cascade. Returns whether the row was updated.
|
|
UnarchiveTaskByCascade(ctx context.Context, id, cascadeID string) (bool, error)
|
|
// UnarchiveTask clears archived_at only when the task carries no
|
|
// cascade stamp (archived_by_cascade_id empty/NULL) — the CAS keeps a
|
|
// delayed manual unarchive from erasing a newer cascade archive.
|
|
// Cascade-stamped rows are restored via UnarchiveTaskByCascade.
|
|
// Returns whether the row was updated.
|
|
UnarchiveTask(ctx context.Context, id string) (bool, error)
|
|
ListTasksForAutoArchive(ctx context.Context) ([]*models.Task, error)
|
|
ListExpiredQuickChatTasks(ctx context.Context, cutoff time.Time) ([]*models.Task, error)
|
|
DeleteExpiredQuickChatTask(ctx context.Context, id string, cutoff time.Time) (bool, error)
|
|
// CountOpenWatcherCreatedTasks returns the number of open watcher-created
|
|
// tasks for a single watch, identified by the integration's task-metadata
|
|
// key (e.g. "sentry_issue_watch_id") and the watch id. Open = non-archived
|
|
// AND state NOT IN (COMPLETED, FAILED, CANCELLED). Used by the
|
|
// orchestrator's watcher throttle gate to enforce a per-watch cap. Keyed
|
|
// by metadata key (not integration name) so this layer stays agnostic of
|
|
// which integrations exist.
|
|
CountOpenWatcherCreatedTasks(ctx context.Context, metadataKey, watchID string) (int, error)
|
|
UpdateTaskState(ctx context.Context, id string, state v1.TaskState) error
|
|
// UpdateTaskStateIfCurrentIn atomically transitions state only when the
|
|
// task's current state is in allowed AND the task is not archived
|
|
// (archived_at IS NULL). The archived check is enforced inside the same
|
|
// UPDATE's WHERE clause, not just by a caller's earlier (non-transactional)
|
|
// read, so a late write can never race an ArchiveTask commit that lands
|
|
// between that read and this call. Returns the pre-update state and
|
|
// whether a row was modified.
|
|
UpdateTaskStateIfCurrentIn(ctx context.Context, id string, state v1.TaskState, allowed []v1.TaskState) (v1.TaskState, bool, error)
|
|
// UpdateTaskStateIfNotArchived is UpdateTaskStateIfCurrentIn without the
|
|
// prior-state constraint — for writers (e.g. IN_PROGRESS reconciliation)
|
|
// that legitimately fire from many prior states and only need the
|
|
// archived_at IS NULL guarantee. Same TOCTOU-closing semantics: the
|
|
// archived check is atomic with the write. Returns the pre-update state
|
|
// and whether a row was modified.
|
|
UpdateTaskStateIfNotArchived(ctx context.Context, id string, state v1.TaskState) (v1.TaskState, bool, error)
|
|
CountTasksByWorkflow(ctx context.Context, workflowID string) (int, error)
|
|
CountTasksByWorkflowStep(ctx context.Context, stepID string) (int, error)
|
|
AddTaskToWorkflow(ctx context.Context, taskID, workflowID, workflowStepID string, position int) error
|
|
RemoveTaskFromWorkflow(ctx context.Context, taskID, workflowID string) error
|
|
ListTasksByProject(ctx context.Context, projectID string) ([]*models.Task, error)
|
|
ListTasksByAssignee(ctx context.Context, agentInstanceID string) ([]*models.Task, error)
|
|
ListTaskTree(ctx context.Context, workspaceID string, filters models.TaskTreeFilters) ([]*models.Task, error)
|
|
// ListChildren returns non-archived, non-ephemeral child tasks of parentID.
|
|
ListChildren(ctx context.Context, parentID string) ([]*models.Task, error)
|
|
// ListChildrenIncludingArchived returns ALL child tasks of parentID,
|
|
// including archived ones. Used by the office task-handoffs unarchive
|
|
// cascade (phase 6) to walk a previously-archived descendant tree.
|
|
ListChildrenIncludingArchived(ctx context.Context, parentID string) ([]*models.Task, error)
|
|
// ReparentDirectChildren updates every row whose parent_id matches
|
|
// oldParentID, replacing it with newParentID. Used by no-cascade
|
|
// delete so direct children of a deleted task become roots
|
|
// (newParentID="") instead of dangling pointers. Affects archived
|
|
// and active rows alike.
|
|
ReparentDirectChildren(ctx context.Context, oldParentID, newParentID string) error
|
|
// ListSiblings returns non-archived, non-ephemeral sibling tasks for taskID.
|
|
// A task is a sibling of taskID when it shares a non-empty parent_id and
|
|
// the same workspace_id, and is not taskID itself. Root tasks (empty
|
|
// parent_id) intentionally have NO siblings — without a non-empty common
|
|
// parent, every other root in the workspace would falsely match.
|
|
ListSiblings(ctx context.Context, taskID string) ([]*models.Task, error)
|
|
IncrementTaskSequence(ctx context.Context, workspaceID string) (int, error)
|
|
GetWorkspaceTaskPrefix(ctx context.Context, workspaceID string) (prefix, officeWorkflowID string, err error)
|
|
}
|
|
|
|
// TaskRepoRepository handles the task↔repository junction table (models.TaskRepository rows).
|
|
// Named TaskRepoRepository to reduce reader confusion with the TaskRepository sub-interface above.
|
|
type TaskRepoRepository interface {
|
|
CreateTaskRepository(ctx context.Context, taskRepo *models.TaskRepository) error
|
|
GetTaskRepository(ctx context.Context, id string) (*models.TaskRepository, error)
|
|
ListTaskRepositories(ctx context.Context, taskID string) ([]*models.TaskRepository, error)
|
|
ListTaskRepositoriesByTaskIDs(ctx context.Context, taskIDs []string) (map[string][]*models.TaskRepository, error)
|
|
UpdateTaskRepository(ctx context.Context, taskRepo *models.TaskRepository) error
|
|
DeleteTaskRepository(ctx context.Context, id string) error
|
|
DeleteTaskRepositoriesByTask(ctx context.Context, taskID string) error
|
|
GetPrimaryTaskRepository(ctx context.Context, taskID string) (*models.TaskRepository, error)
|
|
}
|
|
|
|
// WorkflowRepository handles workflow CRUD.
|
|
type WorkflowRepository interface {
|
|
CreateWorkflow(ctx context.Context, workflow *models.Workflow) error
|
|
GetWorkflow(ctx context.Context, id string) (*models.Workflow, error)
|
|
UpdateWorkflow(ctx context.Context, workflow *models.Workflow) error
|
|
DeleteWorkflow(ctx context.Context, id string) error
|
|
ListWorkflows(ctx context.Context, workspaceID string, includeHidden bool) ([]*models.Workflow, error)
|
|
ReorderWorkflows(ctx context.Context, workspaceID string, workflowIDs []string) error
|
|
}
|
|
|
|
// MessageRepository handles message persistence and lookups.
|
|
type MessageRepository interface {
|
|
CreateMessage(ctx context.Context, message *models.Message) error
|
|
GetMessage(ctx context.Context, id string) (*models.Message, error)
|
|
GetMessageByToolCallID(ctx context.Context, sessionID, toolCallID string) (*models.Message, error)
|
|
GetMessageByPendingID(ctx context.Context, sessionID, pendingID string) (*models.Message, error)
|
|
FindMessageByPendingID(ctx context.Context, pendingID string) (*models.Message, error)
|
|
FindMessagesByPendingID(ctx context.Context, pendingID string) ([]*models.Message, error)
|
|
FindMessageByPendingIDAndQuestion(ctx context.Context, sessionID, pendingID, questionID string) (*models.Message, error)
|
|
FindPendingClarificationMessagesBySessionID(ctx context.Context, sessionID string) ([]*models.Message, error)
|
|
GetPendingActionsBySessionIDs(ctx context.Context, sessionIDs []string) (map[string]models.TaskPendingAction, error)
|
|
UpdateMessage(ctx context.Context, message *models.Message) error
|
|
ListMessages(ctx context.Context, sessionID string) ([]*models.Message, error)
|
|
ListMessagesByTurnID(ctx context.Context, turnID string) ([]*models.Message, error)
|
|
ListMessagesPaginated(ctx context.Context, sessionID string, opts models.ListMessagesOptions) ([]*models.Message, bool, error)
|
|
SearchMessages(ctx context.Context, sessionID string, opts models.SearchMessagesOptions) ([]*models.Message, error)
|
|
DeleteMessage(ctx context.Context, id string) error
|
|
}
|
|
|
|
// TurnRepository handles conversation turn persistence.
|
|
type TurnRepository interface {
|
|
CreateTurn(ctx context.Context, turn *models.Turn) error
|
|
GetTurn(ctx context.Context, id string) (*models.Turn, error)
|
|
GetActiveTurnBySessionID(ctx context.Context, sessionID string) (*models.Turn, error)
|
|
UpdateTurn(ctx context.Context, turn *models.Turn) error
|
|
CompleteTurn(ctx context.Context, id string) error
|
|
AbandonTurn(ctx context.Context, id string) error
|
|
CompletePendingToolCallsForTurn(ctx context.Context, turnID string) (int64, error)
|
|
ListTurnsBySession(ctx context.Context, sessionID string) ([]*models.Turn, error)
|
|
}
|
|
|
|
// SessionRepository handles task session lifecycle and workflow-session relationships.
|
|
type SessionRepository interface {
|
|
CreateTaskSession(ctx context.Context, session *models.TaskSession) error
|
|
GetTaskSession(ctx context.Context, id string) (*models.TaskSession, error)
|
|
GetTaskSessionByTaskID(ctx context.Context, taskID string) (*models.TaskSession, error)
|
|
GetActiveTaskSessionByTaskID(ctx context.Context, taskID string) (*models.TaskSession, error)
|
|
UpdateTaskSession(ctx context.Context, session *models.TaskSession) error
|
|
UpdateTaskSessionState(ctx context.Context, id string, state models.TaskSessionState, errorMessage string) error
|
|
ResetTaskSessionBasesForRepository(ctx context.Context, taskID, repositoryID, baseBranch string) (int64, error)
|
|
ListTaskSessions(ctx context.Context, taskID string) ([]*models.TaskSession, error)
|
|
ListActiveTaskSessions(ctx context.Context) ([]*models.TaskSession, error)
|
|
ListActiveTaskSessionsByTaskID(ctx context.Context, taskID string) ([]*models.TaskSession, error)
|
|
CancelActiveTaskSessionsByTaskID(ctx context.Context, taskID, reason string) (int64, error)
|
|
HasActiveTaskSessionsByAgentProfile(ctx context.Context, agentProfileID string) (bool, error)
|
|
GetActiveTaskInfoByAgentProfile(ctx context.Context, agentProfileID string) ([]agentdto.ActiveTaskInfo, error)
|
|
HasActiveTaskSessionsByExecutor(ctx context.Context, executorID string) (bool, error)
|
|
HasActiveTaskSessionsByEnvironment(ctx context.Context, environmentID string) (bool, error)
|
|
HasActiveTaskSessionsByRepository(ctx context.Context, repositoryID string) (bool, error)
|
|
CountActiveTaskSessionsByRepository(ctx context.Context, repositoryID string) (int, error)
|
|
DeleteEphemeralTasksByAgentProfile(ctx context.Context, agentProfileID string) (int64, error)
|
|
DeleteTaskSession(ctx context.Context, id string) error
|
|
GetPrimarySessionByTaskID(ctx context.Context, taskID string) (*models.TaskSession, error)
|
|
GetPrimarySessionIDsByTaskIDs(ctx context.Context, taskIDs []string) (map[string]string, error)
|
|
GetSessionCountsByTaskIDs(ctx context.Context, taskIDs []string) (map[string]int, error)
|
|
GetPrimarySessionInfoByTaskIDs(ctx context.Context, taskIDs []string) (map[string]*models.TaskSession, error)
|
|
// BatchGetSessionsByTaskIDs returns every session for the given task IDs
|
|
// grouped by task ID, ordered by started_at DESC within each task. One
|
|
// query (chunked to stay within SQLite's host-parameter limit) replaces
|
|
// per-task GetSession loops on the task-list path.
|
|
BatchGetSessionsByTaskIDs(ctx context.Context, taskIDs []string) (map[string][]*models.TaskSession, error)
|
|
SetSessionPrimary(ctx context.Context, sessionID string) error
|
|
UpdateSessionReviewStatus(ctx context.Context, sessionID string, status string) error
|
|
UpdateSessionMetadata(ctx context.Context, sessionID string, metadata map[string]interface{}) error
|
|
SetSessionMetadataKey(ctx context.Context, sessionID, key string, value interface{}) error
|
|
SetSessionACPSessionID(ctx context.Context, sessionID, acpSessionID string) (bool, error)
|
|
DismissLastAgentError(ctx context.Context, sessionID string, expected models.LastAgentError, dismissedAt time.Time) (bool, error)
|
|
GetLastAgentMessage(ctx context.Context, sessionID string) (string, error)
|
|
}
|
|
|
|
// SessionWorktreeRepository handles the task session↔worktree association.
|
|
type SessionWorktreeRepository interface {
|
|
CreateTaskSessionWorktree(ctx context.Context, sessionWorktree *models.TaskSessionWorktree) error
|
|
UpdateTaskSessionWorktreeBranch(ctx context.Context, sessionID, branch string) error
|
|
UpdateTaskSessionWorktreeBranchByRepository(ctx context.Context, sessionID, repositoryID, branch string) error
|
|
ListTaskSessionWorktrees(ctx context.Context, sessionID string) ([]*models.TaskSessionWorktree, error)
|
|
ListWorktreesBySessionIDs(ctx context.Context, sessionIDs []string) (map[string][]*models.TaskSessionWorktree, error)
|
|
DeleteTaskSessionWorktree(ctx context.Context, id string) error
|
|
DeleteTaskSessionWorktreesBySession(ctx context.Context, sessionID string) error
|
|
}
|
|
|
|
// TaskResourceCleanupRepository persists restart-safe task lifecycle cleanup.
|
|
type TaskResourceCleanupRepository interface {
|
|
CreateTaskResourceCleanupJob(ctx context.Context, job *models.TaskResourceCleanupJob) error
|
|
GetTaskResourceCleanupJob(ctx context.Context, id string) (*models.TaskResourceCleanupJob, error)
|
|
GetTaskResourceCleanupJobByOperationID(ctx context.Context, operationID string) (*models.TaskResourceCleanupJob, error)
|
|
ListPreparedTaskResourceCleanupJobs(ctx context.Context) ([]*models.TaskResourceCleanupJob, error)
|
|
ListDueTaskResourceCleanupJobs(ctx context.Context, now time.Time, limit int) ([]*models.TaskResourceCleanupJob, error)
|
|
StartPreparedTaskResourceCleanupJob(ctx context.Context, id string) (bool, error)
|
|
MarkTaskResourceCleanupJobRunning(ctx context.Context, id string) (bool, error)
|
|
CompleteClaimedTaskResourceCleanupJob(ctx context.Context, id string, attempt int, state models.TaskResourceCleanupState, lastError string, nextAttemptAt *time.Time) (bool, error)
|
|
CompleteTaskResourceCleanupJob(ctx context.Context, id string, state models.TaskResourceCleanupState, lastError string, nextAttemptAt *time.Time) error
|
|
CancelArchiveTaskResourceCleanupJobs(ctx context.Context, taskID string) error
|
|
ResetRunningTaskResourceCleanupJobs(ctx context.Context) error
|
|
}
|
|
|
|
// GitSnapshotRepository handles git snapshots and session commit records.
|
|
type GitSnapshotRepository interface {
|
|
CreateGitSnapshot(ctx context.Context, snapshot *models.GitSnapshot) error
|
|
GetLatestGitSnapshot(ctx context.Context, sessionID string) (*models.GitSnapshot, error)
|
|
GetFirstGitSnapshot(ctx context.Context, sessionID string) (*models.GitSnapshot, error)
|
|
GetGitSnapshotsBySession(ctx context.Context, sessionID string, limit int) ([]*models.GitSnapshot, error)
|
|
CreateSessionCommit(ctx context.Context, commit *models.SessionCommit) error
|
|
GetSessionCommits(ctx context.Context, sessionID string) ([]*models.SessionCommit, error)
|
|
GetLatestSessionCommit(ctx context.Context, sessionID string) (*models.SessionCommit, error)
|
|
DeleteSessionCommit(ctx context.Context, id string) error
|
|
}
|
|
|
|
// RepositoryEntityRepository handles git repository entity CRUD and repository scripts.
|
|
// Named RepositoryEntityRepository to avoid conflation with the Repository interface itself;
|
|
// mirrors the sqlite/repository_entity.go implementation file.
|
|
type RepositoryEntityRepository interface {
|
|
CreateRepository(ctx context.Context, repository *models.Repository) error
|
|
GetRepository(ctx context.Context, id string) (*models.Repository, error)
|
|
UpdateRepository(ctx context.Context, repository *models.Repository) error
|
|
DeleteRepository(ctx context.Context, id string) error
|
|
ListRepositories(ctx context.Context, workspaceID string) ([]*models.Repository, error)
|
|
CreateRepositoryScript(ctx context.Context, script *models.RepositoryScript) error
|
|
GetRepositoryScript(ctx context.Context, id string) (*models.RepositoryScript, error)
|
|
UpdateRepositoryScript(ctx context.Context, script *models.RepositoryScript) error
|
|
DeleteRepositoryScript(ctx context.Context, id string) error
|
|
ListRepositoryScripts(ctx context.Context, repositoryID string) ([]*models.RepositoryScript, error)
|
|
ListScriptsByRepositoryIDs(ctx context.Context, repoIDs []string) (map[string][]*models.RepositoryScript, error)
|
|
GetRepositoryByProviderInfo(ctx context.Context, workspaceID, provider, owner, name string) (*models.Repository, error)
|
|
}
|
|
|
|
// ExecutorRepository handles executor CRUD, executor profiles, and running state.
|
|
type ExecutorRepository interface {
|
|
CreateExecutor(ctx context.Context, executor *models.Executor) error
|
|
GetExecutor(ctx context.Context, id string) (*models.Executor, error)
|
|
UpdateExecutor(ctx context.Context, executor *models.Executor) error
|
|
DeleteExecutor(ctx context.Context, id string) error
|
|
ListExecutors(ctx context.Context) ([]*models.Executor, error)
|
|
CreateExecutorProfile(ctx context.Context, profile *models.ExecutorProfile) error
|
|
GetExecutorProfile(ctx context.Context, id string) (*models.ExecutorProfile, error)
|
|
UpdateExecutorProfile(ctx context.Context, profile *models.ExecutorProfile) error
|
|
DeleteExecutorProfile(ctx context.Context, id string) error
|
|
ListExecutorProfiles(ctx context.Context, executorID string) ([]*models.ExecutorProfile, error)
|
|
ListAllExecutorProfiles(ctx context.Context) ([]*models.ExecutorProfile, error)
|
|
ListExecutorsRunning(ctx context.Context) ([]*models.ExecutorRunning, error)
|
|
ListExecutorsRunningByTaskID(ctx context.Context, taskID string) ([]*models.ExecutorRunning, error)
|
|
UpsertExecutorRunning(ctx context.Context, running *models.ExecutorRunning) error
|
|
GetExecutorRunningBySessionID(ctx context.Context, sessionID string) (*models.ExecutorRunning, error)
|
|
DeleteExecutorRunningBySessionID(ctx context.Context, sessionID string) error
|
|
// HasExecutorRunningRow returns true if a row exists for the session.
|
|
// Used to decide "session has been launched at least once" without loading the full row.
|
|
HasExecutorRunningRow(ctx context.Context, sessionID string) (bool, error)
|
|
// UpdateResumeToken performs a CAS-style narrow update of resume_token + last_message_uuid
|
|
// scoped to the row's current agent_execution_id. If the row's agent_execution_id no longer
|
|
// matches expectedExecID (i.e. a new execution has taken over), returns models.ErrExecutionRotated
|
|
// and writes nothing. Use when persisting state from a specific execution that may have been
|
|
// replaced concurrently — typically resume tokens emitted by ACP session events.
|
|
UpdateResumeToken(ctx context.Context, sessionID, expectedExecID, resumeToken, lastMessageUUID string) error
|
|
// UpdateExecutorRunningStatus performs a narrow status update on the row.
|
|
// Used when the agent process is intentionally not being started (prepare-only
|
|
// launch) so the row doesn't sit on the misleading default "starting" forever.
|
|
// Returns models.ErrExecutorRunningNotFound if no row exists for the session.
|
|
UpdateExecutorRunningStatus(ctx context.Context, sessionID, status string) error
|
|
// RepairExecutorRunningDead repairs a row in place to reflect a dead backing
|
|
// process (status=stopped, local_pid cleared, last_seen re-stamped) while
|
|
// preserving resume_token/worktree/endpoint. Used by cleanup paths to honor
|
|
// the resume-safety invariant instead of deleting a resumable row.
|
|
// Returns models.ErrExecutorRunningNotFound if no row exists for the session.
|
|
RepairExecutorRunningDead(ctx context.Context, sessionID string) error
|
|
}
|
|
|
|
// EnvironmentRepository handles environment CRUD.
|
|
type EnvironmentRepository interface {
|
|
CreateEnvironment(ctx context.Context, environment *models.Environment) error
|
|
GetEnvironment(ctx context.Context, id string) (*models.Environment, error)
|
|
UpdateEnvironment(ctx context.Context, environment *models.Environment) error
|
|
DeleteEnvironment(ctx context.Context, id string) error
|
|
ListEnvironments(ctx context.Context) ([]*models.Environment, error)
|
|
}
|
|
|
|
// TaskEnvironmentRepository handles per-task execution environment instances
|
|
// and their per-repository child rows.
|
|
type TaskEnvironmentRepository interface {
|
|
CreateTaskEnvironment(ctx context.Context, env *models.TaskEnvironment) error
|
|
GetTaskEnvironment(ctx context.Context, id string) (*models.TaskEnvironment, error)
|
|
GetTaskEnvironmentByTaskID(ctx context.Context, taskID string) (*models.TaskEnvironment, error)
|
|
UpdateTaskEnvironment(ctx context.Context, env *models.TaskEnvironment) error
|
|
DeleteTaskEnvironment(ctx context.Context, id string) error
|
|
DeleteTaskEnvironmentsByTask(ctx context.Context, taskID string) error
|
|
CreateTaskEnvironmentRepo(ctx context.Context, repo *models.TaskEnvironmentRepo) error
|
|
ListTaskEnvironmentRepos(ctx context.Context, envID string) ([]*models.TaskEnvironmentRepo, error)
|
|
UpdateTaskEnvironmentRepo(ctx context.Context, repo *models.TaskEnvironmentRepo) error
|
|
DeleteTaskEnvironmentRepo(ctx context.Context, id string) error
|
|
DeleteTaskEnvironmentReposByEnv(ctx context.Context, envID string) error
|
|
}
|
|
|
|
// ReviewRepository handles session file review records.
|
|
type ReviewRepository interface {
|
|
UpsertSessionFileReview(ctx context.Context, review *models.SessionFileReview) error
|
|
GetSessionFileReviews(ctx context.Context, sessionID string) ([]*models.SessionFileReview, error)
|
|
DeleteSessionFileReviews(ctx context.Context, sessionID string) error
|
|
}
|
|
|
|
// DocumentRepository handles task document CRUD and revision history.
|
|
// Documents generalize plans: each document is identified by a unique key within a task.
|
|
type DocumentRepository interface {
|
|
CreateDocument(ctx context.Context, doc *models.TaskDocument) error
|
|
GetDocument(ctx context.Context, taskID, key string) (*models.TaskDocument, error)
|
|
UpdateDocument(ctx context.Context, doc *models.TaskDocument) error
|
|
DeleteDocument(ctx context.Context, taskID, key string) error
|
|
ListDocuments(ctx context.Context, taskID string) ([]*models.TaskDocument, error)
|
|
|
|
// Revision history
|
|
InsertDocumentRevision(ctx context.Context, rev *models.TaskDocumentRevision) error
|
|
GetLatestDocumentRevision(ctx context.Context, taskID, key string) (*models.TaskDocumentRevision, error)
|
|
ListDocumentRevisions(ctx context.Context, taskID, key string, limit int) ([]*models.TaskDocumentRevision, error)
|
|
GetDocumentRevision(ctx context.Context, id string) (*models.TaskDocumentRevision, error)
|
|
NextDocumentRevisionNumber(ctx context.Context, taskID, key string) (int, error)
|
|
// WriteDocumentRevision atomically upserts the HEAD document and writes/merges a revision
|
|
// in a single transaction. Pass a non-nil coalesceLatestID to merge into an existing revision;
|
|
// otherwise a new revision is appended with revision_number computed inside the tx.
|
|
WriteDocumentRevision(ctx context.Context, head *models.TaskDocument, rev *models.TaskDocumentRevision, coalesceLatestID *string) error
|
|
}
|
|
|
|
// PlanRepository handles task plan CRUD and its revision history.
|
|
type PlanRepository interface {
|
|
CreateTaskPlan(ctx context.Context, plan *models.TaskPlan) error
|
|
GetTaskPlan(ctx context.Context, taskID string) (*models.TaskPlan, error)
|
|
UpdateTaskPlan(ctx context.Context, plan *models.TaskPlan) error
|
|
MarkTaskPlanImplementationStarted(ctx context.Context, taskID, sessionID, actor string) (*models.TaskPlan, error)
|
|
DeleteTaskPlan(ctx context.Context, taskID string) error
|
|
|
|
// Revision history
|
|
InsertTaskPlanRevision(ctx context.Context, rev *models.TaskPlanRevision) error
|
|
UpdateTaskPlanRevision(ctx context.Context, rev *models.TaskPlanRevision) error
|
|
GetTaskPlanRevision(ctx context.Context, id string) (*models.TaskPlanRevision, error)
|
|
GetLatestTaskPlanRevision(ctx context.Context, taskID string) (*models.TaskPlanRevision, error)
|
|
ListTaskPlanRevisions(ctx context.Context, taskID string, limit int) ([]*models.TaskPlanRevision, error)
|
|
NextTaskPlanRevisionNumber(ctx context.Context, taskID string) (int, error)
|
|
// WritePlanRevision atomically upserts the HEAD plan and writes/merges a revision in a
|
|
// single transaction. Pass a non-nil coalesceLatestID to merge into an existing revision;
|
|
// otherwise a new revision is appended with revision_number computed inside the tx.
|
|
WritePlanRevision(ctx context.Context, head *models.TaskPlan, rev *models.TaskPlanRevision, coalesceLatestID *string) error
|
|
}
|