internal/storage: SQLite-БД для задач и трассировок (таски + статус-машина, трассы с группировкой по task_id)
All checks were successful
build-test / build (push) Successful in 30s

This commit is contained in:
Hermes
2026-08-14 23:38:57 +05:00
parent cc180d7517
commit 089e196dbc
7 changed files with 821 additions and 0 deletions

104
internal/storage/models.go Normal file
View File

@@ -0,0 +1,104 @@
package storage
// Status — статус задачи (state machine).
type Status string
const (
StatusDraft Status = "draft" // только что создана
StatusCollecting Status = "collecting" // аналитик собирает детали
StatusReady Status = "ready" // черновик готов, ждёт запуска
StatusRunning Status = "running" // opencode работает
StatusSuccess Status = "success" // задача выполнена
StatusFailed Status = "failed" // ошибка выполнения
StatusTimeout Status = "timeout" // таймаут opencode
StatusCancelled Status = "cancelled" // отменена пользователем
StatusAborted Status = "aborted" // сбой сбора, черновик выброшен
StatusClosed Status = "closed" // закрыта вручную
)
// AllStatuses — все возможные статусы для валидации.
var AllStatuses = []Status{
StatusDraft, StatusCollecting, StatusReady,
StatusRunning, StatusSuccess, StatusFailed, StatusTimeout,
StatusCancelled, StatusAborted, StatusClosed,
}
// validTransitions задаёт разрешённые переходы статусов.
var validTransitions = map[Status][]Status{
StatusDraft: {StatusCollecting, StatusCancelled, StatusAborted},
StatusCollecting: {StatusReady, StatusDraft, StatusCancelled, StatusAborted},
StatusReady: {StatusRunning, StatusCancelled, StatusAborted, StatusClosed},
StatusRunning: {StatusSuccess, StatusFailed, StatusTimeout, StatusCancelled},
StatusSuccess: {StatusClosed},
StatusFailed: {StatusReady, StatusClosed, StatusCancelled}, // retry
StatusTimeout: {StatusReady, StatusClosed, StatusCancelled}, // retry
StatusCancelled: {StatusClosed},
StatusAborted: {StatusClosed},
StatusClosed: {}, // терминальный
}
// IsValidTransition проверяет, допустим ли переход from → to.
func IsValidTransition(from, to Status) bool {
allowed, ok := validTransitions[from]
if !ok {
return false
}
for _, s := range allowed {
if s == to {
return true
}
}
return false
}
// IsTerminal возвращает true, если статус терминальный.
func IsTerminal(s Status) bool {
return s == StatusSuccess || s == StatusCancelled ||
s == StatusAborted || s == StatusClosed
}
// Task — запись задачи в БД.
type Task struct {
ID int64 `json:"id"`
ChatID string `json:"chat_id"` // tg://<id>
Title string `json:"title"`
Goal string `json:"goal"`
Repo string `json:"repo"`
Why string `json:"why"`
AC string `json:"ac"` // acceptance criteria
TaskTag string `json:"task_tag"` // UUID, стабильный на всю жизнь
Status Status `json:"status"`
CreatedAt SQLiteTime `json:"created_at"`
UpdatedAt SQLiteTime `json:"updated_at"`
}
// Trace — запись трассировки выполнения.
type TraceStatus string
const (
TraceRunning TraceStatus = "running"
TraceSuccess TraceStatus = "success"
TraceFailed TraceStatus = "failed"
TraceTimeout TraceStatus = "timeout"
)
// Trace — лог одного субагента.
type Trace struct {
ID int64 `json:"id"`
TaskID int64 `json:"task_id"`
Agent string `json:"agent"` // analyst | researcher | dev | reviewer
SessionID string `json:"session_id"` // opencode session_id
Prompt string `json:"prompt"`
Output string `json:"output"` // полный NDJSON или summary
Status TraceStatus `json:"status"`
StartedAt SQLiteTime `json:"started_at"`
FinishedAt NullSQLiteTime `json:"finished_at,omitempty"`
}
// TaskFilter — параметры фильтрации списка задач.
type TaskFilter struct {
ChatID string
Status Status
Limit int
Offset int
}