feat: expose workflow runtime through HTTP and Go SDK
This commit is contained in:
55
weave/instance/details.go
Normal file
55
weave/instance/details.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package instance
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// StepDetail 只读取一个当前激活的输入输出,避免步骤列表聚合大载荷。
|
||||
type StepDetail struct {
|
||||
InstanceID core.ID `json:"instance_id"` // InstanceID 授权实例。
|
||||
Revision uint64 `json:"revision,string"` // Revision 与本次步骤详情同一快照的实例版本。
|
||||
Step StepView `json:"step"` // Step 当前步骤与激活身份。
|
||||
Input json.RawMessage `json:"input,omitempty"` // Input 当前激活输入,未激活时省略,显式null仍保留。
|
||||
Output json.RawMessage `json:"output,omitempty"` // Output 当前激活已有结果,尚无结果时省略。
|
||||
ActivatedAt *time.Time `json:"activated_at,omitempty"` // ActivatedAt 当前激活创建时间,不代表等待预算重新开始。
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"` // CompletedAt 当前激活结束时间;核查或进行中可能为空。
|
||||
Poll *PollView `json:"poll,omitempty"` // Poll 当前激活的查询预算及最后任务,仅POLL有值。
|
||||
Wait *WaitView `json:"wait,omitempty"` // Wait 当前信号/定时/最终信号等待,原截止不会因换代延长。
|
||||
}
|
||||
type PollView struct {
|
||||
UsedRounds uint32 `json:"used_rounds"` // UsedRounds 已用业务轮编号,跨恢复激活累计。
|
||||
FirstRound uint32 `json:"first_round"` // FirstRound 本激活继承的首个业务轮。
|
||||
FirstRoundAttemptsUsed uint32 `json:"first_round_attempts_used"` // FirstRoundAttemptsUsed 该轮此前激活实际已用请求尝试。
|
||||
MaxRounds uint32 `json:"max_rounds"` // MaxRounds 固定业务轮上限。
|
||||
MaxAttemptsPerRound uint32 `json:"max_attempts_per_round"` // MaxAttemptsPerRound 原单轮请求尝试上限。
|
||||
IntervalMS uint64 `json:"interval_ms"` // IntervalMS 成功PENDING后的原查询间隔,毫秒。
|
||||
DeadlineAt time.Time `json:"deadline_at"` // DeadlineAt 第一次查询激活确定的绝对截止。
|
||||
NextPollAt *time.Time `json:"next_poll_at,omitempty"` // NextPollAt 下一业务轮可开始时间;非轮间等待为空。
|
||||
CurrentTaskID *core.ID `json:"current_task_id,omitempty"` // CurrentTaskID 当前尚待收集结果的查询任务。
|
||||
LastTaskID core.ID `json:"last_task_id"` // LastTaskID 本激活最后一轮任务,可据此读取执行详情并核查。
|
||||
ResolvedAt *time.Time `json:"resolved_at,omitempty"` // ResolvedAt 当前查询预算结清时间。
|
||||
}
|
||||
type WaitView struct {
|
||||
ID core.ID `json:"id"` // ID 当前激活等待身份。
|
||||
Kind string `json:"kind"` // Kind SIGNAL、TIMER或POLL。
|
||||
State string `json:"state"` // State 等待、满足、过期或取消状态。
|
||||
Name *string `json:"name,omitempty"` // Name 信号名称;TIMER及未核验的旧等待为空。
|
||||
PayloadVersion *string `json:"payload_version,omitempty"` // PayloadVersion 冻结信号内容契约。
|
||||
CorrelationKey string `json:"correlation_key"` // CorrelationKey 原精确关联;TIMER为空字符串。
|
||||
DeadlineAt time.Time `json:"deadline_at"` // DeadlineAt 首次等待的绝对截止。
|
||||
WinnerSignalID *core.ID `json:"winner_signal_id,omitempty"` // WinnerSignalID 已选中信号,不表示在途执行已结束。
|
||||
ResolvedAt *time.Time `json:"resolved_at,omitempty"` // ResolvedAt 等待结清时间。
|
||||
}
|
||||
|
||||
func (s *Service) Step(ctx context.Context, t auth.Token, id core.ID, key string) (StepDetail, error) {
|
||||
if id.Validate() != nil || !versionName(key) {
|
||||
return StepDetail{}, fault.Invalid("valid workflow id and step key required")
|
||||
}
|
||||
return s.store.Step(ctx, t, id, key)
|
||||
}
|
||||
63
weave/instance/operations.go
Normal file
63
weave/instance/operations.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/fault"
|
||||
core "git.yuntuo.hk/k2safe/orbit/model"
|
||||
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
||||
"git.yuntuo.hk/k2safe/orbit/pulse/service"
|
||||
"time"
|
||||
)
|
||||
|
||||
const AuditAction = "workflows:audit:read"
|
||||
|
||||
type OperationSummary struct {
|
||||
// ID 不可改写的审计记录标识。
|
||||
ID core.ID `json:"id"`
|
||||
// WorkflowID 本页授权流程标识。
|
||||
WorkflowID core.ID `json:"workflow_id"`
|
||||
// OperationID 服务端管理操作身份,不是调用方原始幂等键。
|
||||
OperationID core.ID `json:"operation_id"`
|
||||
// ActorID 实际认证主体标识,不接受请求体自报。
|
||||
ActorID string `json:"actor_id"`
|
||||
// Action 管理动作,例如workflow.task.review、workflow.step.retry、workflow.control、workflow.advanced。
|
||||
Action string `json:"action"`
|
||||
// CreatedAt 不可变审计创建时间,UTC微秒精度。
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type OperationView struct {
|
||||
OperationSummary
|
||||
// Reason 实际提交的操作原因。
|
||||
Reason string `json:"reason"`
|
||||
// EvidenceRef 操作证据引用;非核查类操作可能为空。
|
||||
EvidenceRef string `json:"evidence_ref,omitempty"`
|
||||
// Before 操作前的受控状态摘要,不包含完整业务输入。
|
||||
Before json.RawMessage `json:"before"`
|
||||
// After 操作后摘要、核查声明或关联新激活和任务,不包含凭据秘密。
|
||||
After json.RawMessage `json:"after"`
|
||||
}
|
||||
|
||||
type OperationPage struct {
|
||||
// Items 按创建时间及审计ID倒序排列,不返回原因与摘要正文。
|
||||
Items []OperationSummary `json:"items"`
|
||||
// NextCursor 必须沿相同流程继续使用,空表示本次已无下一页。
|
||||
NextCursor string `json:"next_cursor,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Service) ListOperations(ctx context.Context, t auth.Token, id core.ID, in service.HistoryPageInput) (OperationPage, error) {
|
||||
if in.Limit == 0 {
|
||||
in.Limit = 20
|
||||
}
|
||||
if id.Validate() != nil || in.Limit < 1 || in.Limit > 100 || len(in.Cursor) > 1024 {
|
||||
return OperationPage{}, fault.Invalid("invalid workflow history query")
|
||||
}
|
||||
return s.store.ListOperations(ctx, t, id, in)
|
||||
}
|
||||
func (s *Service) GetOperation(ctx context.Context, t auth.Token, id, operation core.ID) (OperationView, error) {
|
||||
if id.Validate() != nil || operation.Validate() != nil {
|
||||
return OperationView{}, fault.Invalid("invalid workflow operation identity")
|
||||
}
|
||||
return s.store.GetOperation(ctx, t, id, operation)
|
||||
}
|
||||
@@ -57,13 +57,14 @@ type StartResult struct {
|
||||
|
||||
// StepView 只列元数据,避免把最多128个大输入输出聚合为无界响应。
|
||||
type StepView struct {
|
||||
ID core.ID `json:"id"` // ID 实例内逻辑步骤身份。
|
||||
Key string `json:"key"` // Key 固定定义步骤名称。
|
||||
Kind weave.NodeKind `json:"kind"` // Kind 固定节点类型。
|
||||
State weave.StepState `json:"state"` // State 当前步骤状态。
|
||||
Revision uint64 `json:"revision,string"` // Revision 步骤并发版本。
|
||||
ActivationID *core.ID `json:"activation_id,omitempty"` // ActivationID 当前激活,未激活时为空。
|
||||
TaskID *core.ID `json:"task_id,omitempty"` // TaskID TASK的直接任务;POLL任务通过轮次查询。
|
||||
ActivationSequence uint32 `json:"activation_sequence,omitempty"` // ActivationSequence 当前激活代次;未激活时省略,信号必须显式指定此代次。
|
||||
ID core.ID `json:"id"` // ID 实例内逻辑步骤身份。
|
||||
Key string `json:"key"` // Key 固定定义步骤名称。
|
||||
Kind weave.NodeKind `json:"kind"` // Kind 固定节点类型。
|
||||
State weave.StepState `json:"state"` // State 当前步骤状态。
|
||||
Revision uint64 `json:"revision,string"` // Revision 步骤并发版本。
|
||||
ActivationID *core.ID `json:"activation_id,omitempty"` // ActivationID 当前激活,未激活时为空。
|
||||
TaskID *core.ID `json:"task_id,omitempty"` // TaskID TASK的直接任务;POLL任务通过轮次查询。
|
||||
}
|
||||
type StepsView struct {
|
||||
InstanceID core.ID `json:"instance_id"` // InstanceID 所属实例。
|
||||
@@ -71,9 +72,12 @@ type StepsView struct {
|
||||
Steps []StepView `json:"steps"` // Steps 按键排序,最多128个,不含内部根检查点。
|
||||
}
|
||||
type Store interface {
|
||||
ListOperations(context.Context, auth.Token, core.ID, service.HistoryPageInput) (OperationPage, error)
|
||||
GetOperation(context.Context, auth.Token, core.ID, core.ID) (OperationView, error)
|
||||
Start(context.Context, auth.Token, string, StartInput) (StartResult, error)
|
||||
Get(context.Context, auth.Token, core.ID) (View, error)
|
||||
Steps(context.Context, auth.Token, core.ID) (StepsView, error)
|
||||
Step(context.Context, auth.Token, core.ID, string) (StepDetail, error)
|
||||
}
|
||||
type Service struct{ store Store }
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
core "git.yuntuo.hk/k2safe/orbit/model"
|
||||
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
||||
"git.yuntuo.hk/k2safe/orbit/pulse/service"
|
||||
)
|
||||
|
||||
type captureStore struct {
|
||||
@@ -28,6 +29,10 @@ func (s *captureStore) Steps(context.Context, auth.Token, core.ID) (StepsView, e
|
||||
s.calls++
|
||||
return StepsView{}, nil
|
||||
}
|
||||
func (s *captureStore) Step(context.Context, auth.Token, core.ID, string) (StepDetail, error) {
|
||||
s.calls++
|
||||
return StepDetail{}, nil
|
||||
}
|
||||
func TestAdmissionBoundaryPreservesExactInput(t *testing.T) {
|
||||
store := &captureStore{}
|
||||
api := New(store)
|
||||
@@ -57,3 +62,12 @@ func TestAdmissionBoundaryPreservesExactInput(t *testing.T) {
|
||||
t.Fatal("missing identity accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *captureStore) ListOperations(context.Context, auth.Token, core.ID, service.HistoryPageInput) (OperationPage, error) {
|
||||
s.calls++
|
||||
return OperationPage{}, nil
|
||||
}
|
||||
func (s *captureStore) GetOperation(context.Context, auth.Token, core.ID, core.ID) (OperationView, error) {
|
||||
s.calls++
|
||||
return OperationView{}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user