Files
orbit/weave/signal/service.go

95 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package signal 定义可靠外部信号的受理与查询;受理成功不等于已经推进流程。
package signal
import (
"context"
"encoding/json"
"strings"
"time"
"unicode"
"unicode/utf8"
"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"
"git.yuntuo.hk/k2safe/orbit/weave/definition"
weave "git.yuntuo.hk/k2safe/orbit/weave/model"
)
const SendAction = "workflows:signal"
const ReadAction = "workflows:read"
const MaxPerWorkflow = 1024 // MaxPerWorkflow 限制实例全部受理信号数,终结/拒绝记录也保留占用,原键恢复不受上限影响。
type Input struct {
Name string `json:"name"` // Name 固定定义信号名称,最多128个ASCII字符。
PayloadVersion string `json:"payload_version"` // PayloadVersion 固定内容契约版本,最多32个ASCII字符。
TargetStepKey string `json:"target_step_key"` // TargetStepKey 固定定义的目标步骤,最多64个ASCII字符。
TargetActivationSequence uint32 `json:"target_activation_sequence"` // TargetActivationSequence 显式目标激活代次,首次及早到信号为1,不能自动跟随人工恢复。
CorrelationKey string `json:"correlation_key"` // CorrelationKey 1至512字节精确UTF8操作关联,保留尾部空格。
Payload json.RawMessage `json:"payload" nullable:"true"` // Payload 固定契约内容,最多256KiB,显式null与缺失不同。
Reason string `json:"reason"` // Reason 受理原因,1至1024个Unicode字符,进入审计。
EvidenceRef string `json:"evidence_ref,omitempty"` // EvidenceRef 可选受控证据引用,最多512个无空白/控制字符,不携带秘密。
}
type View struct {
ID core.ID `json:"id"` // ID 信号持久身份。
NamespaceID core.ID `json:"namespace_id"` // NamespaceID 授权和数据隔离范围。
WorkflowID core.ID `json:"workflow_id"` // WorkflowID 固定目标实例。
Revision uint64 `json:"revision,string"` // Revision 当前信号记录版本。
Name string `json:"name"` // Name 固定信号类型。
PayloadVersion string `json:"payload_version"` // PayloadVersion 固定契约版本。
TargetStepKey string `json:"target_step_key"` // TargetStepKey 目标步骤名称。
TargetActivationSequence uint32 `json:"target_activation_sequence"` // TargetActivationSequence 固定激活序号。
CorrelationKey string `json:"correlation_key"` // CorrelationKey 原始操作关联。
Payload json.RawMessage `json:"payload"` // Payload 原始精确内容。
ActorID string `json:"actor_id"` // ActorID 平台认证主体,不接受请求自报身份。
EvidenceRef string `json:"evidence_ref,omitempty"` // EvidenceRef 原证据引用。
State weave.SignalState `json:"state"` // State PENDING/CONSUMED/EXPIRED/REJECTED。
Disposition string `json:"disposition,omitempty"` // Disposition 稳定消费或拒绝原因。
AcceptedAt time.Time `json:"accepted_at"` // AcceptedAt 数据库可靠受理时间,截止不含等号。
ExpiresAt time.Time `json:"expires_at"` // ExpiresAt 固定实例总截止,不因重报延长。
ConsumedWaitID *core.ID `json:"consumed_wait_id,omitempty"` // ConsumedWaitID 消费它的唯一等待身份。
}
type Result struct {
Signal View `json:"signal"` // Signal 首次受理的冻结视图,当前状态通过Get查询。
Deduplicated bool `json:"deduplicated"` // Deduplicated 原身份恢复,不重报新事件或重新计时。
}
type Store interface {
Send(context.Context, auth.Token, core.ID, string, Input) (Result, error)
Get(context.Context, auth.Token, core.ID, core.ID) (View, error)
}
type Service struct{ store Store }
func New(store Store) *Service { return &Service{store: store} }
func (s *Service) Send(ctx context.Context, t auth.Token, workflow core.ID, key string, in Input) (Result, error) {
if err := service.ValidateKey(key); err != nil {
return Result{}, err
}
if workflow.Validate() != nil || !validName(in.Name, 128) || !validName(in.PayloadVersion, 32) || !validName(in.TargetStepKey, 64) || in.TargetActivationSequence == 0 {
return Result{}, fault.Invalid("valid signal name, contract, step and activation sequence required")
}
if !utf8.ValidString(in.CorrelationKey) || len(in.CorrelationKey) == 0 || len(in.CorrelationKey) > 512 || strings.ContainsRune(in.CorrelationKey, 0) {
return Result{}, fault.Invalid("signal correlation must be 1 to 512 UTF-8 bytes")
}
if err := definition.ValidateData(definition.Type{Kind: "JSON"}, in.Payload); err != nil {
return Result{}, fault.Invalid("valid signal payload required, maximum 256 KiB")
}
if !utf8.ValidString(in.Reason) || strings.TrimSpace(in.Reason) == "" || utf8.RuneCountInString(in.Reason) > 1024 || strings.ContainsRune(in.Reason, 0) {
return Result{}, fault.Invalid("signal reason required, maximum 1024 characters")
}
if !utf8.ValidString(in.EvidenceRef) || utf8.RuneCountInString(in.EvidenceRef) > 512 || strings.IndexFunc(in.EvidenceRef, func(r rune) bool { return unicode.IsSpace(r) || unicode.IsControl(r) }) >= 0 {
return Result{}, fault.Invalid("invalid signal evidence reference")
}
return s.store.Send(ctx, t, workflow, key, in)
}
func validName(value string, max int) bool {
return service.ValidName(value, max) && !strings.ContainsRune(value, ':')
}
func (s *Service) Get(ctx context.Context, t auth.Token, workflow, id core.ID) (View, error) {
if workflow.Validate() != nil || id.Validate() != nil {
return View{}, fault.Invalid("invalid workflow or signal id")
}
return s.store.Get(ctx, t, workflow, id)
}