feat: add execution history queries and preserve bounded JSON payloads
This commit is contained in:
122
pulse/service/history.go
Normal file
122
pulse/service/history.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/fault"
|
||||
core "git.yuntuo.hk/k2safe/orbit/model"
|
||||
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
||||
pulse "git.yuntuo.hk/k2safe/orbit/pulse/model"
|
||||
)
|
||||
|
||||
// HistoryPageInput 用于任务执行历史的有界分页;游标必须沿原父资源继续使用。
|
||||
type HistoryPageInput struct {
|
||||
Limit int // Limit 默认 20,最大 100;列表不返回业务结果正文。
|
||||
Cursor string // Cursor 服务端不透明游标,最长 1024 字节;为空从最新记录开始。
|
||||
}
|
||||
|
||||
// AttemptSummary 是执行尝试的查询快照,不提供当前执行授权。
|
||||
type AttemptSummary struct {
|
||||
ID core.ID `json:"id"` // ID 尝试标识,不是执行秘密。
|
||||
TaskID core.ID `json:"task_id"` // TaskID 所属任务;查询始终限定在该任务下。
|
||||
Epoch uint64 `json:"epoch,string"` // Epoch 单任务执行代次,JSON 为十进制字符串。
|
||||
State pulse.AttemptState `json:"state"` // State 尝试状态,与整个任务状态分开。
|
||||
Revision uint64 `json:"revision,string"` // Revision 当前记录版本,可因续租/恢复变化。
|
||||
LeaseUntil time.Time `json:"lease_until"` // LeaseUntil 数据库记录的租约期限;查询不能授权业务执行。
|
||||
AuthorizedAt *time.Time `json:"authorized_at,omitempty"` // AuthorizedAt 开始批准时间;空表示未批准,不证明副作用已经发生。
|
||||
EndedAt *time.Time `json:"ended_at,omitempty"` // EndedAt 尝试结束时间;仍在执行时为空。
|
||||
ErrorCode string `json:"error_code,omitempty"` // ErrorCode 稳定错误分类;无错误时为空。
|
||||
CreatedAt time.Time `json:"created_at"` // CreatedAt 创建时间,UTC 微秒精度。
|
||||
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt 最新变更时间,不能代替版本校验。
|
||||
}
|
||||
|
||||
// AttemptView 增加当前归属与已保存结果,不返回令牌、摘要或原始请求秘密。
|
||||
type AttemptView struct {
|
||||
AttemptSummary // AttemptSummary 尝试状态、执行代次与时间快照。
|
||||
SessionID core.ID `json:"session_id"` // SessionID 当前或最后归属会话;重连迁移后可变化。
|
||||
WorkerInstanceID core.ID `json:"worker_instance_id"` // WorkerInstanceID 对应进程身份,不是认证凭据。
|
||||
Result json.RawMessage `json:"result,omitempty"` // Result 已保存的尝试结果,未保存时省略;不包含迟到证据。
|
||||
}
|
||||
|
||||
// AttemptPage 返回一页尝试摘要及可选的后续位置。
|
||||
type AttemptPage struct {
|
||||
Items []AttemptSummary `json:"items"` // Items 按 Epoch 降序排列的摘要,空列表返回 []。
|
||||
NextCursor string `json:"next_cursor,omitempty"` // NextCursor 后续页游标,空表示本次已读到末尾。
|
||||
}
|
||||
|
||||
// EvidenceSummary 描述追加证据,原报告并未改变权威任务结果。
|
||||
type EvidenceSummary struct {
|
||||
ID core.ID `json:"id"` // ID 不可变证据标识。
|
||||
TaskID core.ID `json:"task_id"` // TaskID 所属任务,从已验证的父关联确定。
|
||||
AttemptID core.ID `json:"attempt_id"` // AttemptID 所属执行尝试。
|
||||
Outcome string `json:"outcome"` // Outcome Worker 声明的 SUCCEEDED、FAILED 或 UNKNOWN,不代表平台认可。
|
||||
ErrorCode string `json:"error_code,omitempty"` // ErrorCode 原报告的稳定分类,可为空。
|
||||
Reason string `json:"reason"` // Reason LEASE_LOST、NOT_STARTED 或 RESULT_CONFLICT。
|
||||
Disposition string `json:"disposition"` // Disposition 固定为 EVIDENCE_ONLY,表示采集时仅保存证据。
|
||||
CreatedAt time.Time `json:"created_at"` // CreatedAt 首次收到时间,重复报告不改变它。
|
||||
}
|
||||
|
||||
// EvidenceView 提供单条追加证据的原报告身份和正文。
|
||||
type EvidenceView struct {
|
||||
EvidenceSummary // EvidenceSummary 原报告分类及采集时间。
|
||||
MessageID core.ID `json:"message_id"` // MessageID 原结果报告身份,用于追踪同一次重报。
|
||||
Payload json.RawMessage `json:"payload"` // Payload 原始结果/证据引用 JSON,保留数字文本;不提升为任务结果。
|
||||
}
|
||||
|
||||
// EvidencePage 返回一页证据摘要,正文通过详情单独读取。
|
||||
type EvidencePage struct {
|
||||
Items []EvidenceSummary `json:"items"` // Items 按 created_at、ID 降序的证据摘要,不含大正文。
|
||||
NextCursor string `json:"next_cursor,omitempty"` // NextCursor 必须沿同一任务及尝试继续使用。
|
||||
}
|
||||
|
||||
func normalizeHistoryPage(in HistoryPageInput) (HistoryPageInput, error) {
|
||||
if in.Limit == 0 {
|
||||
in.Limit = 20
|
||||
}
|
||||
if in.Limit < 1 || in.Limit > 100 || len(in.Cursor) > 1024 {
|
||||
return in, fault.Invalid("invalid history page limit or cursor")
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
func validateHistoryIDs(ids ...core.ID) error {
|
||||
for _, id := range ids {
|
||||
if id.Validate() != nil {
|
||||
return fault.Invalid("invalid history resource id")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *Service) ListTaskAttempts(ctx context.Context, t auth.Token, task core.ID, in HistoryPageInput) (AttemptPage, error) {
|
||||
if err := validateHistoryIDs(task); err != nil {
|
||||
return AttemptPage{}, err
|
||||
}
|
||||
in, err := normalizeHistoryPage(in)
|
||||
if err != nil {
|
||||
return AttemptPage{}, err
|
||||
}
|
||||
return s.store.ListTaskAttempts(ctx, t, task, in)
|
||||
}
|
||||
func (s *Service) GetTaskAttempt(ctx context.Context, t auth.Token, task, attempt core.ID) (AttemptView, error) {
|
||||
if err := validateHistoryIDs(task, attempt); err != nil {
|
||||
return AttemptView{}, err
|
||||
}
|
||||
return s.store.GetTaskAttempt(ctx, t, task, attempt)
|
||||
}
|
||||
func (s *Service) ListAttemptEvidence(ctx context.Context, t auth.Token, task, attempt core.ID, in HistoryPageInput) (EvidencePage, error) {
|
||||
if err := validateHistoryIDs(task, attempt); err != nil {
|
||||
return EvidencePage{}, err
|
||||
}
|
||||
in, err := normalizeHistoryPage(in)
|
||||
if err != nil {
|
||||
return EvidencePage{}, err
|
||||
}
|
||||
return s.store.ListAttemptEvidence(ctx, t, task, attempt, in)
|
||||
}
|
||||
func (s *Service) GetAttemptEvidence(ctx context.Context, t auth.Token, task, attempt, evidence core.ID) (EvidenceView, error) {
|
||||
if err := validateHistoryIDs(task, attempt, evidence); err != nil {
|
||||
return EvidenceView{}, err
|
||||
}
|
||||
return s.store.GetAttemptEvidence(ctx, t, task, attempt, evidence)
|
||||
}
|
||||
@@ -183,4 +183,8 @@ type Store interface {
|
||||
CancelTask(context.Context, auth.Token, core.ID, string, CancelInput) (CancelResult, error)
|
||||
CreateQueue(context.Context, auth.Token, string, QueueInput) (QueueResult, error)
|
||||
GetQueue(context.Context, auth.Token, core.ID) (QueueView, error)
|
||||
ListTaskAttempts(context.Context, auth.Token, core.ID, HistoryPageInput) (AttemptPage, error)
|
||||
GetTaskAttempt(context.Context, auth.Token, core.ID, core.ID) (AttemptView, error)
|
||||
ListAttemptEvidence(context.Context, auth.Token, core.ID, core.ID, HistoryPageInput) (EvidencePage, error)
|
||||
GetAttemptEvidence(context.Context, auth.Token, core.ID, core.ID, core.ID) (EvidenceView, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user