Files
ratatoskr-go/internal/storage/models.go
ki.sagidullin 1f7ab9a67e feat(events): EventBus + domain-модель статусов
- internal/model: единый источник статусов задачи (Status, TraceStatus,
  машина переходов), без зависимости от storage.
- internal/storage: совместимый мост (type Status = model.Status,
  re-export констант) — внешний код не меняется.
- internal/events: шина событий (fan-out, блокирующий Publish с гарантией
  порядка), события задач/трейсов, отдельная логовая шина + LogWriter,
  Publisher/NilPublisher для внедрения в Core/Worker.
- docs/ui-spec.md: спецификация десктопного UI (Fyne).
2026-08-20 01:02:31 +05:00

120 lines
4.2 KiB
Go

package storage
import (
"encoding/json"
"github.com/kamelion/ratatoskr-go/internal/model"
)
// Status — алиас доменного статуса задачи.
//
// Совместимый мост: весь внешний код продолжает использовать storage.Status
// (например «storage.StatusRunning»), но единый источник истины — model.Status.
type Status = model.Status
// Статусы задачи — re-export из model.
const (
StatusDraft = model.StatusDraft
StatusCollecting = model.StatusCollecting
StatusReady = model.StatusReady
StatusApproved = model.StatusApproved
StatusRunning = model.StatusRunning
StatusSuccess = model.StatusSuccess
StatusFailed = model.StatusFailed
StatusTimeout = model.StatusTimeout
StatusCancelled = model.StatusCancelled
StatusAborted = model.StatusAborted
StatusClosed = model.StatusClosed
)
// AllStatuses — все возможные статусы для валидации.
var AllStatuses = model.AllStatuses
// IsValidTransition проверяет, допустим ли переход from → to.
func IsValidTransition(from, to Status) bool {
return model.IsValidTransition(from, to)
}
// IsTerminal возвращает true, если статус терминальный.
func IsTerminal(s Status) bool {
return model.IsTerminal(s)
}
// 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"` // обратная совместимость: одиночный репозиторий
Repos []string `json:"repos"` // список репозиториев (основной)
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"`
}
// ReposJoined возвращает repos как одну строку (JSON-массив) для хранения в БД.
// Пустой список → пустая строка.
func (t *Task) ReposJoined() string {
if len(t.Repos) == 0 {
return ""
}
b, _ := json.Marshal(t.Repos)
return string(b)
}
// EffectiveRepos возвращает сам репо: если Repos пуст, но Repo задан —
// подтягивает одиночный (обратная совместимость).
func (t *Task) EffectiveRepos() []string {
if len(t.Repos) > 0 {
return t.Repos
}
if t.Repo != "" {
return []string{t.Repo}
}
return nil
}
// SetReposFromDB заполняет Repos из сохранённой строки (JSON), либо
// из одиночного repo (обратная совместимость).
func (t *Task) SetReposFromDB(repos string) {
if repos == "" {
t.Repos = nil
return
}
_ = json.Unmarshal([]byte(repos), &t.Repos)
}
// TraceStatus — алиас доменного статуса трассировки.
type TraceStatus = model.TraceStatus
const (
TraceRunning TraceStatus = model.TraceRunning
TraceSuccess TraceStatus = model.TraceSuccess
TraceFailed TraceStatus = model.TraceFailed
TraceTimeout TraceStatus = model.TraceTimeout
)
// 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
}