docs: consolidate runnable examples and publish v0.1.1 guides

This commit is contained in:
2026-09-24 21:27:25 +08:00
parent e29dec717f
commit 81e56bd633
124 changed files with 1430 additions and 281 deletions

View File

@@ -1,101 +0,0 @@
// application-resources 演示同一应用的两个命名空间共享积压额度。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func client(path string) (*orbit.Client, string, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, "", err
}
var v struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if err := json.Unmarshal(raw, &v); err != nil {
return nil, "", err
}
c, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: v.Token})
return c, v.QueueID, err
}
func run() (err error) {
a, qa, err := client(os.Getenv("ORBIT_CREDENTIALS_FILE"))
if err != nil {
return err
}
b, qb, err := client(os.Getenv("ORBIT_SECOND_CREDENTIALS_FILE"))
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
before, err := a.GetApplicationResources(ctx)
if err != nil {
return err
}
peer, err := b.GetApplicationResources(ctx)
if err != nil {
return err
}
if before.ApplicationID != peer.ApplicationID || qa == qb || before.UnfinishedTasks != 0 {
return errors.New("use two namespaces in the same idle, dedicated example application")
}
op, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
config, err := a.ConfigureApplicationResources(ctx, "config-"+op, orbit.ConfigureApplicationResourcesRequest{ExpectedRevision: before.Revision, Reason: "cross namespace resource example", MaxUnfinishedTasks: 1, MaxConcurrency: 2, RatePerSecond: 10})
if err != nil {
return err
}
defer func() {
cleanup, stop := context.WithTimeout(context.Background(), 8*time.Second)
defer stop()
_, restoreErr := a.ConfigureApplicationResources(cleanup, "restore-"+op, orbit.ConfigureApplicationResourcesRequest{ExpectedRevision: config.Resources.Revision, Reason: "restore example configuration", MaxUnfinishedTasks: before.MaxUnfinishedTasks, MaxConcurrency: before.MaxConcurrency, RatePerSecond: before.RatePerSecond})
err = errors.Join(err, restoreErr)
}()
input := orbit.SubmitTaskRequest{QueueID: qa, Handler: "object.prepare", HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"object_ref":"objects/example"}`), TaskTimeoutMS: 20000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000}}
first, err := a.SubmitTask(ctx, "first-"+op, input)
if err != nil {
return err
}
input.QueueID = qb
_, err = b.SubmitTask(ctx, "second-"+op, input)
var full *orbit.APIError
if !errors.As(err, &full) || full.Code != "APPLICATION_FULL" {
return fmt.Errorf("expected APPLICATION_FULL, got %v", err)
}
fmt.Printf("cross_namespace_backpressure=%s http=%d\n", full.Code, full.StatusCode)
if _, err := a.CancelTask(ctx, first.Task.ID, "cancel-first-"+op, orbit.CancelTaskRequest{ExpectedRevision: 1, Reason: "free shared slot"}); err != nil {
return err
}
second, err := b.SubmitTask(ctx, "second-"+op, input)
if err != nil {
return err
}
fmt.Printf("same_identity_retry_accepted=%t\n", second.Task.State == orbit.TaskWaiting)
if _, err := b.CancelTask(ctx, second.Task.ID, "cancel-second-"+op, orbit.CancelTaskRequest{ExpectedRevision: 1, Reason: "complete example"}); err != nil {
return err
}
after, err := a.GetApplicationResources(ctx)
if err != nil {
return err
}
fmt.Printf("unfinished=%d active=%d pending_starts=%d\n", after.UnfinishedTasks, after.ActiveAttempts, after.PendingStarts)
return nil
}

View File

@@ -1,122 +0,0 @@
// batch 演示批量部分受理、稳定身份核对和释放配额后仅补交失败项。
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token := os.Getenv("ORBIT_TOKEN")
if token == "" && os.Getenv("ORBIT_CREDENTIALS_FILE") != "" {
raw, err := os.ReadFile(os.Getenv("ORBIT_CREDENTIALS_FILE"))
if err != nil {
return fmt.Errorf("read credentials: %w", err)
}
var c struct {
Token string `json:"token"`
}
if json.Unmarshal(raw, &c) != nil {
return fmt.Errorf("invalid credentials file")
}
token = c.Token
}
api, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var random [16]byte
if _, err := rand.Read(random[:]); err != nil {
return err
}
key := "batch-example-" + hex.EncodeToString(random[:])
q, err := api.CreateQueue(ctx, key+"-queue", orbit.CreateQueueRequest{Name: key, MaxConcurrency: 2, RatePerSecond: 10, MaxUnfinishedTasks: 2})
if err != nil {
return err
}
if _, err := api.ConfigureQueue(ctx, q.Queue.ID, key+"-pause", orbit.ConfigureQueueRequest{ExpectedRevision: q.Queue.Revision, Reason: "isolated batch acceptance example", Paused: true, MaxConcurrency: 2, RatePerSecond: 10, MaxUnfinishedTasks: 2}); err != nil {
return err
}
request := orbit.SubmitTasksRequest{}
// 演示运行生成独立身份;生产必须先将每项键与完整参数持久化到业务库,再发送。
for i := 0; i < 3; i++ {
payload := json.RawMessage(fmt.Sprintf(`{"source_ref":"objects/input-%d.txt","sequence":9007199254740993}`, i))
request.Items = append(request.Items, orbit.SubmitTasksItem{IdempotencyKey: fmt.Sprintf("%s-%d", key, i), Task: orbit.SubmitTaskRequest{QueueID: q.Queue.ID, Handler: "file.convert", HandlerVersion: "v1", PayloadVersion: "v1", Payload: payload, TaskTimeoutMS: 60000}})
}
first, err := api.SubmitTasks(ctx, request)
if err != nil {
return err
}
if first.Items[0].Status != orbit.BatchAccepted || first.Items[1].Status != orbit.BatchAccepted || first.Items[2].Status != orbit.BatchRejected || first.Items[2].Error.Code != "QUEUE_FULL" {
return fmt.Errorf("unexpected partial acceptance")
}
fmt.Println("accepted=2 rejected=1 code=QUEUE_FULL")
replay, err := api.SubmitTasks(ctx, request)
if err != nil {
return err
}
for i := 0; i < 2; i++ {
if replay.Items[i].Status != orbit.BatchAccepted || !replay.Items[i].Receipt.Deduplicated || replay.Items[i].Receipt.TaskID != first.Items[i].Receipt.TaskID {
return fmt.Errorf("batch replay changed identity")
}
}
if replay.Items[2].Status != orbit.BatchRejected || replay.Items[2].Error.Code != "QUEUE_FULL" {
return fmt.Errorf("backpressure changed unexpectedly")
}
fmt.Println("replayed=2 same_task_ids=true")
cancelTask := func(id string) error {
current, err := api.GetTask(ctx, id)
if err != nil {
return err
}
out, err := api.CancelTask(ctx, id, key+"-cancel-"+id, orbit.CancelTaskRequest{ExpectedRevision: current.Revision, Reason: "finish batch example"})
if err != nil {
return err
}
if out.Task.State != orbit.TaskCancelled {
return fmt.Errorf("expected confirmed cancellation")
}
return nil
}
if err := cancelTask(first.Items[0].Receipt.TaskID); err != nil {
return err
}
// 子批次索引重新从0开始;键和参数仍来自最初被拒项,不重新生成。
retry, err := api.SubmitTasks(ctx, orbit.SubmitTasksRequest{Items: request.Items[2:]})
if err != nil {
return err
}
if retry.Items[0].Status != orbit.BatchAccepted || retry.Items[0].Receipt.Deduplicated {
return fmt.Errorf("failed item was not independently accepted")
}
fmt.Println("retried_items=1 same_identity=true accepted=true")
for _, id := range []string{first.Items[1].Receipt.TaskID, retry.Items[0].Receipt.TaskID} {
if err := cancelTask(id); err != nil {
return err
}
}
final, err := api.GetQueue(ctx, q.Queue.ID)
if err != nil {
return err
}
if final.UnfinishedTasks != 0 {
return fmt.Errorf("unfinished tasks remain")
}
fmt.Printf("queue=%s paused=%t unfinished=%d\n", final.ID, final.Paused, final.UnfinishedTasks)
return nil
}

View File

@@ -1,133 +0,0 @@
// cancellation 演示运行中取消、处理器确认停止、结果查询和有界排空。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var c struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &c) != nil {
return errors.New("invalid credentials file")
}
if token == "" {
token = c.Token
}
if queue == "" {
queue = c.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
operation, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
name := "object.cancel." + operation
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
defer cancel()
entered := make(chan struct{})
w, err := orbit.NewWorker(client, orbit.WorkerOptions{QueueID: queue, Capacity: 1, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{{Capability: orbit.WorkerCapability{Handler: name, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(work context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
close(entered)
<-work.Done()
if !errors.Is(context.Cause(work), orbit.ErrTaskCancellationRequested) {
return nil, work.Err()
}
// 此示例没有外部副作用,因此可以确认安全停止;真实接入需完成自己的核实。
return nil, &orbit.WorkerCancellationConfirmed{Details: json.RawMessage(`{"evidence_ref":"objects/no-external-work"}`)}
}}}})
if err != nil {
return err
}
task, err := client.SubmitTask(ctx, "submit-"+operation, orbit.SubmitTaskRequest{QueueID: queue, Handler: name, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"object_ref":"objects/example"}`), TaskTimeoutMS: 20000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 15000}})
if err != nil {
return err
}
run, stop := context.WithCancel(ctx)
done := make(chan error, 1)
go func() { done <- w.Run(run) }()
waited := false
defer func() {
stop()
if !waited {
<-done
}
}()
select {
case <-entered:
case err := <-done:
waited = true
return fmt.Errorf("Worker stopped before handler: %v", err)
case <-ctx.Done():
return ctx.Err()
}
for {
current, err := client.GetTask(ctx, task.Task.ID)
if err != nil {
return err
}
key, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
out, err := client.CancelTask(ctx, current.ID, key, orbit.CancelTaskRequest{ExpectedRevision: current.Revision, Reason: "finish cancellation example"})
if err == nil {
fmt.Printf("task=%s cancel_requested=%t accepted_state=%s\n", out.Task.ID, out.Task.CancelRequestedAt != nil, out.Task.State)
break
}
var api *orbit.APIError
if !errors.As(err, &api) || api.Code != "STATE_CONFLICT" {
return err
}
}
for {
current, err := client.GetTask(ctx, task.Task.ID)
if err != nil {
return err
}
if current.State == orbit.TaskCancelled {
fmt.Printf("final_state=%s attempts=%d\n", current.State, current.AttemptCount)
break
}
if current.State == orbit.TaskNeedsReview || current.State == orbit.TaskFailed || current.State == orbit.TaskSucceeded {
return fmt.Errorf("unexpected result: %s", current.State)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
stop()
err = <-done
waited = true
if err != nil {
return err
}
fmt.Printf("worker_drained=true running=%d\n", w.Stats().Running)
return nil
}

View File

@@ -1,129 +0,0 @@
// delayed-task 演示数据库计时的相对延迟、原键核对和实际 Worker 执行。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credentials) != nil {
return errors.New("invalid credentials file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
op, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
in := orbit.SubmitTaskRequest{QueueID: queue, Handler: "object.delayed." + op, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"object_ref":"examples/input","sequence":9007199254740993}`), DelayMS: 1500, TaskTimeoutMS: 15000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 5000}}
accepted, err := client.SubmitTask(ctx, op, in)
if err != nil {
return err
}
// 先输出任务身份,后续失败可据此核对;不输出凭据或任意业务正文。
fmt.Printf("task=%s delay_ms=%d\n", accepted.Task.ID, in.DelayMS)
if !accepted.Task.AvailableAt.Equal(accepted.Task.CreatedAt.Add(1500 * time.Millisecond)) {
return errors.New("delay was not based on database acceptance")
}
w, err := orbit.NewWorker(client, orbit.WorkerOptions{QueueID: queue, Capacity: 1, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{{Capability: orbit.WorkerCapability{Handler: in.Handler, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
// 无外部副作用的示例处理器,保持精确数字文本。
return e.Task.Payload, nil
}}}})
if err != nil {
return err
}
work, stop := context.WithCancel(ctx)
done := make(chan error, 1)
go func() { done <- w.Run(work) }()
waited := false
defer func() {
stop()
if !waited {
<-done
}
}()
for {
current, err := client.GetTask(ctx, accepted.Task.ID)
if err != nil {
return err
}
if current.State == orbit.TaskSucceeded {
history, err := client.ListTaskAttempts(ctx, current.ID, orbit.HistoryPageRequest{})
if err != nil {
return err
}
if len(history.Items) != 1 || history.Items[0].AuthorizedAt == nil || history.Items[0].CreatedAt.Before(accepted.Task.AvailableAt) || history.Items[0].AuthorizedAt.Before(accepted.Task.AvailableAt) {
return errors.New("execution preceded the saved release time")
}
break
}
if current.State == orbit.TaskFailed || current.State == orbit.TaskNeedsReview || current.State == orbit.TaskCancelled {
return fmt.Errorf("task requires attention: %s", current.State)
}
select {
case err := <-done:
waited = true
return fmt.Errorf("Worker stopped before completion: %v", err)
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
duplicate, err := client.SubmitTask(ctx, op, in)
if err != nil {
return err
}
if !duplicate.Deduplicated || duplicate.Task.ID != accepted.Task.ID || !duplicate.Task.AvailableAt.Equal(accepted.Task.AvailableAt) {
return errors.New("replay changed the original delayed task")
}
stop()
err = <-done
waited = true
if err != nil {
return err
}
stats := w.Stats()
if stats.Started != 1 || stats.ReportsConfirmed != 1 || stats.Running != 0 {
return errors.New("Worker did not confirm and drain")
}
fmt.Println("state=SUCCEEDED attempts=1 no_early_start=true original_receipt=true worker_drained=true")
return nil
}

View File

@@ -1,112 +0,0 @@
// event-protocol 验证冻结结果、连接切换与ACK重报;不执行业务写入。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return err
}
var credential struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credential) != nil {
return errors.New("invalid credential file")
}
if token == "" {
token = credential.Token
}
if queue == "" {
queue = credential.QueueID
}
}
if token == "" || queue == "" {
return errors.New("event-enabled token and isolated example queue are required")
}
api, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
ctx, stop := context.WithTimeout(context.Background(), 15*time.Second)
defer stop()
id, err := orbit.NewEventMessageID()
if err != nil {
return err
}
created, err := api.CreateSubscription(ctx, "sub-"+id, orbit.CreateSubscriptionRequest{Name: "protocol-" + id, QueueID: queue, Types: []string{"task.cancelled"}, MaxPending: 10, MaxAttempts: 3})
if err != nil {
return err
}
sub := created.Subscription
task, err := api.SubmitTask(ctx, "task-"+id, orbit.SubmitTaskRequest{QueueID: queue, Handler: "object.prepare", HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{}`)})
if err != nil {
return err
}
if _, err = api.CancelTask(ctx, task.Task.ID, "cancel-"+id, orbit.CancelTaskRequest{ExpectedRevision: task.Task.Revision, Reason: "event protocol example"}); err != nil {
return err
}
first, err := api.DialEvents(ctx, orbit.EventRegistration{SubscriptionID: sub.ID, Capacity: 1})
if err != nil {
return err
}
defer first.Close()
grant, err := first.Next(ctx)
if err != nil {
return err
}
if grant.Event.Snapshot.TaskID != task.Task.ID || grant.Event.Snapshot.State != orbit.TaskCancelled {
return errors.New("unexpected example queue event")
}
// 本例只读取并验证结果,没有业务数据库写入;业务处理必须先提交Inbox事务再ACK。
ackID, err := orbit.NewEventMessageID()
if err != nil {
return err
}
confirmed, err := first.Ack(ctx, ackID, grant.Proof())
if err != nil {
return err
}
first.Close()
second, err := api.DialEvents(ctx, orbit.EventRegistration{SubscriptionID: sub.ID, Capacity: 1})
if err != nil {
return err
}
defer second.Close()
replayID, err := orbit.NewEventMessageID()
if err != nil {
return err
}
replayed, err := second.Ack(ctx, replayID, grant.Proof())
if err != nil {
return err
}
if !replayed.Deduplicated || !replayed.AcknowledgedAt.Equal(confirmed.AcknowledgedAt) {
return errors.New("acknowledgement receipt changed across connections")
}
paused, err := api.ConfigureSubscription(ctx, sub.ID, "pause-"+id, orbit.ConfigureSubscriptionRequest{ExpectedRevision: sub.Revision, Enabled: false, Reason: "protocol example completed"})
if err != nil {
return err
}
fmt.Printf("event=%s task=%s state=%s\n", grant.Event.ID, task.Task.ID, grant.Event.Snapshot.State)
fmt.Printf("ack_replayed=%t same_receipt=true pending=%d subscription_enabled=%t\n", replayed.Deduplicated, paused.Subscription.PendingCount, paused.Subscription.Enabled)
return nil
}

View File

@@ -1,218 +0,0 @@
// file-conversion 演示一个通用的文件格式转换任务:CSV 内容由 Worker 转为 JSON。
// 示例把小文件内容放进任务快照;生产大文件应传对象引用,并在业务侧保存稳定操作键。
package main
import (
"context"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
type conversionInput struct {
// FileName 是输入文件名,仅用于结果追踪,不参与路径访问。
FileName string `json:"file_name"`
// Content 是示例中的小型文件正文;生产场景应替换为受控对象引用。
Content string `json:"content"`
// TargetFormat 固定为 json,演示处理器版本化能力。
TargetFormat string `json:"target_format"`
}
type conversionOutput struct {
// FileName 是转换后文件名。
FileName string `json:"file_name"`
// Format 是输出格式。
Format string `json:"format"`
// Content 是转换后的 JSON 文件正文。
Content string `json:"content"`
// Rows 是转换的记录数。
Rows int `json:"rows"`
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if err := json.Unmarshal(raw, &credentials); err != nil {
return errors.New("invalid credentials file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
runID, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
handler := "file.convert.csv-json." + runID
worker, err := orbit.NewWorker(client, orbit.WorkerOptions{
QueueID: queue,
Capacity: 1,
ShutdownTimeout: 5 * time.Second,
Handlers: []orbit.WorkerHandler{{
Capability: orbit.WorkerCapability{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"},
Handle: convertCSV,
}},
})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
payload, err := json.Marshal(conversionInput{
FileName: "records.csv",
Content: "name,amount\nalpha,2\nbeta,3\n",
TargetFormat: "json",
})
if err != nil {
return err
}
key := "file-conversion-" + runID
accepted, err := client.SubmitTask(ctx, key, orbit.SubmitTaskRequest{
QueueID: queue,
Handler: handler,
HandlerVersion: "v1",
PayloadVersion: "v1",
Payload: payload,
TaskTimeoutMS: 60000,
Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000},
RecoverySafety: orbit.RecoveryReconcileOnly,
})
if err != nil {
return err
}
workerCtx, stop := context.WithCancel(ctx)
done := make(chan error, 1)
go func() { done <- worker.Run(workerCtx) }()
waited := false
defer func() {
stop()
if !waited {
<-done
}
}()
for {
current, err := client.GetTask(ctx, accepted.Task.ID)
if err != nil {
return err
}
switch current.State {
case orbit.TaskSucceeded:
var output conversionOutput
if err := json.Unmarshal(current.Result, &output); err != nil {
return fmt.Errorf("decode conversion result: %w", err)
}
if output.FileName != "records.json" || output.Format != "json" || output.Rows != 2 || output.Content != `[{"amount":"2","name":"alpha"},{"amount":"3","name":"beta"}]` {
return fmt.Errorf("unexpected conversion result: %+v", output)
}
stop()
err = <-done
waited = true
if err != nil {
return err
}
stats := worker.Stats()
if stats.Started != 1 || stats.ReportsConfirmed != 1 || stats.Running != 0 {
return fmt.Errorf("unexpected Worker completion counts: %+v", stats)
}
fmt.Printf("input=%s output=%s rows=%d state=%s worker_drained=true\n", "records.csv", output.FileName, output.Rows, current.State)
return nil
case orbit.TaskFailed, orbit.TaskNeedsReview:
return fmt.Errorf("conversion task requires attention: %s", current.State)
}
select {
case err := <-done:
waited = true
return fmt.Errorf("Worker stopped before conversion completed: %v", err)
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
}
func convertCSV(ctx context.Context, execution orbit.WorkerExecution) (json.RawMessage, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
var in conversionInput
if err := json.Unmarshal(execution.Task.Payload, &in); err != nil {
return nil, &orbit.WorkerFailure{Code: "INVALID_FILE"}
}
out, err := convert(in)
if err != nil {
return nil, &orbit.WorkerFailure{Code: "INVALID_FILE"}
}
return json.Marshal(out)
}
func convert(in conversionInput) (conversionOutput, error) {
var out conversionOutput
if strings.TrimSpace(in.FileName) == "" || in.TargetFormat != "json" || in.Content == "" {
return out, errors.New("invalid conversion input")
}
reader := csv.NewReader(strings.NewReader(in.Content))
header, err := reader.Read()
if err != nil || len(header) == 0 {
return out, errors.New("CSV header is required")
}
seen := make(map[string]struct{}, len(header))
for i := range header {
header[i] = strings.TrimSpace(header[i])
if header[i] == "" {
return out, errors.New("CSV header is empty")
}
if _, ok := seen[header[i]]; ok {
return out, errors.New("CSV header is duplicated")
}
seen[header[i]] = struct{}{}
}
rows := make([]map[string]string, 0, 16)
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil || len(record) != len(header) {
return out, errors.New("CSV row does not match header")
}
row := make(map[string]string, len(header))
for i, value := range record {
row[header[i]] = value
}
rows = append(rows, row)
}
encoded, err := json.Marshal(rows)
if err != nil {
return out, err
}
return conversionOutput{FileName: strings.TrimSuffix(in.FileName, ".csv") + ".json", Format: "json", Content: string(encoded), Rows: len(rows)}, nil
}

View File

@@ -1,25 +0,0 @@
package main
import "testing"
func TestConvertCSV(t *testing.T) {
out, err := convert(conversionInput{FileName: "records.csv", Content: "name,amount\nalpha,2\nbeta,3\n", TargetFormat: "json"})
if err != nil {
t.Fatal(err)
}
if out.FileName != "records.json" || out.Format != "json" || out.Rows != 2 || out.Content != `[{"amount":"2","name":"alpha"},{"amount":"3","name":"beta"}]` {
t.Fatalf("unexpected output: %+v", out)
}
}
func TestConvertRejectsMalformedCSV(t *testing.T) {
for _, in := range []conversionInput{
{FileName: "records.csv", Content: "name,amount\nalpha\n", TargetFormat: "json"},
{FileName: "records.csv", Content: "name,name\nalpha,beta\n", TargetFormat: "json"},
{FileName: "records.csv", Content: "name,amount\nalpha,2\n", TargetFormat: "xml"},
} {
if _, err := convert(in); err == nil {
t.Fatalf("malformed input accepted: %+v", in)
}
}
}

View File

@@ -1,37 +0,0 @@
// Run against a started bootstrap server: ORBIT_URL=http://127.0.0.1:8080 go run ./examples/health
package main
import (
"context"
"fmt"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
"log"
"os"
"time"
)
func main() {
address := os.Getenv("ORBIT_URL")
if address == "" {
address = "http://127.0.0.1:8080"
}
client, err := orbit.NewClient(address, orbit.Options{})
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
live, err := client.Liveness(ctx)
if err != nil {
log.Fatal(err)
}
ready, err := client.Readiness(ctx)
if err != nil {
log.Fatal(err)
}
version, err := client.Version(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("liveness=%s readiness=%s scope=%s version=%s revision=%s\n", live.Status, ready.Status, ready.Scope, version.Version, version.Revision)
}

View File

@@ -1,151 +0,0 @@
// history 创建一次受控的冲突报告,演示通过公开查询区分权威结果和追加证据。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var c struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &c) != nil {
return errors.New("invalid credentials file")
}
if token == "" {
token = c.Token
}
if queue == "" {
queue = c.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
op, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
handler := "history.inspect." + op
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
accepted, err := client.SubmitTask(ctx, "history-"+op, orbit.SubmitTaskRequest{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"object_ref":"examples/input"}`), TaskTimeoutMS: 60000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000}})
if err != nil {
return err
}
registration, err := orbit.NewWorkerRegistration(queue, 1, []orbit.WorkerCapability{{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"}})
if err != nil {
return err
}
w, err := client.DialWorker(ctx, registration)
if err != nil {
return err
}
defer w.Close()
nextID := func() (string, error) { return orbit.NewWorkerMessageID() }
id, err := nextID()
if err != nil {
return err
}
if _, err := w.Capacity(ctx, id, orbit.WorkerCapacity{Sequence: 1, Capacity: 1}); err != nil {
return err
}
a, err := w.NextAssignment(ctx)
if err != nil {
return err
}
if a.Task.ID != accepted.Task.ID {
return errors.New("unexpected task")
}
id, err = nextID()
if err != nil {
return err
}
started, err := w.Start(ctx, id, a.Attempt())
if err != nil {
return err
}
if _, active := started.Lease.ExecutionDeadline(); !active {
return errors.New("missing current execution authority")
}
// 这是故障演示,不对外部系统执行操作;先提交固定的模拟处理结果。
id, err = nextID()
if err != nil {
return err
}
original := json.RawMessage(`{"version":"v1","sequence":9007199254740993}`)
if _, err := w.Finish(ctx, id, orbit.WorkerFinish{Ref: a.Attempt(), Outcome: orbit.WorkerSucceeded, Result: original}); err != nil {
return err
}
// 使用另一消息身份模拟矛盾回报;只能追加证据,不能覆盖先前权威结果。
id, err = nextID()
if err != nil {
return err
}
conflict, err := w.Finish(ctx, id, orbit.WorkerFinish{Ref: a.Attempt(), Outcome: orbit.WorkerSucceeded, Result: json.RawMessage(`{"version":"v2"}`)})
if err != nil {
return err
}
if conflict.Receipt.Disposition != "EVIDENCE_ONLY" {
return errors.New("conflicting report was not isolated as evidence")
}
attempts, err := client.ListTaskAttempts(ctx, a.Task.ID, orbit.HistoryPageRequest{Limit: 20})
if err != nil {
return err
}
if len(attempts.Items) != 1 {
return errors.New("expected one attempt")
}
detail, err := client.GetTaskAttempt(ctx, a.Task.ID, attempts.Items[0].ID)
if err != nil {
return err
}
evidence, err := client.ListAttemptEvidence(ctx, a.Task.ID, detail.ID, orbit.HistoryPageRequest{Limit: 20})
if err != nil {
return err
}
if len(evidence.Items) != 1 {
return errors.New("expected one evidence record")
}
item, err := client.GetAttemptEvidence(ctx, a.Task.ID, detail.ID, evidence.Items[0].ID)
if err != nil {
return err
}
current, err := client.GetTask(ctx, a.Task.ID)
if err != nil {
return err
}
if current.State != orbit.TaskSucceeded || string(current.Result) != string(original) || item.Reason != "RESULT_CONFLICT" {
return errors.New("history did not preserve authoritative outcome")
}
id, err = nextID()
if err != nil {
return err
}
if _, err := w.Capacity(ctx, id, orbit.WorkerCapacity{Sequence: 2, Capacity: 0, Drain: true}); err != nil {
return err
}
fmt.Printf("task=%s state=%s attempts=%d evidence=%d reason=%s disposition=%s\n", current.ID, current.State, len(attempts.Items), len(evidence.Items), item.Reason, item.Disposition)
return nil
}

View File

@@ -1,119 +0,0 @@
// Package flowdemo 仅为可运行流程示例提供初始化和有界等待,不属于公开SDK或业务恢复策略。
package flowdemo
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func Client() (*orbit.Client, string, string, error) {
raw, err := os.ReadFile(os.Getenv("ORBIT_CREDENTIALS_FILE"))
if err != nil {
return nil, "", "", err
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credentials) != nil || credentials.Token == "" || credentials.QueueID == "" {
return nil, "", "", errors.New("invalid private credentials file")
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: credentials.Token})
if err != nil {
return nil, "", "", err
}
operation, err := orbit.NewWorkerMessageID()
return client, credentials.QueueID, operation, err
}
// PublishStart 每次示例运行创建独立身份;不将重新运行命令当作未知响应的原键恢复。
func PublishStart(ctx context.Context, api *orbit.Client, operation, name string, spec orbit.WorkflowSpecification, input json.RawMessage) (orbit.WorkflowInstance, error) {
var empty orbit.WorkflowInstance
fmt.Printf("scenario=%s operation=%s\n", name, operation)
created, err := api.CreateWorkflowDefinition(ctx, operation, orbit.CreateWorkflowDefinitionRequest{Name: name + "." + operation, Description: "controlled standalone workflow example"})
if err != nil {
return empty, err
}
if _, err = api.PublishWorkflowVersion(ctx, created.Definition.ID, operation, orbit.PublishWorkflowVersionRequest{Version: "v1", Specification: spec, Reason: "publish controlled SDK example"}); err != nil {
return empty, err
}
result, err := api.StartWorkflow(ctx, operation, orbit.StartWorkflowRequest{DefinitionID: created.Definition.ID, Version: "v1", BusinessKey: operation, Input: input})
if err != nil {
return empty, err
}
fmt.Printf("workflow=%s version=%s\n", result.Instance.ID, result.Instance.Version)
return result.Instance, nil
}
// Runner 持有一个真实Worker及其结束通知,等待时同时检测Worker异常退出。
type Runner struct {
Worker *orbit.Worker
stop context.CancelFunc
done chan struct{}
err error // err 仅在done关闭后读取,关闭通道建立同步关系。
}
func StartWorker(ctx context.Context, api *orbit.Client, queue string, handlers ...orbit.WorkerHandler) (*Runner, error) {
w, err := orbit.NewWorker(api, orbit.WorkerOptions{QueueID: queue, Capacity: 1, ShutdownTimeout: 5 * time.Second, Handlers: handlers})
if err != nil {
return nil, err
}
workerCtx, stop := context.WithCancel(ctx)
r := &Runner{Worker: w, stop: stop, done: make(chan struct{})}
go func() { r.err = w.Run(workerCtx); close(r.done) }()
return r, nil
}
func (r *Runner) Close() error {
r.stop()
<-r.done
return r.err
}
func (r *Runner) Wait(ctx context.Context, check func() (bool, error)) error {
for {
ready, err := check()
if err != nil || ready {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case <-r.done:
return fmt.Errorf("example Worker stopped before expected state: %v", r.err)
case <-time.After(100 * time.Millisecond):
}
}
}
func (r *Runner) Step(ctx context.Context, api *orbit.Client, workflow, key string, predicate func(orbit.WorkflowStepDetail) bool) (orbit.WorkflowStepDetail, error) {
var out orbit.WorkflowStepDetail
err := r.Wait(ctx, func() (bool, error) {
var err error
out, err = api.GetWorkflowStep(ctx, workflow, key)
return err == nil && predicate(out), err
})
return out, err
}
func (r *Runner) Workflow(ctx context.Context, api *orbit.Client, workflow, state string) (orbit.WorkflowInstance, error) {
var out orbit.WorkflowInstance
err := r.Wait(ctx, func() (bool, error) {
var err error
out, err = api.GetWorkflow(ctx, workflow)
if err != nil || out.State == state {
return err == nil, err
}
if out.State == "NEEDS_REVIEW" || out.State == "FAILED" || out.State == "CANCELLED" || out.State == "SUCCEEDED" {
return false, fmt.Errorf("unexpected workflow state %s; wanted %s", out.State, state)
}
return false, nil
})
return out, err
}

View File

@@ -1,169 +0,0 @@
// keyed-concurrency 演示跨队列资源互斥及资源端单调序号校验。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"sync"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
var errStale = errors.New("stale resource token")
// resource 仅用于本地示范。生产必须在资源存储事务内持久化最高序号和操作身份,
// 并与实际写入原子提交;Go mutex 无法保护其他进程或外部服务。
type resource struct {
mu sync.Mutex
namespace, key string
highest uint64
operations map[string]bool
}
func (r *resource) apply(namespace, key string, token uint64, operation string) error {
r.mu.Lock()
defer r.mu.Unlock()
if namespace != r.namespace || key != r.key || token == 0 || token < r.highest {
return errStale
}
r.highest = token
// 同一操作的重报不产生第二次模拟写入。
r.operations[operation] = true
return nil
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token := os.Getenv("ORBIT_TOKEN")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" && token == "" {
raw, err := os.ReadFile(path)
if err != nil {
return err
}
var c struct {
Token string `json:"token"`
}
if err = json.Unmarshal(raw, &c); err != nil {
return err
}
token = c.Token
}
api, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
defer cancel()
op, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
name := "keyed." + op
var queues []string
var tasks []orbit.Task
for i := 0; i < 2; i++ {
q, err := api.CreateQueue(ctx, fmt.Sprintf("queue-%s-%d", op, i), orbit.CreateQueueRequest{Name: fmt.Sprintf("%s.%d", name, i), MaxConcurrency: 4, RatePerSecond: 100})
if err != nil {
return err
}
queues = append(queues, q.Queue.ID)
in := orbit.SubmitTaskRequest{QueueID: q.Queue.ID, Handler: name, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"object_ref":"objects/shared"}`), ConcurrencyKey: "object/" + op, TaskTimeoutMS: 20000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000}}
out, err := api.SubmitTask(ctx, fmt.Sprintf("operation-%s-%d", op, i), in)
if err != nil {
return err
}
tasks = append(tasks, out.Task)
}
r := &resource{namespace: tasks[0].NamespaceID, key: tasks[0].ConcurrencyKey, operations: map[string]bool{}}
handler := orbit.WorkerHandler{Capability: orbit.WorkerCapability{Handler: name, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
// 本示例每条任务表示一个独立操作,Task.ID 在安全重试期间保持不变。
if err := r.apply(e.Task.NamespaceID, e.Task.ConcurrencyKey, e.ConcurrencyToken, e.Task.ID); err != nil {
return nil, &orbit.WorkerFailure{Code: "STALE_RESOURCE_TOKEN"}
}
return json.RawMessage(`{"updated":true}`), nil
}}
run, stop := context.WithCancel(ctx)
done := make(chan error, 2)
started := 0
defer func() {
stop()
for i := 0; i < started; i++ {
<-done
}
}()
for _, queue := range queues {
w, err := orbit.NewWorker(api, orbit.WorkerOptions{QueueID: queue, Capacity: 4, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{handler}})
if err != nil {
return err
}
started++
go func() { done <- w.Run(run) }()
}
var low, high uint64
for _, task := range tasks {
for {
current, err := api.GetTask(ctx, task.ID)
if err != nil {
return err
}
if current.State == orbit.TaskSucceeded {
break
}
if current.State == orbit.TaskFailed || current.State == orbit.TaskCancelled || current.State == orbit.TaskNeedsReview {
return fmt.Errorf("unexpected task state: %s", current.State)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(80 * time.Millisecond):
}
}
history, err := api.ListTaskAttempts(ctx, task.ID, orbit.HistoryPageRequest{})
if err != nil {
return err
}
if len(history.Items) != 1 || history.Items[0].ConcurrencyToken == 0 {
return errors.New("missing resource token")
}
v := history.Items[0].ConcurrencyToken
if low == 0 || v < low {
low = v
}
if v > high {
high = v
}
}
if low == high {
return errors.New("resource token reused")
}
if !errors.Is(r.apply(r.namespace, r.key, low, "late-write"), errStale) {
return errors.New("old resource token accepted")
}
r.mu.Lock()
count := len(r.operations)
r.mu.Unlock()
if count != 2 {
return errors.New("unexpected resource writes")
}
stop()
for started > 0 {
err := <-done
started--
if err != nil {
return err
}
}
fmt.Printf("queues=2 completed=2 tokens_increasing=true\nresource_writes=%d stale_write_rejected=true worker_drained=true\n", count)
return nil
}

View File

@@ -1,116 +0,0 @@
// priority 演示近同时到期任务的优先级顺序,真实受理与执行由 Orbit 完成。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token := os.Getenv("ORBIT_TOKEN")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" && token == "" {
raw, err := os.ReadFile(path)
if err != nil {
return err
}
var c struct {
Token string `json:"token"`
}
if err = json.Unmarshal(raw, &c); err != nil {
return err
}
token = c.Token
}
api, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
defer cancel()
op, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
name := "priority." + op
created, err := api.CreateQueue(ctx, "queue-"+op, orbit.CreateQueueRequest{Name: name, MaxConcurrency: 1, RatePerSecond: 100, MaxUnfinishedTasks: 3})
if err != nil {
return err
}
var tasks []orbit.Task
for _, priority := range []int16{0, 9, 4} {
in := orbit.SubmitTaskRequest{QueueID: created.Queue.ID, Handler: name, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"object_ref":"objects/example","n":9007199254740993}`), Priority: priority, TaskTimeoutMS: 20000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000}}
out, err := api.SubmitTask(ctx, fmt.Sprintf("task-%s-%d", op, priority), in)
if err != nil {
return err
}
tasks = append(tasks, out.Task)
}
order := make(chan int16, 3)
w, err := orbit.NewWorker(api, orbit.WorkerOptions{QueueID: created.Queue.ID, Capacity: 1, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{{Capability: orbit.WorkerCapability{Handler: name, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
order <- e.Task.Priority
return e.Task.Payload, nil
}}}})
if err != nil {
return err
}
run, stop := context.WithCancel(ctx)
done := make(chan error, 1)
go func() { done <- w.Run(run) }()
waited := false
defer func() {
stop()
if !waited {
<-done
}
}()
for _, task := range tasks {
for {
v, err := api.GetTask(ctx, task.ID)
if err != nil {
return err
}
if v.State == orbit.TaskSucceeded {
if v.Priority != task.Priority || v.AttemptCount != 1 {
return errors.New("frozen priority or execution count changed")
}
break
}
if v.State == orbit.TaskFailed || v.State == orbit.TaskNeedsReview || v.State == orbit.TaskCancelled {
return fmt.Errorf("unexpected task state: %s", v.State)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(80 * time.Millisecond):
}
}
}
got := []int16{<-order, <-order, <-order}
if got[0] != 9 || got[1] != 4 || got[2] != 0 {
return fmt.Errorf("unexpected dispatch order: %v", got)
}
stop()
err = <-done
waited = true
if err != nil {
return err
}
fmt.Println("submitted_priorities=0,9,4 executed_priorities=9,4,0")
fmt.Println("completed=3 attempts_per_task=1 worker_drained=true")
return nil
}

View File

@@ -1,190 +0,0 @@
// recovery 演示未知结果、人工核查、关联新任务和不可变审计;只处理本地模拟输入。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var c struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &c) != nil {
return errors.New("invalid credentials file")
}
if token == "" {
token = c.Token
}
if queue == "" {
queue = c.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
op, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
handler := "recovery.inspect." + op
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
accepted, err := client.SubmitTask(ctx, "recovery-"+op, orbit.SubmitTaskRequest{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"object_ref":"examples/input"}`), TaskTimeoutMS: 60000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000}})
if err != nil {
return err
}
registration, err := orbit.NewWorkerRegistration(queue, 1, []orbit.WorkerCapability{{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"}})
if err != nil {
return err
}
w, err := client.DialWorker(ctx, registration)
if err != nil {
return err
}
defer w.Close()
nextID := func() (string, error) { return orbit.NewWorkerMessageID() }
id, err := nextID()
if err != nil {
return err
}
if _, err := w.Capacity(ctx, id, orbit.WorkerCapacity{Sequence: 1, Capacity: 1}); err != nil {
return err
}
a, err := w.NextAssignment(ctx)
if err != nil {
return err
}
if a.Task.ID != accepted.Task.ID {
return errors.New("unexpected task")
}
id, err = nextID()
if err != nil {
return err
}
started, err := w.Start(ctx, id, a.Attempt())
if err != nil {
return err
}
if _, active := started.Lease.ExecutionDeadline(); !active {
return errors.New("missing current execution authority")
}
// 本例不操作外部系统:模拟“旧执行已结束,但最初无法判断结果”的场景。
id, err = nextID()
if err != nil {
return err
}
if _, err := w.Finish(ctx, id, orbit.WorkerFinish{Ref: a.Attempt(), Outcome: orbit.WorkerUnknown, Result: json.RawMessage(`{"status":"unknown"}`), ErrorCode: "EXAMPLE_UNCERTAIN"}); err != nil {
return err
}
current, err := client.GetTask(ctx, a.Task.ID)
if err != nil {
return err
}
if current.State != orbit.TaskNeedsReview {
return errors.New("expected manual review")
}
// 生产中先核查旧执行和外部效果,保存真实证据,再由专门管理凭据提交此结论。
// 每次操作将幂等键与完整请求持久化;响应丢失时保持原键、版本、结论与证据重报。
reviewKey := "review-" + op
reviewInput := orbit.ReviewTaskRequest{ExpectedRevision: current.Revision, Reason: "模拟旧执行已结束,确认操作未完成", EvidenceRef: "evidence:example-reconciliation", ExecutionSettled: true, Outcome: orbit.TaskFailed, Result: json.RawMessage(`{"confirmed":true,"sequence":9007199254740993}`)}
reviewed, err := client.ReviewTask(ctx, current.ID, reviewKey, reviewInput)
if err != nil {
return err
}
replay, err := client.ReviewTask(ctx, current.ID, reviewKey, reviewInput)
if err != nil {
return err
}
if !replay.Deduplicated || replay.AuditID != reviewed.AuditID {
return errors.New("review identity changed")
}
// 人工重试保留原任务,另建同业务身份的新任务,并显式给出新的总时间预算。
retryKey := "retry-" + op
retryInput := orbit.RetryTaskRequest{ExpectedRevision: reviewed.Task.Revision, Reason: "已核实模拟任务可安全重新执行", EvidenceRef: "evidence:example-safe-retry", SafeToRetry: true, TaskTimeoutMS: 60000}
retried, err := client.RetryTask(ctx, current.ID, retryKey, retryInput)
if err != nil {
return err
}
a, err = w.NextAssignment(ctx)
if err != nil {
return err
}
if a.Task.ID != retried.Task.ID || a.Task.RetryOf == nil || *a.Task.RetryOf != current.ID {
return errors.New("missing retry lineage")
}
id, err = nextID()
if err != nil {
return err
}
started, err = w.Start(ctx, id, a.Attempt())
if err != nil {
return err
}
if _, active := started.Lease.ExecutionDeadline(); !active {
return errors.New("missing retry execution authority")
}
id, err = nextID()
if err != nil {
return err
}
if _, err := w.Finish(ctx, id, orbit.WorkerFinish{Ref: a.Attempt(), Outcome: orbit.WorkerSucceeded, Result: json.RawMessage(`{"object_ref":"examples/output"}`)}); err != nil {
return err
}
audit, err := client.GetTaskOperation(ctx, current.ID, retried.AuditID)
if err != nil {
return err
}
page, err := client.ListTaskOperations(ctx, current.ID, orbit.HistoryPageRequest{Limit: 20})
if err != nil {
return err
}
original, err := client.GetTask(ctx, current.ID)
if err != nil {
return err
}
child, err := client.GetTask(ctx, retried.Task.ID)
if err != nil {
return err
}
if original.State != orbit.TaskFailed || child.State != orbit.TaskSucceeded || audit.Action != "task.retry" {
return errors.New("unexpected recovery result")
}
id, err = nextID()
if err != nil {
return err
}
if _, err := w.Capacity(ctx, id, orbit.WorkerCapacity{Sequence: 2, Drain: true}); err != nil {
return err
}
lineage, err := client.GetTaskSource(ctx, child.ID)
if err != nil {
return err
}
if lineage.Kind != "MANUAL_RETRY" || lineage.RetryOf == nil || *lineage.RetryOf != original.ID {
return errors.New("manual retry source mismatch")
}
fmt.Printf("source_kind=%s parent=%s\n", lineage.Kind, *lineage.RetryOf)
fmt.Printf("source=%s state=%s retry=%s state=%s audit=%s operations=%d\n", original.ID, original.State, child.ID, child.State, audit.ID, len(page.Items))
return nil
}

View File

@@ -1,150 +0,0 @@
// resources 演示队列暂停、积压背压、幂等核对、释放空间与限速执行。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token := os.Getenv("ORBIT_TOKEN")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" && token == "" {
raw, err := os.ReadFile(path)
if err != nil {
return err
}
var c struct {
Token string `json:"token"`
}
if err := json.Unmarshal(raw, &c); err != nil {
return err
}
token = c.Token
}
api, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
defer cancel()
op, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
name := "resources." + op
created, err := api.CreateQueue(ctx, "queue-"+op, orbit.CreateQueueRequest{Name: name, MaxConcurrency: 2, RatePerSecond: 1, MaxUnfinishedTasks: 2})
if err != nil {
return err
}
q := created.Queue
configure := func(paused bool) error {
out, err := api.ConfigureQueue(ctx, q.ID, fmt.Sprintf("configure-%s-%t", op, paused), orbit.ConfigureQueueRequest{ExpectedRevision: q.Revision, Reason: "resource control example", Paused: paused, MaxConcurrency: 2, RatePerSecond: 1, MaxUnfinishedTasks: 2})
if err == nil {
q = out.Queue
}
return err
}
if err := configure(true); err != nil {
return err
}
in := orbit.SubmitTaskRequest{QueueID: q.ID, Handler: name, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"object_ref":"objects/example","n":9007199254740993}`), TaskTimeoutMS: 20000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000}}
first, err := api.SubmitTask(ctx, "first-"+op, in)
if err != nil {
return err
}
second, err := api.SubmitTask(ctx, "second-"+op, in)
if err != nil {
return err
}
_, err = api.SubmitTask(ctx, "third-"+op, in)
var rejected *orbit.APIError
if !errors.As(err, &rejected) || rejected.Code != "QUEUE_FULL" {
return fmt.Errorf("expected QUEUE_FULL, got %v", err)
}
fmt.Printf("backpressure=%s http=%d\n", rejected.Code, rejected.StatusCode)
duplicate, err := api.SubmitTask(ctx, "first-"+op, in)
if err != nil {
return err
}
if !duplicate.Deduplicated || duplicate.Task.ID != first.Task.ID {
return errors.New("original receipt was not preserved")
}
if _, err := api.CancelTask(ctx, first.Task.ID, "cancel-"+op, orbit.CancelTaskRequest{ExpectedRevision: first.Task.Revision, Reason: "free one unfinished slot"}); err != nil {
return err
}
// 明确 429 表示未受理;释放空间后仍使用第三个任务的原幂等键与原内容。
third, err := api.SubmitTask(ctx, "third-"+op, in)
if err != nil {
return err
}
fmt.Println("duplicate=true rejected_request_retried=true")
w, err := orbit.NewWorker(api, orbit.WorkerOptions{QueueID: q.ID, Capacity: 2, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{{Capability: orbit.WorkerCapability{Handler: name, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
return e.Task.Payload, nil
}}}})
if err != nil {
return err
}
run, stop := context.WithCancel(ctx)
done := make(chan error, 1)
go func() { done <- w.Run(run) }()
waited := false
defer func() {
stop()
if !waited {
<-done
}
}()
if err := configure(false); err != nil {
return err
}
for _, id := range []string{second.Task.ID, third.Task.ID} {
for {
task, err := api.GetTask(ctx, id)
if err != nil {
return err
}
if task.State == orbit.TaskSucceeded {
fmt.Printf("task=%s state=%s attempts=%d\n", id, task.State, task.AttemptCount)
break
}
if task.State == orbit.TaskFailed || task.State == orbit.TaskNeedsReview || task.State == orbit.TaskCancelled {
return fmt.Errorf("unexpected task state: %s", task.State)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(80 * time.Millisecond):
}
}
}
q, err = api.GetQueue(ctx, q.ID)
if err != nil {
return err
}
if q.UnfinishedTasks != 0 {
return errors.New("queue still has unfinished work")
}
stop()
err = <-done
waited = true
if err != nil {
return err
}
fmt.Printf("unfinished=%d rate_per_second=%d worker_drained=true\n", q.UnfinishedTasks, q.RatePerSecond)
return nil
}

View File

@@ -1,149 +0,0 @@
// retry 演示明确失败的安全重试:同一任务两次尝试,退避由平台持久化管理。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"sync/atomic"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credentials) != nil {
return errors.New("invalid credentials file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
operation, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
handler := "text.normalize.retry." + operation
var calls atomic.Uint32
process := func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
// 第一次在产生业务效果前明确拒绝,可安全重试;计数仅用于制造示例故障。
if calls.Add(1) == 1 {
return nil, &orbit.WorkerFailure{Code: "TEMPORARY_REJECTION", Retryable: true}
}
return normalize(ctx, e)
}
w, err := orbit.NewWorker(client, orbit.WorkerOptions{QueueID: queue, Capacity: 1, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{{Capability: orbit.WorkerCapability{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: process}}})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// 示例先受理,再启动消费者;生产通常是独立的长驻 Worker 进程。
var tasks []orbit.Task
for i, text := range []string{" safe retry "} {
payload, _ := json.Marshal(struct {
Text string `json:"text"`
}{text})
accepted, err := client.SubmitTask(ctx, fmt.Sprintf("retry-%s-%d", operation, i), orbit.SubmitTaskRequest{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", Payload: payload, TaskTimeoutMS: 60000, Retry: orbit.RetryPolicy{MaxAttempts: 3, AttemptTimeoutMS: 10000, InitialDelayMS: 500, MaxDelayMS: 2000}, RecoverySafety: orbit.RecoveryReconcileOnly})
if err != nil {
return err
}
tasks = append(tasks, accepted.Task)
}
workerCtx, stop := context.WithCancel(ctx)
done := make(chan error, 1)
go func() { done <- w.Run(workerCtx) }()
// 任一调用失败时也等待有界排空,不在 main 返回时静默丢掉执行中的 Worker。
waited := false
defer func() {
stop()
if !waited {
<-done
}
}()
for _, task := range tasks {
for {
current, err := client.GetTask(ctx, task.ID)
if err != nil {
return err
}
if current.State == orbit.TaskSucceeded {
history, err := client.ListTaskAttempts(ctx, current.ID, orbit.HistoryPageRequest{})
if err != nil {
return err
}
if current.AttemptCount != 2 || len(history.Items) != 2 || history.Items[1].State != orbit.AttemptFailed || !history.Items[1].Retryable || current.EffectUncertain {
return errors.New("retry history mismatch")
}
fmt.Printf("task=%s state=%s attempts=%d first_retryable=%t result=%s\n", current.ID, current.State, current.AttemptCount, history.Items[1].Retryable, current.Result)
break
}
if current.State == orbit.TaskFailed || current.State == orbit.TaskNeedsReview {
return fmt.Errorf("task %s requires attention: %s", current.ID, current.State)
}
select {
case err := <-done:
waited = true
return fmt.Errorf("Worker stopped before completion: %v", err)
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
}
stop()
err = <-done
waited = true
if err != nil {
return err
}
stats := w.Stats()
if stats.Started != 2 || stats.ReportsConfirmed != 2 || stats.Running != 0 {
return errors.New("unexpected Worker completion counts")
}
fmt.Printf("worker_drained=true started=%d confirmed=%d running=%d\n", stats.Started, stats.ReportsConfirmed, stats.Running)
return nil
}
func normalize(ctx context.Context, execution orbit.WorkerExecution) (json.RawMessage, error) {
var in struct {
Text string `json:"text"`
}
if err := json.Unmarshal(execution.Task.Payload, &in); err != nil {
return nil, &orbit.WorkerFailure{Code: "INVALID_TEXT"}
}
// 模拟可取消的短工作;真实 I/O 也必须使用传入的 ctx。
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(200 * time.Millisecond):
}
return json.Marshal(struct {
Text string `json:"text"`
}{Text: strings.ToUpper(strings.TrimSpace(in.Text))})
}

View File

@@ -1,157 +0,0 @@
// schedules 演示持久化周期任务:创建、恢复、Worker 执行、触发历史和暂停。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credentials) != nil {
return errors.New("invalid credentials file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
operation, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
handler := "object.snapshot." + operation
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
defer cancel()
worker, err := orbit.NewWorker(client, orbit.WorkerOptions{QueueID: queue, Capacity: 1, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{{Capability: orbit.WorkerCapability{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
// 实际副作用应按 Task.ID 或业务操作键去重;此示例只返回输入快照。
return e.Task.Payload, nil
}}}})
if err != nil {
return err
}
start := time.Now().UTC().Truncate(time.Microsecond).Add(1500 * time.Millisecond)
end := start.Add(10 * time.Second)
created, err := client.CreateSchedule(ctx, "create-"+operation, orbit.CreateScheduleRequest{Name: handler, Paused: true, Definition: orbit.ScheduleDefinition{
Rule: orbit.ScheduleRule{Kind: "FIXED_RATE", Timezone: "UTC", StartAt: start, EndAt: &end, IntervalMS: 1000},
Policy: orbit.SchedulePolicy{Misfire: "COALESCE", Overlap: "SERIAL", CatchupLimit: 2, CatchupWindowMS: 30000, GraceMS: 1000},
Task: orbit.SubmitTaskRequest{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"source_ref":"objects/sample","sequence":9007199254740993}`)},
}})
if err != nil {
return err
}
if _, err := client.ResumeSchedule(ctx, created.Schedule.ID, "resume-"+operation, orbit.ControlScheduleRequest{ExpectedRevision: created.Schedule.Revision, Reason: "start bounded example"}); err != nil {
return err
}
workerCtx, stop := context.WithCancel(ctx)
done := make(chan error, 1)
go func() { done <- worker.Run(workerCtx) }()
waited := false
defer func() {
stop()
if !waited {
<-done
}
}()
completed := map[string]bool{}
for len(completed) < 2 {
page, err := client.ListScheduleOccurrences(ctx, created.Schedule.ID, orbit.SchedulePageRequest{})
if err != nil {
return err
}
for _, item := range page.Items {
if item.TaskID == nil || completed[*item.TaskID] {
continue
}
task, err := client.GetTask(ctx, *item.TaskID)
if err != nil {
return err
}
if task.State == orbit.TaskSucceeded {
origin, err := client.GetTaskSource(ctx, task.ID)
if err != nil {
return err
}
if origin.Kind != "SCHEDULE" || origin.Schedule == nil || origin.Schedule.ScheduleID != created.Schedule.ID || origin.Schedule.OccurrenceID != item.ID || origin.Schedule.ScheduleVersion != item.ScheduleVersion {
return errors.New("task source did not match frozen occurrence")
}
fmt.Printf("source_kind=%s fixed_version=%d\n", origin.Kind, origin.Schedule.ScheduleVersion)
completed[task.ID] = true
fmt.Printf("schedule=%s occurrence=%s task=%s version=%d state=%s\n", created.Schedule.ID, item.ID, task.ID, item.ScheduleVersion, task.State)
}
if task.State == orbit.TaskFailed || task.State == orbit.TaskNeedsReview {
return fmt.Errorf("scheduled task requires attention: %s", task.State)
}
}
if len(completed) >= 2 {
break
}
select {
case err := <-done:
waited = true
return fmt.Errorf("Worker stopped before completion: %v", err)
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
// 扫描也改变计划版本;遇到 CAS 冲突读取新状态,再以新操作身份重试暂停。
for {
current, err := client.GetSchedule(ctx, created.Schedule.ID)
if err != nil {
return err
}
key, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
paused, err := client.PauseSchedule(ctx, current.ID, key, orbit.ControlScheduleRequest{ExpectedRevision: current.Revision, Reason: "example finished"})
if err == nil {
fmt.Printf("paused=%t completed_tasks=%d current_version=%d\n", paused.Schedule.Paused, len(completed), paused.Schedule.CurrentVersion)
break
}
var apiErr *orbit.APIError
if !errors.As(err, &apiErr) || apiErr.Code != "STATE_CONFLICT" {
return err
}
}
stop()
err = <-done
waited = true
if err != nil {
return err
}
fmt.Printf("worker_drained=true running=%d\n", worker.Stats().Running)
return nil
}

View File

@@ -1,109 +0,0 @@
// subscriptions 演示订阅管理和幂等回执;事件消费及Inbox完整示例随后独立提供。
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return err
}
var credential struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if err = json.Unmarshal(raw, &credential); err != nil {
return err
}
if token == "" {
token = credential.Token
}
if queue == "" {
queue = credential.QueueID
}
}
if token == "" || queue == "" {
return errors.New("provide an event-enabled credential and queue via ORBIT_CREDENTIALS_FILE or environment")
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
var random [16]byte
if _, err = rand.Read(random[:]); err != nil {
return err
}
operation := hex.EncodeToString(random[:])
input := orbit.CreateSubscriptionRequest{Name: "results-" + operation, QueueID: queue, Types: []string{"task.succeeded", "task.failed", "task.cancelled", "task.needs_review"}, MaxPending: 100, MaxAttempts: 20}
created, err := client.CreateSubscription(ctx, "create-"+operation, input)
if err != nil {
return err
}
duplicate, err := client.CreateSubscription(ctx, "create-"+operation, input)
if err != nil {
return err
}
if !duplicate.Deduplicated || duplicate.Subscription.ID != created.Subscription.ID {
return errors.New("subscription identity was not preserved")
}
sub := created.Subscription
configure := func(enabled bool, key string) error {
out, err := client.ConfigureSubscription(ctx, sub.ID, key, orbit.ConfigureSubscriptionRequest{ExpectedRevision: sub.Revision, Enabled: enabled, Reason: "subscription management example"})
if err == nil {
sub = out.Subscription
}
return err
}
if err = configure(false, "pause-"+operation); err != nil {
return err
}
if err = configure(true, "resume-"+operation); err != nil {
return err
}
current, err := client.GetSubscription(ctx, sub.ID)
if err != nil {
return err
}
if !current.Enabled || current.Revision != 3 {
return errors.New("subscription control state mismatch")
}
page, err := client.ListSubscriptions(ctx, queue, orbit.SubscriptionPageRequest{Limit: 100})
if err != nil {
return err
}
found := false
for _, item := range page.Items {
found = found || item.ID == sub.ID
}
if !found {
return errors.New("subscription absent from scoped listing")
}
// 示例不启动消费者。结束时暂停本次订阅,保留配置和审计以便核查。
if err = configure(false, "finish-"+operation); err != nil {
return err
}
fmt.Printf("subscription=%s duplicate=true resumed_revision=3\n", sub.ID)
fmt.Printf("final_enabled=%t revision=%d pending=%d\n", sub.Enabled, sub.Revision, sub.PendingCount)
return nil
}

View File

@@ -1,121 +0,0 @@
// task-query 演示业务键精确匹配、创建时间边界和任务摘要分页,不启动Worker。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if err := json.Unmarshal(raw, &credentials); err != nil {
return fmt.Errorf("invalid credentials file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
if queue == "" {
return fmt.Errorf("ORBIT_QUEUE_ID is required")
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
op, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
businessKey := "对象 /+% é " + op + " "
var tasks []orbit.Task
for i, key := range []string{businessKey, businessKey, businessKey[:len(businessKey)-1]} {
payload := json.RawMessage(`{"object_ref":"examples/input","format":"text"}`)
if i == 1 {
payload = json.RawMessage(`{"object_ref":"examples/input","format":"json"}`)
}
out, err := client.SubmitTask(ctx, fmt.Sprintf("query-%s-%d", op, i), orbit.SubmitTaskRequest{QueueID: queue, Handler: "query." + op, HandlerVersion: "v1", PayloadVersion: "v1", Payload: payload, BusinessKey: key, TaskTimeoutMS: 60000})
if err != nil {
return err
}
tasks = append(tasks, out.Task)
}
// 改参数使用新身份与相同关联键,读取旧任务确认原输入仍保留。
for _, task := range tasks[:2] {
current, err := client.GetTask(ctx, task.ID)
if err != nil {
return err
}
if string(current.Payload) != string(task.Payload) {
return errors.New("new linked submission changed existing input")
}
}
if tasks[0].ID == tasks[1].ID || string(tasks[0].Payload) == string(tasks[1].Payload) {
return errors.New("parameter change did not create an independent task")
}
from, before := tasks[0].CreatedAt, tasks[2].CreatedAt.Add(time.Microsecond)
query := orbit.ListTasksRequest{QueueID: queue, State: orbit.TaskWaiting, BusinessKey: businessKey, CreatedFrom: &from, CreatedBefore: &before, Limit: 1}
matched, pages := 0, 0
for {
page, err := client.ListTasks(ctx, query)
if err != nil {
return err
}
pages++
for _, task := range page.Items {
if task.BusinessKey != businessKey {
return errors.New("business key was not exact")
}
matched++
}
if page.NextCursor == "" {
break
}
query.Cursor = page.NextCursor
}
if matched != 2 || pages != 2 {
return errors.New("unexpected filtered pages")
}
query.Cursor = ""
end := tasks[1].CreatedAt
query.CreatedBefore = &end
bounded, err := client.ListTasks(ctx, query)
if err != nil {
return err
}
if len(bounded.Items) != 1 || bounded.Items[0].ID != tasks[0].ID {
return errors.New("exclusive time boundary failed")
}
for i, task := range tasks {
if _, err := client.CancelTask(ctx, task.ID, fmt.Sprintf("query-cancel-%s-%d", op, i), orbit.CancelTaskRequest{ExpectedRevision: task.Revision, Reason: "complete query example"}); err != nil {
return err
}
}
fmt.Printf("matched=%d pages=%d before_excluded=true original_payload_preserved=true cancelled=%d\n", matched, pages, len(tasks))
return nil
}

View File

@@ -1,98 +0,0 @@
// tasks 演示受理、重复提交、查询、分页与取消;此示例不启动业务 Worker。
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if err := json.Unmarshal(raw, &credentials); err != nil {
return fmt.Errorf("invalid credentials file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
if queue == "" {
return fmt.Errorf("ORBIT_QUEUE_ID is required")
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var operation [16]byte
if _, err := rand.Read(operation[:]); err != nil {
return err
}
key := "example-" + hex.EncodeToString(operation[:])
// 生产接入应先在业务库保存稳定键;本示例的两次请求共用同一个键。
in := orbit.SubmitTaskRequest{QueueID: queue, Handler: "file.convert", HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"source_ref":"example/input.txt","format":"pdf","sequence":9007199254740993}`)}
first, err := client.SubmitTask(ctx, key, in)
if err != nil {
return err
}
again, err := client.SubmitTask(ctx, key, in)
if err != nil {
return err
}
if first.Task.ID != again.Task.ID || !again.Deduplicated {
return fmt.Errorf("duplicate submission did not return the original task")
}
current, err := client.GetTask(ctx, first.Task.ID)
if err != nil {
return err
}
page, err := client.ListTasks(ctx, orbit.ListTasksRequest{QueueID: queue, Limit: 10})
if err != nil {
return err
}
cancelled, err := client.CancelTask(ctx, current.ID, key+"-cancel", orbit.CancelTaskRequest{Reason: "finish task acceptance example", ExpectedRevision: current.Revision})
if err != nil {
return err
}
// 仅历史兼容性 smoke 使用 ORBIT_SKIP_SOURCE;正常示例验证当前来源接口。
if os.Getenv("ORBIT_SKIP_SOURCE") == "" {
origin, err := client.GetTaskSource(ctx, current.ID)
if err != nil {
return err
}
if origin.Kind != "DIRECT" {
return fmt.Errorf("expected direct task source")
}
fmt.Printf("source_kind=%s\n", origin.Kind)
}
fmt.Printf("task=%s duplicate=%t listed=%d final_state=%s revision=%d\n", current.ID, again.Deduplicated, len(page.Items), cancelled.Task.State, cancelled.Task.Revision)
if cancelled.Task.State != orbit.TaskCancelled {
return fmt.Errorf("expected CANCELLED")
}
return nil
}

View File

@@ -1,214 +0,0 @@
// worker-protocol 演示一次文本规范化任务的真实执行与原消息重报。
// 这是底层协议示例;持续运行、自动续租与优雅退出见 examples/worker 的运行器示例。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credentials) != nil {
return errors.New("invalid credentials file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
// 每次运行使用独立能力名,避免接走前一次示例遗留的任务。
operation, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
handler := "text.normalize." + operation
registration, err := orbit.NewWorkerRegistration(queue, 1, []orbit.WorkerCapability{{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"}})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
defer cancel()
accepted, err := client.SubmitTask(ctx, "normalize-"+operation, orbit.SubmitTaskRequest{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"text":" Hello Orbit "}`), RecoverySafety: orbit.RecoveryIdempotent, TaskTimeoutMS: 60000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 20000}})
if err != nil {
return err
}
session := &demoSession{client: client, registration: registration}
defer session.close()
if err := session.connect(ctx); err != nil {
return err
}
capacityID, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
if _, err := session.conn.Capacity(ctx, capacityID, orbit.WorkerCapacity{Sequence: 1, Capacity: 1}); err != nil {
return err
}
assignment, err := session.conn.NextAssignment(ctx)
if err != nil {
return err
}
if assignment.Task.ID != accepted.Task.ID {
return errors.New("unexpected task in isolated example")
}
ref := assignment.Attempt()
session.ref = &ref
startID, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
started, err := session.confirm(ctx, func(c *orbit.WorkerConnection) (orbit.WorkerCommandResult, error) { return c.Start(ctx, startID, ref) })
if err != nil {
return err
}
deadline, active := started.Lease.ExecutionDeadline()
if !active {
return errors.New("start receipt does not grant current execution authority")
}
// 业务代码只拿任务输入;不接收注册秘密或尝试秘密。处理器只调用这一次。
work, stop := context.WithDeadline(ctx, deadline)
result, handlerErr := normalize(work, assignment.Task.Payload)
stop()
finish := orbit.WorkerFinish{Ref: ref, Outcome: orbit.WorkerSucceeded, Result: result}
if handlerErr != nil {
// 普通异常不能证明外部效果未发生;默认报告 UNKNOWN,保留核查路径。
finish.Outcome = orbit.WorkerUnknown
finish.ErrorCode = "HANDLER_UNCONFIRMED"
finish.Result = json.RawMessage(`{"reason":"handler did not confirm completion"}`)
}
finishID, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
confirmed, err := session.confirm(ctx, func(c *orbit.WorkerConnection) (orbit.WorkerCommandResult, error) {
return c.Finish(ctx, finishID, finish)
})
if err != nil {
return err
}
if confirmed.Receipt.Disposition != "APPLIED" {
return errors.New("result saved as evidence only; inspect authoritative task state")
}
current, err := client.GetTask(ctx, assignment.Task.ID)
if err != nil {
return err
}
if current.State != orbit.TaskSucceeded || current.AttemptCount != 1 || string(current.Result) != string(result) {
return errors.New("task did not complete as expected")
}
drainID, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
// 新连接尚未开启容量时,Sequence=2 仍是合法递增值。
if _, err := session.conn.Capacity(ctx, drainID, orbit.WorkerCapacity{Sequence: 2, Capacity: 0, Drain: true}); err != nil {
return err
}
fmt.Printf("task=%s final_state=%s attempts=%d result=%s\n", current.ID, current.State, current.AttemptCount, current.Result)
return nil
}
func normalize(ctx context.Context, payload json.RawMessage) (json.RawMessage, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
var in struct {
Text string `json:"text"`
}
if err := json.Unmarshal(payload, &in); err != nil {
return nil, err
}
return json.Marshal(struct {
Text string `json:"text"`
}{Text: strings.ToUpper(strings.TrimSpace(in.Text))})
}
type demoSession struct {
client *orbit.Client
registration orbit.WorkerRegistration
conn *orbit.WorkerConnection
ref *orbit.WorkerAttempt
}
func (s *demoSession) close() {
if s.conn != nil {
s.conn.Close()
}
}
func (s *demoSession) connect(ctx context.Context) error {
s.close()
c, err := s.client.DialWorker(ctx, s.registration)
if err != nil {
return err
}
s.conn = c
if s.ref != nil {
id, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
// 即使租约已失效,也可以重报原结果取得原回执或保存迟到证据。
if _, err := c.Resume(ctx, id, *s.ref); err != nil {
return err
}
}
return nil
}
// confirm 只重报控制操作,不再次调用业务函数;同一操作闭包固定消息 ID 与内容。
func (s *demoSession) confirm(ctx context.Context, call func(*orbit.WorkerConnection) (orbit.WorkerCommandResult, error)) (orbit.WorkerCommandResult, error) {
var out orbit.WorkerCommandResult
var err error
for attempt := 0; attempt < 3; attempt++ {
if attempt > 0 {
select {
case <-ctx.Done():
return out, ctx.Err()
case <-time.After(time.Duration(attempt) * 100 * time.Millisecond):
}
if err = s.connect(ctx); err != nil {
return out, err
}
}
out, err = call(s.conn)
if err == nil {
return out, nil
}
var control *orbit.WorkerError
retry := errors.Is(err, orbit.ErrWorkerDisconnected) || errors.Is(err, context.DeadlineExceeded) && ctx.Err() == nil || errors.As(err, &control) && control.Code == "STALE_SESSION"
if !retry {
return out, err
}
}
return out, fmt.Errorf("control result still unconfirmed after bounded replay: %w", err)
}

View File

@@ -1,133 +0,0 @@
// worker 演示自动 Worker:声明处理器,Run 管理连接/心跳/续租/恢复,取消后排空。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read private credentials: %w", err)
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credentials) != nil {
return errors.New("invalid credentials file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
operation, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
handler := "text.normalize.runtime." + operation
w, err := orbit.NewWorker(client, orbit.WorkerOptions{QueueID: queue, Capacity: 2, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{{Capability: orbit.WorkerCapability{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: normalize}}})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// 示例先受理,再启动消费者;生产通常是独立的长驻 Worker 进程。
var tasks []orbit.Task
for i, text := range []string{" hello orbit ", " reusable worker ", " durable result "} {
payload, _ := json.Marshal(struct {
Text string `json:"text"`
}{text})
accepted, err := client.SubmitTask(ctx, fmt.Sprintf("runtime-%s-%d", operation, i), orbit.SubmitTaskRequest{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", Payload: payload, TaskTimeoutMS: 60000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000}, RecoverySafety: orbit.RecoveryIdempotent})
if err != nil {
return err
}
tasks = append(tasks, accepted.Task)
}
workerCtx, stop := context.WithCancel(ctx)
done := make(chan error, 1)
go func() { done <- w.Run(workerCtx) }()
// 任一调用失败时也等待有界排空,不在 main 返回时静默丢掉执行中的 Worker。
waited := false
defer func() {
stop()
if !waited {
<-done
}
}()
for _, task := range tasks {
for {
current, err := client.GetTask(ctx, task.ID)
if err != nil {
return err
}
if current.State == orbit.TaskSucceeded {
fmt.Printf("task=%s state=%s result=%s\n", current.ID, current.State, current.Result)
break
}
if current.State == orbit.TaskFailed || current.State == orbit.TaskNeedsReview {
return fmt.Errorf("task %s requires attention: %s", current.ID, current.State)
}
select {
case err := <-done:
waited = true
return fmt.Errorf("Worker stopped before completion: %v", err)
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
}
stop()
err = <-done
waited = true
if err != nil {
return err
}
stats := w.Stats()
if stats.Started != 3 || stats.ReportsConfirmed != 3 || stats.Running != 0 {
return errors.New("unexpected Worker completion counts")
}
fmt.Printf("worker_drained=true started=%d confirmed=%d running=%d\n", stats.Started, stats.ReportsConfirmed, stats.Running)
return nil
}
func normalize(ctx context.Context, execution orbit.WorkerExecution) (json.RawMessage, error) {
var in struct {
Text string `json:"text"`
}
if err := json.Unmarshal(execution.Task.Payload, &in); err != nil {
return nil, &orbit.WorkerFailure{Code: "INVALID_TEXT"}
}
// 模拟可取消的短工作;真实 I/O 也必须使用传入的 ctx。
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(200 * time.Millisecond):
}
return json.Marshal(struct {
Text string `json:"text"`
}{Text: strings.ToUpper(strings.TrimSpace(in.Text))})
}

View File

@@ -1,132 +0,0 @@
// workflow-definition 演示研发定义与不可变版本发布;当前不启动流程实例。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
"os"
"reflect"
"time"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return err
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credentials) != nil {
return errors.New("invalid private credential file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
op := os.Getenv("ORBIT_OPERATION_ID")
if op == "" {
op, err = orbit.NewWorkerMessageID()
if err != nil {
return err
}
}
// 先输出稳定操作身份;响应丢失时用同一身份、同一版本和同一源码重新运行。
fmt.Printf("operation=%s\n", op)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
created, err := client.CreateWorkflowDefinition(ctx, op+".create", orbit.CreateWorkflowDefinitionRequest{Name: "object-flow." + op, Description: "对象准备、并行生成结果与有界状态查询"})
if err != nil {
return err
}
id := created.Definition.ID
in := orbit.PublishWorkflowVersionRequest{Version: "v1", Reason: "initial developer release", Specification: specification(queue)}
published, err := client.PublishWorkflowVersion(ctx, id, op+".v1", in)
if err != nil {
return err
}
again, err := client.PublishWorkflowVersion(ctx, id, op+".v1", in)
if err != nil || !again.Deduplicated || !reflect.DeepEqual(again.Version, published.Version) {
return fmt.Errorf("original publication replay mismatch: %v", err)
}
same, err := client.PublishWorkflowVersion(ctx, id, op+".v1.same", in)
if err != nil || !same.Deduplicated || same.Version.ID != published.Version.ID {
return fmt.Errorf("same-version replay mismatch: %v", err)
}
in.Version = "v2"
in.Reason = "extend finite workflow deadline"
in.Specification.TimeoutMS = 90000
next, err := client.PublishWorkflowVersion(ctx, id, op+".v2", in)
if err != nil {
return err
}
original, err := client.GetWorkflowVersion(ctx, id, "v1")
if err != nil || !reflect.DeepEqual(original, published.Version) {
return fmt.Errorf("original immutable version changed: %v", err)
}
meta, err := client.GetWorkflowDefinition(ctx, id)
if err != nil {
return err
}
fmt.Printf("definition=%s revision=%d v1=%s v2=%s steps=%d max_concurrency=%d max_task_attempts=%d\n", id, meta.Revision, original.ID, next.Version.ID, original.Analysis.Steps, original.Analysis.MaxConcurrency, original.Analysis.MaxTaskAttempts)
fmt.Println("Both definitions are stored. No workflow instance or business task has been started.")
return nil
}
func specification(queue string) orbit.WorkflowSpecification {
str := orbit.WorkflowType{Kind: "STRING"}
boolean := orbit.WorkflowType{Kind: "BOOLEAN"}
input := orbit.WorkflowType{Kind: "OBJECT", Fields: map[string]orbit.WorkflowType{"object_ref": str, "operation": str, "approved": boolean}}
literal := func(raw string) orbit.WorkflowValue {
return orbit.WorkflowValue{Kind: "LITERAL", Literal: json.RawMessage(raw)}
}
block := func(value orbit.WorkflowValue) orbit.WorkflowBlock {
return orbit.WorkflowBlock{OutputType: str, Output: value}
}
task := func(handler string, value orbit.WorkflowValue) orbit.WorkflowTask {
return orbit.WorkflowTask{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", InputType: str, Input: value, ResultType: str, TimeoutMS: 5000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 3000}, RecoverySafety: orbit.RecoveryReconcileOnly}
}
prepare := task("object.prepare", orbit.WorkflowRef("input", "object_ref"))
branchDefault := block(literal(`"standard"`))
left, right := orbit.WorkflowRef("input", "approved"), literal(`true`)
branch := orbit.WorkflowBranch{Cases: []orbit.WorkflowBranchCase{{When: orbit.WorkflowPredicate{Op: "EQ", Left: &left, Right: &right}, Body: block(literal(`"approved"`))}}, Default: &branchDefault}
parallel := orbit.WorkflowParallel{}
for _, name := range []string{"preview", "manifest"} {
work := task("object."+name, orbit.WorkflowRef("prepare"))
body := block(orbit.WorkflowRef(name))
body.Steps = []orbit.WorkflowStep{{Key: name, Kind: "TASK", Task: &work}}
parallel.Branches = append(parallel.Branches, orbit.WorkflowParallelBranch{Name: name, Body: body})
}
status := orbit.WorkflowType{Kind: "OBJECT", Fields: map[string]orbit.WorkflowType{"status": {Kind: "STRING", Enum: []string{"PENDING", "DONE", "FAILED", "REVIEW"}}}}
query := task("job.query", orbit.WorkflowRef("input", "operation"))
query.ResultType = status
query.Retry = orbit.RetryPolicy{MaxAttempts: 2, AttemptTimeoutMS: 1000, InitialDelayMS: 100, MaxDelayMS: 200}
query.RecoverySafety = orbit.RecoveryIdempotent
poll := orbit.WorkflowPoll{Task: query, MaxRounds: 5, TimeoutMS: 20000, IntervalMS: 1000, StatusPath: []string{"status"}, Pending: []string{"PENDING"}, Succeeded: []string{"DONE"}, Failed: []string{"FAILED"}, Review: []string{"REVIEW"}, FinalSignal: &orbit.WorkflowSignal{Name: "job.final", PayloadVersion: "v1", Correlation: orbit.WorkflowRef("input", "operation"), PayloadType: status, TimeoutMS: 20000}}
return orbit.WorkflowSpecification{SchemaVersion: 1, InputType: input, TimeoutMS: 60000, FailurePolicy: "REVIEW", Body: orbit.WorkflowBlock{Steps: []orbit.WorkflowStep{
{Key: "prepare", Kind: "TASK", Task: &prepare},
{Key: "route", Kind: "BRANCH", Branch: &branch},
{Key: "outputs", Kind: "PARALLEL", Parallel: &parallel},
{Key: "approval", Kind: "SIGNAL", Signal: &orbit.WorkflowSignal{Name: "object.approval", PayloadVersion: "v1", Correlation: orbit.WorkflowRef("input", "operation"), PayloadType: boolean, TimeoutMS: 10000}},
{Key: "settle", Kind: "TIMER", Timer: &orbit.WorkflowTimer{DelayMS: 1000}},
{Key: "result", Kind: "POLL", Poll: &poll},
}, OutputType: status, Output: orbit.WorkflowRef("result")}}
}

View File

@@ -1,249 +0,0 @@
// workflow-poll 区分请求失败重试与成功PENDING后的业务轮次,并演示轮次耗尽进入核查。
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"sync/atomic"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
"git.yuntuo.hk/k2safe/orbit/sdk/go/examples/internal/flowdemo"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
api, queue, operation, err := flowdemo.Client()
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
for _, exhausted := range []bool{false, true} {
if err := scenario(ctx, api, queue, operation, exhausted); err != nil {
return err
}
}
return nil
}
func scenario(ctx context.Context, api *orbit.Client, queue, operation string, exhausted bool) error {
mode, expected := "success", "SUCCEEDED"
if exhausted {
mode, expected = "exhausted", "NEEDS_REVIEW"
}
operation += "." + mode
var requests, submissions atomic.Int32
// 这是本地外部渠道夹具。真实业务应查询已存在的外部作业,不能在轮询里重新提交作业。
remote := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/jobs/"+operation {
http.NotFound(w, r)
return
}
if r.Method == "POST" {
raw, err := io.ReadAll(io.LimitReader(r.Body, 1025))
var in struct {
OperationID string `json:"operation_id"`
}
if err != nil || len(raw) > 1024 || json.Unmarshal(raw, &in) != nil || in.OperationID != operation {
http.Error(w, "invalid operation", 400)
return
}
if submissions.Add(1) != 1 {
http.Error(w, "unexpected repeated submission", 409)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
_ = json.NewEncoder(w).Encode(in)
return
}
if r.Method != "GET" || submissions.Load() != 1 {
http.NotFound(w, r)
return
}
n := requests.Add(1)
if !exhausted && n == 1 {
http.Error(w, "temporary query failure", http.StatusServiceUnavailable)
return
}
state := "PENDING"
if !exhausted && n >= 3 {
state = "DONE"
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(state)
}))
defer remote.Close()
input, _ := json.Marshal(struct {
OperationID string `json:"operation_id"`
}{operation})
typ := orbit.WorkflowType{Kind: "OBJECT", Fields: map[string]orbit.WorkflowType{"operation_id": {Kind: "STRING"}}}
status := orbit.WorkflowType{Kind: "STRING", Enum: []string{"PENDING", "DONE"}}
handler := "job.query." + operation
task := orbit.WorkflowTask{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", InputType: typ, Input: orbit.WorkflowRef("input"), ResultType: status, TimeoutMS: 20000, Retry: orbit.RetryPolicy{MaxAttempts: 2, InitialDelayMS: 1000, MaxDelayMS: 1000, AttemptTimeoutMS: 5000}, RecoverySafety: orbit.RecoveryIdempotent}
submit := task
submit.Handler, submit.ResultType, submit.RecoverySafety = "job.submit."+operation, typ, orbit.RecoveryReconcileOnly
submit.Retry = orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 5000}
task.Input = orbit.WorkflowRef("submit")
spec := orbit.WorkflowSpecification{SchemaVersion: 1, InputType: typ, TimeoutMS: 45000, FailurePolicy: "REVIEW", Body: orbit.WorkflowBlock{Steps: []orbit.WorkflowStep{{Key: "submit", Kind: "TASK", Task: &submit}, {Key: "query", Kind: "POLL", Poll: &orbit.WorkflowPoll{Task: task, MaxRounds: 2, TimeoutMS: 30000, IntervalMS: 1000, Pending: []string{"PENDING"}, Succeeded: []string{"DONE"}}}}, OutputType: status, Output: orbit.WorkflowRef("query")}}
v, err := flowdemo.PublishStart(ctx, api, operation, "poll."+mode, spec, input)
if err != nil {
return err
}
httpClient := remote.Client()
httpClient.Timeout = 3 * time.Second
runner, err := flowdemo.StartWorker(ctx, api, queue, orbit.WorkerHandler{Capability: orbit.WorkerCapability{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
var in struct {
OperationID string `json:"operation_id"`
}
if json.Unmarshal(e.Task.Payload, &in) != nil || in.OperationID != operation {
return nil, &orbit.WorkerFailure{Code: "INVALID_JOB"}
}
req, err := http.NewRequestWithContext(ctx, "GET", remote.URL+"/jobs/"+in.OperationID, nil)
if err != nil {
return nil, err
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
} // 未知传输错误保留UNKNOWN,由冻结安全策略处理。
defer resp.Body.Close()
if resp.StatusCode == http.StatusServiceUnavailable {
// 本例渠道明确拒绝只读查询,可以声明请求失败且允许Pulse重试。
return nil, &orbit.WorkerFailure{Code: "QUERY_TEMPORARY", Retryable: true}
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("unexpected query response")
}
raw, err := io.ReadAll(io.LimitReader(resp.Body, 1025))
if err != nil {
return nil, err
}
var state string
if len(raw) > 1024 || json.Unmarshal(raw, &state) != nil || state != "PENDING" && state != "DONE" {
return nil, errors.New("invalid query result")
}
return json.RawMessage(raw), nil // PENDING是成功查询结果,不能转为可重试错误。
}}, orbit.WorkerHandler{Capability: orbit.WorkerCapability{Handler: submit.Handler, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
req, err := http.NewRequestWithContext(ctx, "POST", remote.URL+"/jobs/"+operation, bytes.NewReader(e.Task.Payload))
if err != nil {
return nil, err
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
} // 提交结果未知必须核查,不能交给POLL重新提交。
defer resp.Body.Close()
if resp.StatusCode != http.StatusAccepted {
return nil, errors.New("submission receipt unavailable")
}
raw, err := io.ReadAll(io.LimitReader(resp.Body, 1025))
if err != nil {
return nil, err
}
var accepted struct {
OperationID string `json:"operation_id"`
}
if len(raw) > 1024 || json.Unmarshal(raw, &accepted) != nil || accepted.OperationID != operation {
return nil, errors.New("submission identity mismatch")
}
return json.RawMessage(raw), nil
}})
if err != nil {
return err
}
defer runner.Close()
initial, err := runner.Step(ctx, api, v.ID, "query", func(d orbit.WorkflowStepDetail) bool { return d.Poll != nil && d.Step.ActivationID != nil })
if err != nil {
return err
}
final, err := runner.Workflow(ctx, api, v.ID, expected)
if err != nil {
return err
}
step, err := api.GetWorkflowStep(ctx, v.ID, "query")
if err != nil {
return err
}
if step.Poll == nil || step.Step.ActivationID == nil || step.Poll.UsedRounds != 2 || step.Poll.MaxAttemptsPerRound != 2 || !step.Poll.DeadlineAt.Equal(initial.Poll.DeadlineAt) || !final.DeadlineAt.Equal(v.DeadlineAt) || *step.Step.ActivationID != *initial.Step.ActivationID {
return errors.New("poll budget, deadline or activation changed")
}
// 历史按业务轮倒序;每轮再独立读取Pulse尝试,二者不可混为同一个计数。
cursor, rounds, totalAttempts := "", 0, 0
for {
page, err := api.ListWorkflowPollRounds(ctx, v.ID, "query", *step.Step.ActivationID, orbit.HistoryPageRequest{Limit: 1, Cursor: cursor})
if err != nil {
return err
}
for _, round := range page.Items {
rounds++
source, err := api.GetTaskSource(ctx, round.TaskID)
if err != nil {
return err
}
if source.Workflow == nil || source.Workflow.WorkflowID != v.ID || source.Workflow.PollRound != round.Round {
return errors.New("query round source mismatch")
}
attempts, err := api.ListTaskAttempts(ctx, round.TaskID, orbit.HistoryPageRequest{Limit: 10})
if err != nil {
return err
}
want := 1
if !exhausted && round.Round == 1 {
want = 2
}
if len(attempts.Items) != want || attempts.NextCursor != "" {
return errors.New("request retries consumed wrong business rounds")
}
for i, a := range attempts.Items {
expect := "SUCCEEDED"
if want == 2 && i == 1 {
expect = "FAILED"
}
if string(a.State) != expect || a.EndedAt == nil {
return errors.New("unexpected request attempt history")
}
}
totalAttempts += len(attempts.Items)
}
if page.NextCursor == "" {
break
}
cursor = page.NextCursor
}
expectedRequests := 3
if exhausted {
expectedRequests = 2
}
if rounds != 2 || totalAttempts != expectedRequests || int(requests.Load()) != expectedRequests || submissions.Load() != 1 {
return errors.New("unexpected query count")
}
if !exhausted && string(final.Output) != `"DONE"` {
return errors.New("final business result missing")
}
if exhausted && (step.Step.State != "NEEDS_REVIEW" || step.Poll.CurrentTaskID != nil || step.Poll.NextPollAt != nil) {
return errors.New("exhausted poll remained runnable")
}
if err := runner.Close(); err != nil {
return err
}
stats := runner.Worker.Stats()
if int(stats.Started) != expectedRequests+1 || int(stats.ReportsConfirmed) != expectedRequests+1 || stats.Running != 0 {
return errors.New("Worker did not drain all requests")
}
fmt.Printf("poll_scenario=%s state=%s submissions=%d rounds=%d attempts=%d requests=%d deadline_preserved=true worker_drained=true\n", mode, final.State, submissions.Load(), rounds, totalAttempts, requests.Load())
return nil
}

View File

@@ -1,116 +0,0 @@
// workflow-query 发布有限等待流程,按精确业务键和固定版本分页,并按控制版本结清示例实例。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
raw, err := os.ReadFile(os.Getenv("ORBIT_CREDENTIALS_FILE"))
if err != nil {
return err
}
var credentials struct {
Token string `json:"token"`
}
if json.Unmarshal(raw, &credentials) != nil || credentials.Token == "" {
return errors.New("invalid private credentials file")
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: credentials.Token})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
operation, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
spec := orbit.WorkflowSpecification{SchemaVersion: 1, InputType: orbit.WorkflowType{Kind: "NULL"}, TimeoutMS: 120000, FailurePolicy: "REVIEW", Body: orbit.WorkflowBlock{
Steps: []orbit.WorkflowStep{{Key: "receipt", Kind: "SIGNAL", Signal: &orbit.WorkflowSignal{Name: "object.ready", PayloadVersion: "v1", Correlation: orbit.WorkflowValue{Kind: "LITERAL", Literal: json.RawMessage(`"` + operation + `"`)}, PayloadType: orbit.WorkflowType{Kind: "BOOLEAN"}, TimeoutMS: 120000}}},
OutputType: orbit.WorkflowType{Kind: "BOOLEAN"}, Output: orbit.WorkflowRef("receipt"),
}}
d, err := client.CreateWorkflowDefinition(ctx, operation, orbit.CreateWorkflowDefinitionRequest{Name: "query." + operation})
if err != nil {
return err
}
if _, err = client.PublishWorkflowVersion(ctx, d.Definition.ID, operation, orbit.PublishWorkflowVersionRequest{Version: "v1", Specification: spec, Reason: "publish query example"}); err != nil {
return err
}
key := "对象 /+% é " + operation + " "
var instances []orbit.WorkflowInstance
for i, business := range []string{key, key, key[:len(key)-1]} {
out, err := client.StartWorkflow(ctx, fmt.Sprintf("%s-%d", operation, i), orbit.StartWorkflowRequest{DefinitionID: d.Definition.ID, Version: "v1", Input: json.RawMessage(`null`), BusinessKey: business})
if err != nil {
return err
}
instances = append(instances, out.Instance)
}
from, before := instances[0].CreatedAt, instances[2].CreatedAt.Add(time.Microsecond)
query := orbit.ListWorkflowsRequest{DefinitionID: d.Definition.ID, Version: "v1", BusinessKey: key, State: "WAITING", CreatedFrom: &from, CreatedBefore: &before, Limit: 1}
seen := map[string]bool{}
pages := 0
for {
page, err := client.ListWorkflows(ctx, query)
if err != nil {
return err
}
pages++
for _, v := range page.Items {
if v.BusinessKey != key || v.DefinitionID != d.Definition.ID || v.Version != "v1" || seen[v.ID] {
return errors.New("workflow query changed filters or repeated an item")
}
seen[v.ID] = true
}
if page.NextCursor == "" {
break
}
query.Cursor = page.NextCursor
}
if len(seen) != 2 || pages != 2 || !seen[instances[0].ID] || !seen[instances[1].ID] {
return errors.New("expected exactly two matching workflow instances")
}
fmt.Printf("workflow_query_verified=true matched=%d pages=%d exact_trailing_space=true\n", len(seen), pages)
// 取消记录保留,固定定义版本和审计也保留;失败时不要直接删除运行记录。
for _, v := range instances {
current, err := client.GetWorkflow(ctx, v.ID)
if err != nil {
return err
}
if _, err = client.ControlWorkflow(ctx, v.ID, operation+"-cleanup", orbit.WorkflowControlRequest{Operation: "CANCEL", ExpectedControlRevision: current.ControlRevision, Reason: "query example cleanup"}); err != nil {
return err
}
for {
current, err = client.GetWorkflow(ctx, v.ID)
if err != nil {
return err
}
if current.State == "CANCELLED" {
break
}
if current.State == "NEEDS_REVIEW" {
return errors.New("cleanup requires review")
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(50 * time.Millisecond):
}
}
}
fmt.Println("workflow_query_cleanup=CANCELLED count=3")
return nil
}

View File

@@ -1,284 +0,0 @@
// workflow-recovery 注入本地渠道提交后断线,使用明确证据恢复,保留UNKNOWN尝试和暂停门控。
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"sync"
"sync/atomic"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
"git.yuntuo.hk/k2safe/orbit/sdk/go/examples/internal/flowdemo"
)
const exactSequence uint64 = 9007199254740993
type request struct {
OperationID string `json:"operation_id"` // OperationID 原外部操作身份,核查时保持不变。
Sequence uint64 `json:"sequence"` // Sequence 精确业务序号,不使用float64解析。
}
type receipt struct {
OperationID string `json:"operation_id"` // OperationID 已完成操作的稳定身份。
Sequence uint64 `json:"sequence"` // Sequence 渠道已保存的精确序号。
State string `json:"state"` // State 本夹具唯一可核实的成功状态CONFIRMED。
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
api, queue, operation, err := flowdemo.Client()
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
var mu sync.Mutex
var committed json.RawMessage
var submissions atomic.Int32
var dropped atomic.Bool
// 本地夹具代表唯一受控外部系统:先保存结果,再关闭HTTP连接而不发送回执。
// 真实人工核查需要持久业务证据及执行已结束的证明,不能只凭GET成功推断所有执行已停止。
remote := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/objects/"+operation {
http.NotFound(w, r)
return
}
if r.Method == "GET" {
mu.Lock()
raw := append([]byte(nil), committed...)
mu.Unlock()
if len(raw) == 0 {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(raw)
return
}
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
raw, err := io.ReadAll(io.LimitReader(r.Body, 4097))
var in request
if err != nil || len(raw) > 4096 || json.Unmarshal(raw, &in) != nil || in.OperationID != operation || in.Sequence != exactSequence {
http.Error(w, "invalid command", 400)
return
}
if submissions.Add(1) != 1 {
http.Error(w, "unexpected resubmission", 409)
return
}
evidence, _ := json.Marshal(receipt{operation, exactSequence, "CONFIRMED"})
mu.Lock()
committed = evidence
mu.Unlock()
conn, _, err := w.(http.Hijacker).Hijack()
if err == nil {
dropped.Store(true)
_ = conn.Close()
}
}))
defer remote.Close()
httpClient := remote.Client()
httpClient.Timeout = 3 * time.Second
requestType := orbit.WorkflowType{Kind: "OBJECT", Fields: map[string]orbit.WorkflowType{"operation_id": {Kind: "STRING"}, "sequence": {Kind: "NUMBER"}}}
receiptType := orbit.WorkflowType{Kind: "OBJECT", Fields: map[string]orbit.WorkflowType{"operation_id": {Kind: "STRING"}, "sequence": {Kind: "NUMBER"}, "state": {Kind: "STRING", Enum: []string{"CONFIRMED"}}}}
publishHandler, recordHandler := "object.publish."+operation, "object.record."+operation
publish := orbit.WorkflowTask{QueueID: queue, Handler: publishHandler, HandlerVersion: "v1", PayloadVersion: "v1", InputType: requestType, Input: orbit.WorkflowRef("input"), ResultType: receiptType, TimeoutMS: 30000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 5000}, RecoverySafety: orbit.RecoveryReconcileOnly}
record := publish
record.Handler = recordHandler
record.InputType = receiptType
record.Input = orbit.WorkflowRef("publish")
record.RecoverySafety = orbit.RecoveryIdempotent
spec := orbit.WorkflowSpecification{SchemaVersion: 1, InputType: requestType, TimeoutMS: 60000, FailurePolicy: "REVIEW", Body: orbit.WorkflowBlock{Steps: []orbit.WorkflowStep{{Key: "publish", Kind: "TASK", Task: &publish}, {Key: "record", Kind: "TASK", Task: &record}}, OutputType: receiptType, Output: orbit.WorkflowRef("record")}}
input, _ := json.Marshal(request{operation, exactSequence})
v, err := flowdemo.PublishStart(ctx, api, operation, "recovery", spec, input)
if err != nil {
return err
}
runner, err := flowdemo.StartWorker(ctx, api, queue,
orbit.WorkerHandler{Capability: orbit.WorkerCapability{Handler: publishHandler, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
req, err := http.NewRequestWithContext(ctx, "POST", remote.URL+"/objects/"+operation, bytes.NewReader(e.Task.Payload))
if err != nil {
return nil, err
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
} // 回执丢失只能报UNKNOWN,不能声明明确失败以触发重发。
resp.Body.Close()
return nil, errors.New("fixture expected a lost response; inspect remote operation")
}},
orbit.WorkerHandler{Capability: orbit.WorkerCapability{Handler: recordHandler, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
if err := verifyReceipt(e.Task.Payload, operation); err != nil {
return nil, &orbit.WorkerFailure{Code: "INVALID_RECEIPT"}
}
return e.Task.Payload, nil
}})
if err != nil {
return err
}
defer runner.Close()
step, err := runner.Step(ctx, api, v.ID, "publish", func(d orbit.WorkflowStepDetail) bool { return d.Step.State == "NEEDS_REVIEW" })
if err != nil {
return err
}
if step.Step.TaskID == nil || step.Step.ActivationID == nil {
return errors.New("review source missing")
}
taskID, activation := *step.Step.TaskID, *step.Step.ActivationID
task, err := api.GetTask(ctx, taskID)
if err != nil {
return err
}
attempts, err := api.ListTaskAttempts(ctx, taskID, orbit.HistoryPageRequest{Limit: 2})
if err != nil {
return err
}
if task.State != orbit.TaskNeedsReview || len(attempts.Items) != 1 || attempts.Items[0].State != "UNKNOWN" || attempts.Items[0].EndedAt == nil || submissions.Load() != 1 || !dropped.Load() {
return errors.New("lost response did not retain one UNKNOWN execution")
}
if err := runner.Wait(ctx, func() (bool, error) {
s := runner.Worker.Stats()
return s.Running == 0 && s.ReportsConfirmed == 1, nil
}); err != nil {
return err
}
current, err := api.GetWorkflow(ctx, v.ID)
if err != nil {
return err
}
if _, err = api.ControlWorkflow(ctx, v.ID, operation+".pause", orbit.WorkflowControlRequest{Operation: "PAUSE", ExpectedControlRevision: current.ControlRevision, Reason: "hold successor while operator verifies evidence"}); err != nil {
return err
}
// 这里只在受控夹具证明“唯一提交已经结束、操作身份和结果一致”后模拟管理员决定。
req, err := http.NewRequestWithContext(ctx, "GET", remote.URL+"/objects/"+operation, nil)
if err != nil {
return err
}
resp, err := httpClient.Do(req)
if err != nil {
return err
}
evidence, readErr := io.ReadAll(io.LimitReader(resp.Body, 4097))
resp.Body.Close()
if readErr != nil {
return readErr
}
if resp.StatusCode != 200 || len(evidence) > 4096 {
return errors.New("verified external evidence unavailable")
}
if err := verifyReceipt(evidence, operation); err != nil {
return err
}
step, err = api.GetWorkflowStep(ctx, v.ID, "publish")
if err != nil {
return err
}
evidenceRef := "evidence:local-fixture/" + operation
decision := orbit.WorkflowRecoveryDecision{ExpectedRevision: step.Revision, Reason: "verified fixture commit, original operation and settled execution", EvidenceRef: evidenceRef}
command := orbit.ReviewWorkflowTaskRequest{WorkflowRecoveryDecision: decision, TaskID: taskID, ExpectedTaskRevision: task.Revision, ExecutionSettled: true, Outcome: orbit.TaskSucceeded, Result: evidence}
reviewed, err := api.ReviewWorkflowTask(ctx, v.ID, operation+".review", command)
if err != nil {
return err
}
again, err := api.ReviewWorkflowTask(ctx, v.ID, operation+".review", command)
if err != nil {
return err
}
if !again.Deduplicated || again.AuditID != reviewed.AuditID || reviewed.Task == nil || reviewed.TaskAuditID == nil {
return errors.New("review identity or dual audit missing")
}
step, err = runner.Step(ctx, api, v.ID, "publish", func(d orbit.WorkflowStepDetail) bool { return d.Revision > reviewed.Instance.Revision })
if err != nil {
return err
}
if step.Step.ActivationID == nil || *step.Step.ActivationID != activation {
return errors.New("review changed activation")
}
decision.ExpectedRevision = step.Revision
resolved, err := api.ResolveWorkflowStep(ctx, v.ID, operation+".resolve", orbit.ResolveWorkflowStepRequest{WorkflowStepDecision: orbit.WorkflowStepDecision{WorkflowRecoveryDecision: decision, StepKey: "publish", ActivationID: activation, ExpectedStepRevision: step.Step.Revision}, ExecutionSettled: true, Outcome: "SUCCEEDED", Result: evidence})
if err != nil {
return err
}
current, err = api.GetWorkflow(ctx, v.ID)
if err != nil {
return err
}
decision.ExpectedRevision = current.Revision
continued, err := api.ContinueWorkflow(ctx, v.ID, operation+".continue", orbit.ContinueWorkflowRequest{WorkflowRecoveryDecision: decision, ReviewResolved: true})
if err != nil {
return err
}
next, err := api.GetWorkflowStep(ctx, v.ID, "record")
if err != nil {
return err
}
if !continued.Instance.Paused || next.Step.ActivationID != nil || next.Step.TaskID != nil || next.Step.State != "BLOCKED" {
return errors.New("manual recovery bypassed pause gate")
}
for action, id := range map[string]string{"workflow.task.review": reviewed.AuditID, "workflow.step.resolve": resolved.AuditID, "workflow.review.continue": continued.AuditID} {
audit, err := api.GetWorkflowOperation(ctx, v.ID, id)
if err != nil {
return err
}
if audit.Action != action || audit.EvidenceRef != evidenceRef || audit.Reason != decision.Reason || audit.ActorID == "" {
return errors.New("management evidence or actor missing")
}
}
current, err = api.GetWorkflow(ctx, v.ID)
if err != nil {
return err
}
if _, err = api.ControlWorkflow(ctx, v.ID, operation+".resume", orbit.WorkflowControlRequest{Operation: "RESUME", ExpectedControlRevision: current.ControlRevision, Reason: "verified review complete; explicitly release pause"}); err != nil {
return err
}
final, err := runner.Workflow(ctx, api, v.ID, "SUCCEEDED")
if err != nil {
return err
}
if err := verifyReceipt(final.Output, operation); err != nil {
return err
}
old, err := api.GetTaskAttempt(ctx, taskID, attempts.Items[0].ID)
if err != nil {
return err
}
if old.State != "UNKNOWN" || old.EndedAt == nil || !old.EndedAt.Equal(*attempts.Items[0].EndedAt) || submissions.Load() != 1 || !final.DeadlineAt.Equal(v.DeadlineAt) {
return errors.New("recovery rewrote history, repeated submission or extended deadline")
}
if err := runner.Close(); err != nil {
return err
}
stats := runner.Worker.Stats()
if stats.Started != 2 || stats.ReportsConfirmed != 2 || stats.Running != 0 {
return errors.New("unexpected executions or incomplete drain")
}
fmt.Printf("recovery_verified=true state=%s submissions=%d unknown_history_preserved=true review_deduplicated=true pause_preserved=true audits=3 sequence=%d worker_drained=true\n", final.State, submissions.Load(), exactSequence)
return nil
}
func verifyReceipt(raw json.RawMessage, operation string) error {
var out receipt
if json.Unmarshal(raw, &out) != nil || out.OperationID != operation || out.Sequence != exactSequence || out.State != "CONFIRMED" {
return errors.New("external receipt identity or exact sequence mismatch")
}
return nil
}

View File

@@ -1,252 +0,0 @@
// workflow 通过公共SDK运行并行处理、计时等待、外部回执与后续发布;业务处理器在Worker中执行。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
raw, err := os.ReadFile(os.Getenv("ORBIT_CREDENTIALS_FILE"))
if err != nil {
return err
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credentials) != nil || credentials.Token == "" || credentials.QueueID == "" {
return errors.New("invalid private credentials file")
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: credentials.Token})
if err != nil {
return err
}
operation, err := orbit.NewWorkerMessageID()
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
handler := "artifact.normalize." + operation
typ := orbit.WorkflowType{Kind: "OBJECT", Fields: map[string]orbit.WorkflowType{"text": {Kind: "STRING"}}}
task := orbit.WorkflowTask{QueueID: credentials.QueueID, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", InputType: typ, Input: orbit.WorkflowRef("input"), ResultType: typ, TimeoutMS: 20000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 5000}, RecoverySafety: orbit.RecoveryIdempotent}
pair := orbit.WorkflowParallel{Branches: []orbit.WorkflowParallelBranch{
{Name: "left", Body: orbit.WorkflowBlock{Steps: []orbit.WorkflowStep{{Key: "left", Kind: "TASK", Task: &task}}, OutputType: typ, Output: orbit.WorkflowRef("left")}},
{Name: "right", Body: orbit.WorkflowBlock{Steps: []orbit.WorkflowStep{{Key: "right", Kind: "TASK", Task: &task}}, OutputType: typ, Output: orbit.WorkflowRef("right")}},
}}
publish := task
publish.Input = orbit.WorkflowRef("pair", "left")
spec := orbit.WorkflowSpecification{SchemaVersion: 1, TimeoutMS: 60000, FailurePolicy: "REVIEW", InputType: typ, Body: orbit.WorkflowBlock{Steps: []orbit.WorkflowStep{
{Key: "pair", Kind: "PARALLEL", Parallel: &pair},
{Key: "cooldown", Kind: "TIMER", Timer: &orbit.WorkflowTimer{DelayMS: 500}},
{Key: "receipt", Kind: "SIGNAL", Signal: &orbit.WorkflowSignal{Name: "artifact.confirmed", PayloadVersion: "v1", Correlation: orbit.WorkflowValue{Kind: "LITERAL", Literal: json.RawMessage(`"` + operation + `"`)}, PayloadType: orbit.WorkflowType{Kind: "BOOLEAN"}, TimeoutMS: 30000}},
{Key: "publish", Kind: "TASK", Task: &publish},
}, OutputType: typ, Output: orbit.WorkflowRef("publish")}}
definition, err := client.CreateWorkflowDefinition(ctx, operation, orbit.CreateWorkflowDefinitionRequest{Name: "artifact." + operation, Description: "并行结果生成与模拟外部回执"})
if err != nil {
return err
}
if _, err = client.PublishWorkflowVersion(ctx, definition.Definition.ID, operation, orbit.PublishWorkflowVersionRequest{Version: "v1", Specification: spec, Reason: "publish runnable workflow example"}); err != nil {
return err
}
start := orbit.StartWorkflowRequest{DefinitionID: definition.Definition.ID, Version: "v1", Input: json.RawMessage(`{"text":" hello orbit "}`), BusinessKey: operation}
accepted, err := client.StartWorkflow(ctx, operation, start)
if err != nil {
return err
}
repeated, err := client.StartWorkflow(ctx, operation, start)
if err != nil || !repeated.Deduplicated || repeated.Instance.ID != accepted.Instance.ID {
return errors.New("start identity was not preserved")
}
worker, err := orbit.NewWorker(client, orbit.WorkerOptions{QueueID: credentials.QueueID, Capacity: 2, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{{Capability: orbit.WorkerCapability{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: normalize}}})
if err != nil {
return err
}
workerCtx, stop := context.WithCancel(ctx)
done := make(chan error, 1)
go func() { done <- worker.Run(workerCtx) }()
waited := false
defer func() {
stop()
if !waited {
<-done
}
}()
var wait orbit.WorkflowStepDetail
for {
wait, err = client.GetWorkflowStep(ctx, accepted.Instance.ID, "receipt")
if err != nil {
return err
}
if wait.Step.State == "WAITING" {
break
}
current, e := client.GetWorkflow(ctx, accepted.Instance.ID)
if e != nil {
return e
}
if current.State == "NEEDS_REVIEW" || current.State == "FAILED" {
return fmt.Errorf("workflow requires attention: %s", current.State)
}
select {
case e := <-done:
waited = true
return fmt.Errorf("Worker stopped: %v", e)
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
if wait.Wait == nil || wait.Step.ActivationSequence == 0 {
return errors.New("wait contract missing")
}
// 示例模拟外部系统的已验证回执。实际系统由独立、具备信号权限的调用者确认业务事实。
signal := orbit.SendWorkflowSignalRequest{Name: *wait.Wait.Name, PayloadVersion: *wait.Wait.PayloadVersion, TargetStepKey: wait.Step.Key, TargetActivationSequence: wait.Step.ActivationSequence, CorrelationKey: wait.Wait.CorrelationKey, Payload: json.RawMessage(`true`), Reason: "simulated verified external receipt"}
sent, err := client.SendWorkflowSignal(ctx, accepted.Instance.ID, operation, signal)
if err != nil {
return err
}
again, err := client.SendWorkflowSignal(ctx, accepted.Instance.ID, operation, signal)
if err != nil || !again.Deduplicated || again.Signal.ID != sent.Signal.ID {
return errors.New("signal identity was not preserved")
}
for {
current, e := client.GetWorkflow(ctx, accepted.Instance.ID)
if e != nil {
return e
}
if current.State == "SUCCEEDED" {
fmt.Printf("workflow=%s state=%s result=%s start_deduplicated=%t signal_deduplicated=%t\n", current.ID, current.State, current.Output, repeated.Deduplicated, again.Deduplicated)
break
}
if current.State == "NEEDS_REVIEW" || current.State == "FAILED" || current.State == "CANCELLED" {
return fmt.Errorf("workflow requires attention: %s", current.State)
}
select {
case e := <-done:
waited = true
return fmt.Errorf("Worker stopped: %v", e)
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
steps, err := client.GetWorkflowSteps(ctx, accepted.Instance.ID)
if err != nil {
return err
}
verified := 0
for _, step := range steps.Steps {
if step.TaskID == nil {
continue
}
source, err := client.GetTaskSource(ctx, *step.TaskID)
if err != nil {
return err
}
if source.Workflow == nil || source.Workflow.WorkflowID != accepted.Instance.ID || source.Workflow.DefinitionVersionID != accepted.Instance.DefinitionVersionID || source.Workflow.StepID != step.ID || source.Workflow.ActivationID != *step.ActivationID || source.Workflow.ActivationSequence != 1 {
return errors.New("workflow task source mismatch")
}
verified++
}
if verified != 3 {
return errors.New("incomplete workflow task sources")
}
fmt.Printf("workflow_sources_verified=%d fixed_version=%s\n", verified, accepted.Instance.Version)
history, err := client.ListWorkflowActivations(ctx, accepted.Instance.ID, "receipt", orbit.HistoryPageRequest{Limit: 20})
if err != nil {
return err
}
if len(history.Items) != 1 {
return errors.New("unexpected receipt activation history")
}
historical, err := client.GetWorkflowActivation(ctx, accepted.Instance.ID, "receipt", history.Items[0].ID)
if err != nil {
return err
}
if historical.Wait == nil || historical.Wait.WinnerSignalID == nil || *historical.Wait.WinnerSignalID != sent.Signal.ID {
return errors.New("historical signal winner missing")
}
signals, err := client.ListWorkflowSignals(ctx, accepted.Instance.ID, orbit.HistoryPageRequest{Limit: 20})
if err != nil {
return err
}
if len(signals.Items) != 1 || signals.Items[0].State != "CONSUMED" || signals.Items[0].ID != sent.Signal.ID {
return errors.New("signal history missing")
}
fmt.Println("workflow_history_verified=true signal_state=CONSUMED")
// 时间线用于审计展示,游标只是倒序翻页位置,不作为可靠消息消费水位。
cursor, eventCount, consumed := "", 0, false
for {
page, err := client.ListWorkflowEvents(ctx, accepted.Instance.ID, orbit.HistoryPageRequest{Limit: 5, Cursor: cursor})
if err != nil {
return err
}
for _, event := range page.Items {
eventCount++
if event.AggregateKind == "workflow_signal" && event.AggregateID == sent.Signal.ID && event.State == "CONSUMED" {
detail, err := client.GetWorkflowEvent(ctx, accepted.Instance.ID, event.ID)
if err != nil {
return err
}
var transition struct {
From string `json:"from"`
ConsumedWaitID string `json:"consumed_wait_id"`
}
if json.Unmarshal(detail.Payload, &transition) != nil || transition.From != "PENDING" || transition.ConsumedWaitID != historical.Wait.ID {
return errors.New("signal transition evidence mismatch")
}
consumed = true
}
}
if page.NextCursor == "" {
break
}
cursor = page.NextCursor
}
if !consumed {
return errors.New("consumed signal event missing")
}
fmt.Printf("workflow_timeline_verified=true events=%d signal_transition=PENDING->CONSUMED\n", eventCount)
stop()
err = <-done
waited = true
if err != nil {
return err
}
stats := worker.Stats()
if stats.Started != 3 || stats.ReportsConfirmed != 3 || stats.Running != 0 {
return errors.New("unexpected task count or incomplete drain")
}
fmt.Printf("worker_drained=true started=%d confirmed=%d\n", stats.Started, stats.ReportsConfirmed)
return nil
}
func normalize(ctx context.Context, execution orbit.WorkerExecution) (json.RawMessage, error) {
var in struct {
Text string `json:"text"`
}
if json.Unmarshal(execution.Task.Payload, &in) != nil {
return nil, &orbit.WorkerFailure{Code: "INVALID_TEXT"}
}
if err := ctx.Err(); err != nil {
return nil, err
}
return json.Marshal(struct {
Text string `json:"text"`
}{strings.ToUpper(strings.TrimSpace(in.Text))})
}