feat: establish Orbit models, migrations and development baseline

This commit is contained in:
2026-09-17 07:04:21 +08:00
commit 4f2935e36f
84 changed files with 4915 additions and 0 deletions

38
pulse/model/attempt.go Normal file
View File

@@ -0,0 +1,38 @@
package model
import (
"time"
core "git.yuntuo.hk/k2safe/orbit/model"
)
// Attempt 表示一次任务执行尝试,保存租约、授权代次与独立结果。
// 对应表 pulse_attempts;这是服务端持久化模型,不直接作为 API 响应。
type Attempt struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// TaskID 关联的 Pulse 任务标识。
TaskID core.ID `db:"task_id"`
// Epoch 该任务的授权代次,同一任务内唯一且递增。
Epoch uint64 `db:"epoch"`
// SessionID 当前执行归属的 Worker 会话标识。
SessionID core.ID `db:"session_id"`
// TokenDigest 执行秘密令牌的 SHA-256 摘要,不输出原令牌。
TokenDigest core.Digest `db:"token_digest"`
// State 当前执行尝试的状态,不能代替整个任务的状态。
State AttemptState `db:"state"`
// LeaseUntil 已确认的租约截止时间,必须用数据库时间核验。
LeaseUntil time.Time `db:"lease_until"`
// AuthorizedAt 平台允许处理器开始的时间;不能证明业务副作用已经发生;可为空,表示尚未设置该关联或值。
AuthorizedAt *time.Time `db:"authorized_at"`
// EndedAt 本次尝试结束时间;尚未结束时为空。
EndedAt *time.Time `db:"ended_at"`
// ErrorCode 稳定错误分类,供恢复策略判断;不得写入敏感原始错误;可为空,表示尚未设置该关联或值。
ErrorCode *string `db:"error_code"`
// Result 已保存的结果或结果引用 JSON;未取得结果时为空。
Result core.JSON `db:"result"`
// ResultDigest 结果语义摘要,用于识别确认重报与冲突结果;可为空,表示尚未设置该关联或值。
ResultDigest *core.Digest `db:"result_digest"`
}
func (Attempt) TableName() string { return "pulse_attempts" }

View File

@@ -0,0 +1,28 @@
package model
import (
"time"
core "git.yuntuo.hk/k2safe/orbit/model"
)
// AttemptReceipt 表示执行操作的持久化确认,用于断线或确认丢失后的幂等补取。
// 对应表 pulse_attempt_receipts;这是服务端持久化模型,不直接作为 API 响应。
type AttemptReceipt struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// AttemptID 关联的独立执行尝试标识。
AttemptID core.ID `db:"attempt_id"`
// Operation 稳定操作类型,用于区分不同接口的幂等作用域。
Operation string `db:"operation"`
// MessageID 可靠操作消息身份,跨重连重报时保持稳定。
MessageID core.ID `db:"message_id"`
// RequestDigest 规范化请求语义的 SHA-256 摘要,用于识别同键不同内容。
RequestDigest core.Digest `db:"request_digest"`
// Response 首次可靠提交后的响应 JSON,供重复请求补取确认。
Response core.JSON `db:"response"`
// ExpiresAt 过期时间,统一 UTC;到期后的行为由对应资源策略决定。
ExpiresAt time.Time `db:"expires_at"`
}
func (AttemptReceipt) TableName() string { return "pulse_attempt_receipts" }

View File

@@ -0,0 +1,22 @@
package model
import (
core "git.yuntuo.hk/k2safe/orbit/model"
)
// ConcurrencyLimit 表示跨节点共享的并发限制,实际额度更新必须处于事务内。
// 对应表 pulse_concurrency_limits;这是服务端持久化模型,不直接作为 API 响应。
type ConcurrencyLimit struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// ScopeKey 资源作用域标识;空字符串表示当前操作的默认作用域。
ScopeKey string `db:"scope_key"`
// KeyDigest 稳定幂等键或资源键的 SHA-256 摘要。
KeyDigest core.Digest `db:"key_digest"`
// MaxPermits 该共享资源允许占用的许可上限。
MaxPermits uint32 `db:"max_permits"`
// UsedPermits 已占用的有效许可数,必须与许可记录同事务维护。
UsedPermits uint32 `db:"used_permits"`
}
func (ConcurrencyLimit) TableName() string { return "pulse_concurrency_limits" }

View File

@@ -0,0 +1,20 @@
package model
import (
core "git.yuntuo.hk/k2safe/orbit/model"
)
// ExecutionGate 表示通用执行门控,协调暂停与新执行授权的竞争。
// 对应表 pulse_execution_gates;这是服务端持久化模型,不直接作为 API 响应。
type ExecutionGate struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// Paused 暂停标记;停止新的受控工作,不表示在途请求已停止。
Paused bool `db:"paused"`
// ControlRevision 暂停、继续等控制操作的版本,避免旧操作覆盖新决定。
ControlRevision uint64 `db:"control_revision"`
// Reason 操作原因或处理说明,需要保留审计信息;可为空,表示尚未设置该关联或值。
Reason *string `db:"reason"`
}
func (ExecutionGate) TableName() string { return "pulse_execution_gates" }

30
pulse/model/occurrence.go Normal file
View File

@@ -0,0 +1,30 @@
package model
import (
"time"
core "git.yuntuo.hk/k2safe/orbit/model"
)
// Occurrence 表示一个原计划触发点的处理记录,包含触发、跳过或合并决定。
// 对应表 pulse_schedule_occurrences;这是服务端持久化模型,不直接作为 API 响应。
type Occurrence struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// ScheduleID 所属调度计划标识。
ScheduleID core.ID `db:"schedule_id"`
// ScheduleVersion 该触发点采用的计划版本号。
ScheduleVersion uint64 `db:"schedule_version"`
// ScheduledAt 原计划触发时间,统一 UTC;参与触发去重。
ScheduledAt time.Time `db:"scheduled_at"`
// Decision 触发处理决定:已触发、已跳过或已合并。
Decision string `db:"decision"`
// TaskID 关联的 Pulse 任务标识;可为空,表示尚未设置该关联或值。
TaskID *core.ID `db:"task_id"`
// CoveredUntil 合并触发覆盖的结束边界;没有合并范围时为空。
CoveredUntil *time.Time `db:"covered_until"`
// Reason 操作原因或处理说明,需要保留审计信息;可为空,表示尚未设置该关联或值。
Reason *string `db:"reason"`
}
func (Occurrence) TableName() string { return "pulse_schedule_occurrences" }

24
pulse/model/permit.go Normal file
View File

@@ -0,0 +1,24 @@
package model
import (
"time"
core "git.yuntuo.hk/k2safe/orbit/model"
)
// Permit 表示一次执行尝试占用的并发许可及释放历史。
// 对应表 pulse_permits;这是服务端持久化模型,不直接作为 API 响应。
type Permit struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// LimitID 关联的并发限制记录标识。
LimitID core.ID `db:"limit_id"`
// AttemptID 关联的独立执行尝试标识。
AttemptID core.ID `db:"attempt_id"`
// LeaseUntil 已确认的租约截止时间,必须用数据库时间核验。
LeaseUntil time.Time `db:"lease_until"`
// ReleasedAt 许可释放时间;为空表示尚未记录释放。
ReleasedAt *time.Time `db:"released_at"`
}
func (Permit) TableName() string { return "pulse_permits" }

33
pulse/model/policy.go Normal file
View File

@@ -0,0 +1,33 @@
package model
import (
"errors"
"time"
)
// RetryPolicy 保存到受理时冻结的策略快照,时长统一使用毫秒。
// MaxAttempts 包含首次尝试,不能理解为首次之外的重试次数。
type RetryPolicy struct {
// MaxAttempts 最多执行尝试数,包含首次尝试。
MaxAttempts uint32 `json:"max_attempts"`
// InitialDelayMS 首次重试前的等待时长,单位毫秒。
InitialDelayMS uint64 `json:"initial_delay_ms"`
// MaxDelayMS 重试等待时长上限,单位毫秒。
MaxDelayMS uint64 `json:"max_delay_ms"`
// AttemptTimeoutMS 单次执行尝试的超时时长,单位毫秒。
AttemptTimeoutMS uint64 `json:"attempt_timeout_ms"`
}
func (p RetryPolicy) Validate() error {
maxMS := uint64((1<<63 - 1) / int64(time.Millisecond))
if p.MaxAttempts == 0 || p.AttemptTimeoutMS == 0 || p.AttemptTimeoutMS > maxMS {
return errors.New("finite attempt count and representable positive timeout required")
}
if p.InitialDelayMS > p.MaxDelayMS || p.MaxDelayMS > maxMS {
return errors.New("invalid retry delay range")
}
if p.MaxAttempts > 1 && p.InitialDelayMS == 0 {
return errors.New("retry delay must be positive")
}
return nil
}

24
pulse/model/queue.go Normal file
View File

@@ -0,0 +1,24 @@
package model
import (
core "git.yuntuo.hk/k2safe/orbit/model"
)
// Queue 表示任务路由与资源控制队列,不要求每个租户单独创建队列。
// 对应表 pulse_queues;这是服务端持久化模型,不直接作为 API 响应。
type Queue struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// Name 可读名称,唯一性范围由对应数据表约束。
Name string `db:"name"`
// Paused 暂停标记;停止新的受控工作,不表示在途请求已停止。
Paused bool `db:"paused"`
// MaxConcurrency 允许同时持有的有效执行许可上限,必须大于零。
MaxConcurrency uint32 `db:"max_concurrency"`
// RatePerSecond 每秒新执行的速率配置;实际限流由共享协调逻辑落实。
RatePerSecond uint32 `db:"rate_per_second"`
// Policy 队列资源与执行配置的 JSON 文档。
Policy core.JSON `db:"policy"`
}
func (Queue) TableName() string { return "pulse_queues" }

24
pulse/model/schedule.go Normal file
View File

@@ -0,0 +1,24 @@
package model
import (
"time"
core "git.yuntuo.hk/k2safe/orbit/model"
)
// Schedule 表示调度计划的当前控制状态,规则内容由不可变版本保存。
// 对应表 pulse_schedules;这是服务端持久化模型,不直接作为 API 响应。
type Schedule struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// Name 可读名称,唯一性范围由对应数据表约束。
Name string `db:"name"`
// CurrentVersion 当前生效的计划版本号,发布时与版本记录原子更新。
CurrentVersion uint64 `db:"current_version"`
// Paused 暂停标记;停止新的受控工作,不表示在途请求已停止。
Paused bool `db:"paused"`
// NextFireAt 下一计划触发时间;没有后续触发时为空。
NextFireAt *time.Time `db:"next_fire_at"`
}
func (Schedule) TableName() string { return "pulse_schedules" }

View File

@@ -0,0 +1,34 @@
package model
import (
"time"
core "git.yuntuo.hk/k2safe/orbit/model"
)
// ScheduleVersion 表示已发布的不可变计划版本,记录规则与任务模板。
// 对应表 pulse_schedule_versions;这是服务端持久化模型,不直接作为 API 响应。
type ScheduleVersion struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// ScheduleID 所属调度计划标识。
ScheduleID core.ID `db:"schedule_id"`
// Version 不可变发布版本;同版本不同内容必须拒绝。
Version uint64 `db:"version"`
// EffectiveAt 该计划版本开始生效的时间。
EffectiveAt time.Time `db:"effective_at"`
// Timezone 计划计算使用的 IANA 时区名称。
Timezone string `db:"timezone"`
// Specification 版本化规则定义 JSON;发布后禁止原地修改。
Specification core.JSON `db:"specification"`
// TaskTemplate 每次触发创建任务时采用的输入与策略模板。
TaskTemplate core.JSON `db:"task_template"`
// MisfirePolicy 漏跑策略:跳过、合并或有界补跑。
MisfirePolicy string `db:"misfire_policy"`
// OverlapPolicy 上次未完成时的策略:并行、串行或跳过。
OverlapPolicy string `db:"overlap_policy"`
// CatchupLimit 单次漏跑补偿上限;补跑策略必须配置正数。
CatchupLimit uint32 `db:"catchup_limit"`
}
func (ScheduleVersion) TableName() string { return "pulse_schedule_versions" }

57
pulse/model/state.go Normal file
View File

@@ -0,0 +1,57 @@
package model
// TaskState 表示任务状态,与执行尝试状态独立;任务终态不重新打开。
type TaskState string
const (
TaskWaiting TaskState = "WAITING" // 等待派发、延时或退避到期。
TaskLeased TaskState = "LEASED" // 已领取但尚未获得开始授权。
TaskRunning TaskState = "RUNNING" // 已授权处理器开始执行。
TaskNeedsReview TaskState = "NEEDS_REVIEW" // 结果未知或需要人工核查。
TaskSucceeded TaskState = "SUCCEEDED" // 业务处理器成功结果已保存。
TaskFailed TaskState = "FAILED" // 已知失败且不再继续执行。
TaskCancelled TaskState = "CANCELLED" // 取消已确认,不能理解为业务回滚。
)
func (s TaskState) Terminal() bool {
return s == TaskSucceeded || s == TaskFailed || s == TaskCancelled
}
// CanTransitionTo 只检查状态图,不能代替数据库事务中的权限、版本、租约、
// 暂停门控、副作用证据和重试预算校验。
func (s TaskState) CanTransitionTo(next TaskState) bool {
switch s {
case TaskWaiting:
return next == TaskLeased || next == TaskCancelled || next == TaskFailed
case TaskLeased:
return next == TaskRunning || next == TaskWaiting || next == TaskCancelled || next == TaskFailed
case TaskRunning:
return next == TaskSucceeded || next == TaskWaiting || next == TaskFailed || next == TaskNeedsReview || next == TaskCancelled
case TaskNeedsReview:
return next == TaskSucceeded || next == TaskFailed
default:
return false
}
}
type AttemptState string
const (
AttemptLeased AttemptState = "LEASED"
AttemptRunning AttemptState = "RUNNING"
AttemptSucceeded AttemptState = "SUCCEEDED"
AttemptFailed AttemptState = "FAILED"
AttemptCancelled AttemptState = "CANCELLED"
AttemptExpired AttemptState = "EXPIRED"
AttemptUnknown AttemptState = "UNKNOWN"
)
// RecoverySafety 表示恢复安全等级,空值或未知值默认需要核查。
type RecoverySafety string
const (
RecoveryIdempotent RecoverySafety = "IDEMPOTENT" // 接入方明确保证可幂等恢复。
RecoveryReconcileOnly RecoverySafety = "RECONCILE_ONLY" // 恢复前必须查询结果或人工核查。
)
func (s RecoverySafety) CanRecoverAutomatically() bool { return s == RecoveryIdempotent }

29
pulse/model/state_test.go Normal file
View File

@@ -0,0 +1,29 @@
package model
import "testing"
func TestRecoveryDoesNotReopenTerminalOrUnknownWork(t *testing.T) {
for _, state := range []TaskState{TaskSucceeded, TaskFailed, TaskCancelled, TaskNeedsReview, TaskState("new-future-state")} {
if state.CanTransitionTo(TaskWaiting) || state.CanTransitionTo(TaskRunning) {
t.Fatalf("unsafe automatic restart from %s", state)
}
}
for _, safety := range []RecoverySafety{"", RecoveryReconcileOnly, "future"} {
if safety.CanRecoverAutomatically() {
t.Fatalf("unsafe default: %s", safety)
}
}
if !TaskRunning.CanTransitionTo(TaskNeedsReview) {
t.Fatal("unknown effects cannot be preserved")
}
}
func TestRetryPolicyRejectsUnboundedOrOverflowingParameters(t *testing.T) {
for _, p := range []RetryPolicy{{}, {MaxAttempts: 2, AttemptTimeoutMS: 1}, {MaxAttempts: 1, AttemptTimeoutMS: ^uint64(0)}, {MaxAttempts: 2, AttemptTimeoutMS: 1, InitialDelayMS: 100, MaxDelayMS: 1}} {
if p.Validate() == nil {
t.Fatalf("accepted unsafe policy: %+v", p)
}
}
if err := (RetryPolicy{MaxAttempts: 3, AttemptTimeoutMS: 1000, InitialDelayMS: 100, MaxDelayMS: 5000}).Validate(); err != nil {
t.Fatal(err)
}
}

68
pulse/model/task.go Normal file
View File

@@ -0,0 +1,68 @@
package model
import (
"time"
core "git.yuntuo.hk/k2safe/orbit/model"
)
// Task 表示任务主记录,执行尝试另存历史,终态不自动回退。
// 对应表 pulse_tasks;这是服务端持久化模型,不直接作为 API 响应。
type Task struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// QueueID 任务所属队列,必须属于同一命名空间。
QueueID core.ID `db:"queue_id"`
// GateID 可选执行门控;为空表示没有流程级门控,仍受队列与权限控制。
GateID *core.ID `db:"gate_id"`
// State 任务状态;与执行尝试状态分开,未知业务效果进入待核查。
State TaskState `db:"state"`
// Handler 业务 Worker 注册的处理器名称。
Handler string `db:"handler"`
// HandlerVersion 处理器兼容版本,受理后不得静默替换。
HandlerVersion string `db:"handler_version"`
// PayloadVersion 输入参数的解释版本。
PayloadVersion string `db:"payload_version"`
// Payload 版本化输入或事件内容;仅承载通用 JSON,不解释行业规则。
Payload core.JSON `db:"payload"`
// Result 已保存的结果或结果引用 JSON;未取得结果时为空。
Result core.JSON `db:"result"`
// PolicySnapshot 受理时冻结的实际生效策略 JSON。
PolicySnapshot core.JSON `db:"policy_snapshot"`
// RecoverySafety 恢复安全等级;未知值按需要核查处理,不能默认自动重跑。
RecoverySafety RecoverySafety `db:"recovery_safety"`
// BusinessKey 业务侧关联键,用于检索追踪,不自动提供业务幂等;可为空,表示尚未设置该关联或值。
BusinessKey *string `db:"business_key"`
// OwnerKind 来源资源类型,例如流程激活;为空表示无来源关联。
OwnerKind *string `db:"owner_kind"`
// OwnerID 来源资源标识,必须与来源类型同时有值或同时为空。
OwnerID *core.ID `db:"owner_id"`
// Priority 同一调度范围内的优先级,不承诺跨队列全局顺序。
Priority int16 `db:"priority"`
// AvailableAt 最早可被领取或处理的时间,统一 UTC。
AvailableAt time.Time `db:"available_at"`
// StartDeadlineAt 最晚允许新尝试开始的时间;为空时仅受总时限约束。
StartDeadlineAt *time.Time `db:"start_deadline_at"`
// DeadlineAt 总截止时间,统一 UTC;到期不能证明外部业务没有生效。
DeadlineAt time.Time `db:"deadline_at"`
// AttemptTimeoutMS 单次尝试的超时时长,单位毫秒。
AttemptTimeoutMS uint64 `db:"attempt_timeout_ms"`
// MaxAttempts 最多执行尝试数,包含首次尝试。
MaxAttempts uint32 `db:"max_attempts"`
// AttemptCount 已产生的执行尝试数,恢复时不得重置。
AttemptCount uint32 `db:"attempt_count"`
// CurrentAttemptID 当前关联的执行尝试,必须属于本任务;可为空,表示尚未设置该关联或值。
CurrentAttemptID *core.ID `db:"current_attempt_id"`
// LeaseEpoch 递增的执行或消费代次,用于隔离旧授权。
LeaseEpoch uint64 `db:"lease_epoch"`
// LeaseUntil 当前执行授权截止时间;没有有效授权时为空。
LeaseUntil *time.Time `db:"lease_until"`
// CancelRequestedAt 取消意图的持久化时间;不等于取消已完成;可为空,表示尚未设置该关联或值。
CancelRequestedAt *time.Time `db:"cancel_requested_at"`
// RetryOf 来源记录标识,人工重新发起时用于保留历史关联;可为空,表示尚未设置该关联或值。
RetryOf *core.ID `db:"retry_of"`
// CompletedAt 终结时间;非终态为空,进入终态时必须写入。
CompletedAt *time.Time `db:"completed_at"`
}
func (Task) TableName() string { return "pulse_tasks" }

View File

@@ -0,0 +1,34 @@
package model
import (
"time"
core "git.yuntuo.hk/k2safe/orbit/model"
)
// WorkerSession 表示一次 Worker 连接的持久化身份与能力快照。
// 对应表 pulse_worker_sessions;这是服务端持久化模型,不直接作为 API 响应。
type WorkerSession struct {
// Record 包含命名空间、记录标识、并发版本和 UTC 时间。
core.Record
// WorkerInstanceID Worker 进程实例标识,重连保留,进程重启应更换。
WorkerInstanceID core.ID `db:"worker_instance_id"`
// CredentialID 关联的命名空间凭据标识。
CredentialID core.ID `db:"credential_id"`
// NodeID 承载当前连接的平台节点标识。
NodeID core.ID `db:"node_id"`
// SessionEpoch 该 Worker 实例的连接代次,用于隔离旧会话。
SessionEpoch uint64 `db:"session_epoch"`
// Capabilities 已登记的处理器版本、队列等能力快照。
Capabilities core.JSON `db:"capabilities"`
// Capacity Worker 声明的执行容量,仍受平台权限和配额约束。
Capacity uint32 `db:"capacity"`
// CapacitySequence 会话内容量消息序号,重复上报不得重复增加额度。
CapacitySequence uint64 `db:"capacity_sequence"`
// Draining 是否正在排空连接;排空时不接收新任务。
Draining bool `db:"draining"`
// ExpiresAt 过期时间,统一 UTC;到期后的行为由对应资源策略决定。
ExpiresAt time.Time `db:"expires_at"`
}
func (WorkerSession) TableName() string { return "pulse_worker_sessions" }