package mysql import ( "context" "database/sql" "encoding/hex" "encoding/json" "errors" "time" "unicode/utf8" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/weave/definition" "git.yuntuo.hk/k2safe/orbit/weave/signal" ) type workflowSignalWait struct { id core.ID sequence uint32 state, correlation string deadline, activated time.Time winner *core.ID contract *definition.Signal } // readWorkflowSignalWait 读取已经冻结的等待身份,不能把历史NULL元数据解释成有效信号契约。 func readWorkflowSignalWait(ctx context.Context, tx *sql.Tx, g *workflowGraph, row *workflowStep) (workflowSignalWait, error) { var w workflowSignalWait node, ok := g.program.Step(row.view.Key) w.contract = signalContract(node) if !ok || w.contract == nil || row.view.ActivationID == nil { return w, errors.New("signal step definition invalid") } var count int if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM weave_waits WHERE namespace_id=? AND workflow_id=? AND activation_id=?`, g.view.NamespaceID, g.view.ID, row.view.ActivationID).Scan(&count); err != nil { return w, err } if count != 1 { return w, errors.New("signal wait set invalid") } var name, version *string var kind string err := tx.QueryRowContext(ctx, `SELECT w.id,w.kind,w.state,w.signal_name,w.payload_version,w.correlation_key,w.deadline_at,w.winner_signal_id,a.sequence,a.created_at FROM weave_waits w JOIN weave_activations a ON a.namespace_id=w.namespace_id AND a.workflow_id=w.workflow_id AND a.id=w.activation_id AND a.step_id=? WHERE w.namespace_id=? AND w.workflow_id=? AND w.activation_id=? FOR UPDATE`, row.view.ID, g.view.NamespaceID, g.view.ID, row.view.ActivationID).Scan(&w.id, &kind, &w.state, &name, &version, &w.correlation, &w.deadline, &w.winner, &w.sequence, &w.activated) if err != nil { return w, err } if name == nil || version == nil { return w, workflowSourceError("SIGNAL_WAIT_UNVERIFIED") } if w.sequence == 0 || kind != string(row.view.Kind) || *name != w.contract.Name || *version != w.contract.PayloadVersion || !utf8.ValidString(w.correlation) || len(w.correlation) == 0 || len(w.correlation) > 512 || !w.deadline.Equal(minimumTime(g.view.DeadlineAt, w.activated.Add(time.Duration(w.contract.TimeoutMS)*time.Millisecond))) { return w, workflowSourceError("SIGNAL_WAIT_INVALID") } if node.Signal != nil { var input struct { Name string `json:"name"` Version string `json:"payload_version"` Correlation string `json:"correlation"` } if json.Unmarshal(row.input, &input) != nil || input.Name != *name || input.Version != *version || input.Correlation != w.correlation { return w, workflowSourceError("SIGNAL_ACTIVATION_INPUT_INVALID") } } return w, nil } // verifyWorkflowSignal 核对不可改写受理审计及请求摘要,防止可变状态读取替换原受理参数或时间。 func verifyWorkflowSignal(ctx context.Context, tx *sql.Tx, g *workflowGraph, row *workflowStep, w workflowSignalWait, v signal.View, now time.Time) error { if v.NamespaceID != g.view.NamespaceID || v.WorkflowID != g.view.ID || v.TargetStepKey != row.view.Key || v.TargetActivationSequence != w.sequence || v.CorrelationKey != w.correlation || v.Name != w.contract.Name || v.PayloadVersion != w.contract.PayloadVersion || v.AcceptedAt.After(now) || !v.AcceptedAt.Before(w.deadline) || !v.ExpiresAt.Equal(g.view.DeadlineAt) || definition.ValidateData(w.contract.PayloadType, v.Payload) != nil { return workflowSourceError("SIGNAL_IDENTITY_INVALID") } var digest core.Digest if err := tx.QueryRowContext(ctx, `SELECT request_digest FROM weave_signals WHERE namespace_id=? AND workflow_id=? AND id=?`, g.view.NamespaceID, g.view.ID, v.ID).Scan(&digest); err != nil { return err } var count int if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM orbit_audit_events WHERE namespace_id=? AND resource_kind='workflow_signal' AND resource_id=? AND action='workflow.signal'`, g.view.NamespaceID, v.ID).Scan(&count); err != nil { return err } if count != 1 { return workflowSourceError("SIGNAL_ACCEPTANCE_PROOF_MISSING") } var actor, reason string var evidence sql.NullString var raw []byte if err := tx.QueryRowContext(ctx, "SELECT actor_id,reason,evidence_ref,`after` FROM orbit_audit_events WHERE namespace_id=? AND resource_kind='workflow_signal' AND resource_id=? AND action='workflow.signal'", g.view.NamespaceID, v.ID).Scan(&actor, &reason, &evidence, &raw); err != nil { return err } var meta struct { SignalID core.ID `json:"signal_id"` WorkflowID core.ID `json:"workflow_id"` Key string `json:"target_step_key"` Sequence uint32 `json:"target_activation_sequence"` Digest string `json:"request_digest"` AcceptedAt time.Time `json:"accepted_at"` ExpiresAt time.Time `json:"expires_at"` } if json.Unmarshal(raw, &meta) != nil || meta.SignalID != v.ID || meta.WorkflowID != g.view.ID || meta.Key != row.view.Key || meta.Sequence != w.sequence || meta.Digest != hex.EncodeToString(digest[:]) || !meta.AcceptedAt.Equal(v.AcceptedAt) || !meta.ExpiresAt.Equal(v.ExpiresAt) || actor != v.ActorID || evidence.String != v.EvidenceRef { return workflowSourceError("SIGNAL_ACCEPTANCE_PROOF_INVALID") } expected, err := requestDigest(signal.Input{Name: v.Name, PayloadVersion: v.PayloadVersion, TargetStepKey: v.TargetStepKey, TargetActivationSequence: v.TargetActivationSequence, CorrelationKey: v.CorrelationKey, Payload: v.Payload, Reason: reason, EvidenceRef: v.EvidenceRef}) if err != nil { return err } if expected != digest { return workflowSourceError("SIGNAL_REQUEST_CHANGED") } return nil } // selectedWorkflowSignal 返回已胜出信号或最早按时受理的匹配信号;查询结果竞争由调用方在同一实例锁下裁决。 func selectedWorkflowSignal(ctx context.Context, tx *sql.Tx, g *workflowGraph, row *workflowStep, w workflowSignalWait, now time.Time) (*signal.View, error) { var id core.ID if w.winner != nil { if w.state != "SATISFIED" { return nil, workflowSourceError("SIGNAL_WINNER_WAIT_INVALID") } id = *w.winner } else { if w.state != "WAITING" { return nil, nil } err := tx.QueryRowContext(ctx, `SELECT id FROM weave_signals WHERE namespace_id=? AND workflow_id=? AND target_step_key=? AND target_activation_sequence=? AND state='PENDING' AND type=? AND payload_version=? AND correlation_key=? AND accepted_at