// 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) }