feat: provide foreign-key-free fresh install baseline

This commit is contained in:
2026-09-20 01:25:59 +08:00
parent a722d19cd9
commit 88ec4545b4
14 changed files with 1081 additions and 27 deletions

View File

@@ -4,7 +4,7 @@
- 服务端分为公共能力、Pulse 与 Weave;SDK 是独立 Go module,不引用服务端模型或存储实现。
- 新增或修改数据模型必须提供中文注释,覆盖模型职责及每个持久化字段的含义。明确单位、可空语义、版本/幂等/租约/预算等约束,避免仅重复字段名。
- SQL 数据表和字段同步维护中文 COMMENT;已应用的迁移不得回改,后续演进新增前向迁移。
- 数据库禁止外键及级联删除。关联关系由服务端在同一事务中按命名空间校验;保留查询索引、唯一约束和 CHECK。历史外键由 000008 前向移除,后续迁移不得重新引入,真实 MySQL 回归必须断言外键为 0。
- 数据库禁止外键及级联删除。关联关系由服务端在同一事务中按命名空间校验;保留查询索引、唯一约束和 CHECK。历史外键由 000008 前向移除,空库使用当前版本无外键基线,后续迁移不得重新引入,真实 MySQL 回归必须断言外键为 0。
- Pulse 涉及用量的事务先锁应用额度再锁队列,恢复和调度遵守同一顺序;不得在已锁队列后新增应用额度锁。应用额度与身份行分离,不能升级认证共享锁。
- 优先级冻结为 0–9,每级30秒排序优势;dispatch_at 是数据库生成排序字段,不能替代 available_at 的实际就绪检查。节点按认证应用与队列轮转领取,内存轮转/空探测缓存不得授予执行权或改变持久化配额。
- 状态变更、租约、门控、许可、事件及审计必须按设计的事务边界实现,不用内存状态代替持久化确认。

View File

@@ -12,7 +12,7 @@ Orbit 是面向多个业务项目的通用任务调度与流程编排平台,
Weave已提供[定义发布](docs/api/workflow-definitions.md)、[实例启动、步骤详情、暂停/继续/取消、信号和操作审计](docs/api/workflows.md),以及[六类人工核查恢复](docs/api/workflow-recovery.md)的HTTP与独立Go SDK。持久运行器支持顺序、分支、有限并行、TIMER、SIGNAL及有界POLL,恢复沿用原预算与截止。[并行作业与回执示例](docs/examples/workflow.md)提供完整调用链;[历史激活/轮次/信号及信号审计](docs/api/workflow-history.md)已接入;[实例筛选分页](docs/api/workflow-queries.md)已接入,[状态事件时间线](docs/api/workflow-events.md)已接入,独立[轮询](docs/examples/workflow-poll.md)与[恢复](docs/examples/workflow-recovery.md)示例已接入,已补[版本/暂停竞争/全停回归](docs/reports/phase-4-5-races.md),功能阶段证据见[P4/P5核对](docs/reports/phase-4-5.md),提交断线及派发未送达见[故障边界](docs/reports/phase-6-fault-boundaries.md);[任务流程来源](docs/api/task-source.md#5-流程固定来源)已接入,P4/P5功能阶段已通过,P6生产验证和P7发布交付继续推进。
数据库禁止外键,关联一致性通过服务端事务校验,索引、唯一约束和 CHECK 保留;readiness 同时检查迁移及零外键要求。[运行观测](docs/api/observability.md)提供固定基数 HTTP、数据库池和后台批次指标;发布前可执行只读的 [`orbit-admin verify`](docs/api/operations.md) 核验迁移、零外键和固定索引。这不表示完整平台已具备生产服务能力。验证证据见 [P0](docs/reports/phase-0.md)、[P1](docs/reports/phase-1.md)、[P2 阶段验收](docs/reports/phase-2.md)、[安全重试](docs/reports/phase-3-safe-retry.md)、[计划调度](docs/reports/phase-3-schedules.md)、[取消与无外键回归](docs/reports/phase-3-cancellation.md) 及 [队列资源回归](docs/reports/phase-3-queue-resources.md)。
数据库禁止外键,关联一致性通过服务端事务校验,索引、唯一约束和 CHECK 保留;新装库使用无外键当前版本基线,已有库沿不可变迁移链升级;readiness 同时检查迁移及零外键要求。[运行观测](docs/api/observability.md)提供固定基数 HTTP、数据库池和后台批次指标;发布前可执行只读的 [`orbit-admin verify`](docs/api/operations.md) 核验迁移、零外键和固定索引。这不表示完整平台已具备生产服务能力。验证证据见 [P0](docs/reports/phase-0.md)、[P1](docs/reports/phase-1.md)、[P2 阶段验收](docs/reports/phase-2.md)、[安全重试](docs/reports/phase-3-safe-retry.md)、[计划调度](docs/reports/phase-3-schedules.md)、[取消与无外键回归](docs/reports/phase-3-cancellation.md) 及 [队列资源回归](docs/reports/phase-3-queue-resources.md)。
## 目录

View File

@@ -23,19 +23,21 @@ func main() {
}
func run() error {
if len(os.Args) < 2 {
return errors.New("usage: orbit-admin bootstrap|verify|retention inventory|credential status|rotate|revoke|apply")
return errors.New("usage: orbit-admin bootstrap|verify|observe snapshot|retention inventory|credential status|rotate|revoke|apply")
}
switch os.Args[1] {
case "bootstrap":
return runBootstrap(os.Args[2:])
case "verify":
return runVerify(os.Args[2:])
case "observe":
return runObserve(os.Args[2:])
case "retention":
return runRetention(os.Args[2:])
case "credential":
return runCredential(os.Args[2:])
}
return errors.New("usage: orbit-admin bootstrap|verify|retention inventory|credential status|rotate|revoke|apply")
return errors.New("usage: orbit-admin bootstrap|verify|observe snapshot|retention inventory|credential status|rotate|revoke|apply")
}
func runVerify(args []string) error {
@@ -69,6 +71,42 @@ func runVerify(args []string) error {
return nil
}
// runObserve exposes the same bounded, read-only snapshot used by the private
// metrics sampler. It is deliberately an operator command rather than a
// tenant-facing API: values are per database node and may be truncated.
func runObserve(args []string) error {
if len(args) != 1 || args[0] != "snapshot" {
return errors.New("usage: orbit-admin observe snapshot")
}
cfg, err := config.Load()
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
db, err := mysql.Open(ctx, cfg.MySQLDSN, 2, 1)
if err != nil {
return errors.New("database unavailable")
}
defer db.Close()
snapshot, err := mysql.ReadOperationalSnapshot(ctx, db)
if err != nil {
return errors.New("operational snapshot unavailable")
}
output := struct {
Snapshot mysql.OperationalSnapshot `json:"snapshot"`
SampleLimit int `json:"sample_limit"`
EventWindowSeconds int `json:"event_window_seconds"`
Scope string `json:"scope"`
}{
Snapshot: snapshot,
SampleLimit: 10000,
EventWindowSeconds: 300,
Scope: "single database node; bounded sample, not a global exact count",
}
return json.NewEncoder(os.Stdout).Encode(output)
}
func runRetention(args []string) error {
if len(args) == 0 || args[0] != "inventory" {
return errors.New("usage: orbit-admin retention inventory --before RFC3339")

View File

@@ -126,7 +126,7 @@ P2 服务端已新增会话隔离、两步派发、租约、并发许可、结
000007_task_cancellation 允许尝试证据记录 CANCELLED,不新增实体;取消意图、结果和历史不确定效果的规则见 [取消契约](api/cancellation.md)。
000008_no_foreign_keys 移除历史 70 个外键,保留索引、唯一约束、CHECK、触发器和数据。旧迁移已在回归库应用,按不可变校验和约定保留原文件;新装按顺序运行至当前版本,最终结构同样为零外键。后续迁移禁止再引入外键。升级旧库时先停用旧服务,应用完整迁移再启动新版;迁移需要 DDL 锁,应在目标部署环境演练维护窗口。
000008_no_foreign_keys 移除历史 70 个外键,保留索引、唯一约束、CHECK、触发器和数据。旧迁移已在回归库应用,按不可变校验和约定保留原文件;已有库继续按历史链前向升级。新装库由迁移器使用当前版本的无外键基线直接建表,再登记 1–28 的不可变校验和,因此初始化过程也不会先创建历史外键;基线版本变化时必须随新迁移重新生成并回归。后续迁移禁止再引入外键。升级旧库时先停用旧服务,应用完整迁移再启动新版;迁移需要 DDL 锁,应在目标部署环境演练维护窗口。
000009_queue_resources 增加 Queue.MaxUnfinishedTasks、UnfinishedTasks、StartWindowUntil、StartsInWindow,Go 字段与 SQL COMMENT 均有中文语义,实体仍为 32。未终结任务按 WAITING/LEASED/RUNNING/NEEDS_REVIEW 回填;窗口初始为空、计数为零。运行计数与任务/开始授权同事务维护,不改变队列配置 revision,配置变更不能重置它们。降额允许已有使用量暂时超过新上限,不删除任务或撤销已有许可;详见 [队列契约](api/queues.md) 和 [资源回归](reports/phase-3-queue-resources.md)。

View File

@@ -58,8 +58,8 @@ P4/P5定时等待与后续查询轮次的实现及回归记录见[时钟报告](
### P6 首批实施顺序
1. 已补[首批可复现故障工具](reports/phase-6-fault-boundaries.md):A04“派发已提交但消息尚未送达”、W01/W13事务提交结果不明,以及W16最终回执/旧PENDING结果重放/下一轮竞争。每个切点必须有实际数据库或进程证据,不能用响应错误推断事务是否已提交,也不能用普通SIGNAL竞争替代最终查询场景。
2. 已实现[基础运行观测](api/observability.md)及[有界业务状态与事件采样](reports/phase-6-business-observability.md):HTTP/WS、数据库连接池、后台批次、固定任务/交付/流程/Inbox状态计数、最近事件类别、终态耗时样本、固定事件阶段耗时、最早年龄、截断/新鲜度指标和可离线验证的告警规则;独立采集监听默认关闭,固定指标目录拒绝动态标签;发布前只读结构核验由[`orbit-admin verify`](api/operations.md)提供。仍需端到端外部效果延迟、全局持久吞吐、Inbox从事件产生到推进完成的精确阶段延迟、全局发送队列、多节点聚合及生产采集/通知验证。
1. 已补[首批可复现故障工具](reports/phase-6-fault-boundaries.md):A04“派发已提交但消息尚未送达”、W01/W13事务提交结果不明,以及W16最终回执/旧PENDING结果重放/下一轮竞争。迁移器同时提供当前版本无外键新装基线;每个切点必须有实际数据库或进程证据,不能用响应错误推断事务是否已提交,也不能用普通SIGNAL竞争替代最终查询场景。
2. 已实现[基础运行观测](api/observability.md)及[有界业务状态与事件采样](reports/phase-6-business-observability.md):HTTP/WS、数据库连接池、后台批次、固定任务/交付/流程/Inbox状态计数、最近事件类别、终态耗时样本、固定事件阶段耗时、最早年龄、截断/新鲜度指标和可离线验证的告警规则;独立采集监听默认关闭,固定指标目录拒绝动态标签;发布前只读结构核验由[`orbit-admin verify`](api/operations.md)提供,运维人员也可用 `orbit-admin observe snapshot` 获取同一有界单节点快照。仍需端到端外部效果延迟、全局持久吞吐、Inbox从事件产生到推进完成的精确阶段延迟、全局发送队列、多节点聚合及生产采集/通知验证。
3. 已补齐受保护管理环境中的固定凭据操作文件、轮换、撤销、幂等回执和脱敏管理审计;轮换保留授权绑定并要求新令牌重新建立会话,旧 Worker 会话被排空且立即过期,撤销允许在停用范围内执行且不改写历史记录。已用一次性 MySQL 定向验证旧令牌失效、新令牌重绑和零外键;仍需补齐提交不明故障切点、多节点安全重绑演练、凭据最小权限配置核对和生产密钥托管/审计演练。
4. 已补[`orbit-admin retention inventory`](api/retention.md)只读盘点和[阶段报告](reports/phase-6-retention-inventory.md),能区分过期幂等、确定终态、未知效果、未终结流程、已确认/未确认交付、已消费/未消费Inbox、事件和审计规模;它不执行归档或删除。后续仍需评审各类保留周期、归档格式、受保护记录核对,再执行备份恢复、兼容升级和六类容量负载。未确认交付、有效幂等身份、当前/历史执行证据和未终结流程不能被普通清理破坏。生产HA及RPO/RTO继续按实际拓扑验证,不缩减为本机多进程检查。
5. 已补[`orbit-load`](api/load.md)有界受理负载工具:固定请求形状、并发上限、每请求超时、无自动重试、延迟百分位和有限错误分类;已完成一次隔离 MySQL + HTTP 小样本实测,证据见[负载报告](reports/phase-6-load.md)。标准 Go 1.27.1 隔离回归仍需在工具链恢复后复跑;该工具只提供受理层测量,真实 Worker/流程/数据库拓扑容量仍需独立报告。

View File

@@ -21,6 +21,7 @@
| 任务 `priority` 与节点领取池 | P3 已实现有界优先级加速、应用/队列轮转 | [优先级与公平派发](priority-and-fairness.md) |
| 凭据校验、初始化、固定操作文件轮换/撤销与回执重放 CLI | P1/P6 已实现;不提供公网管理路由,测试专用提交不明切点已回归 | [鉴权](authentication.md)、[凭据报告](../reports/phase-6-credentials.md) |
| `orbit-admin verify` 发布前结构核验 | 已实现只读迁移、零外键和 000026–000028 固定索引检查 | [运维核验](operations.md) |
| `orbit-admin observe snapshot` 只读运行快照 | 已实现固定状态、事件窗口和阶段耗时的有界单节点快照 | [运维核验](operations.md) |
| `orbit-admin retention inventory` 保留清单 | 已实现带 UTC 截止时间的只读盘点;不执行归档、删除或权限变更 | [保留清单](retention.md) |
| `sdk/go/cmd/orbit-load` 有界受理负载工具 | 已实现固定并发、无自动重试、延迟百分位和错误分类;只用于隔离环境 | [负载工具](load.md) |
| `scripts/test-backup-restore.sh` 隔离备份恢复演练 | 已实现一次性 MySQL 逻辑备份、独立恢复、核心计数及零外键核验 | [备份恢复演练](backup-restore.md) |

View File

@@ -16,3 +16,22 @@ ORBIT_MYSQL_DSN='user:password@tcp(127.0.0.1:3306)/orbit' \
`complete=true` 表示当前二进制的迁移账本、Orbit 表零外键,以及 000026–000028 的六个有界观测索引均满足要求。命令不会证明备份可恢复、复制已确认、业务副作用已核对或生产 RPO/RTO 达标;这些仍需隔离环境恢复演练和真实拓扑验收。失败时会先输出不含 DSN/密钥的检查结果,再以非零状态退出。
发布流程应先停止不兼容的旧写入节点,在隔离环境和目标维护窗口应用 `orbit-migrate up`,随后执行 `orbit-admin verify`;只有输出完整且人工核对备份、权限、连接路由后才启动新版本。verify 不是运行时协调器,多个节点可并发读取,但不能替代一次性迁移锁。
## 2. 只读运行快照
需要在没有开启指标监听时查看平台积压趋势,可执行:
```sh
ORBIT_MYSQL_DSN='user:password@tcp(127.0.0.1:3306)/orbit' \
go run ./cmd/orbit-admin observe snapshot
```
命令只执行固定索引上的只读查询,输出 `snapshot`、`sample_limit`、`event_window_seconds` 和 `scope`。任务、交付、流程、Inbox按固定状态分别采样,每个状态最多10000条;事件和阶段耗时只看最近5分钟。达到上限时 `snapshot.truncated=true`,计数只能解释为本节点有界样本,不能作为全局精确数量。命令不接受租户、状态或表名参数,也不执行修复、归档、删除和权限变更。
示例:
```json
{"snapshot":{"task_states":{"WAITING":0,"LEASED":0,"RUNNING":0,"NEEDS_REVIEW":0,"SUCCEEDED":0,"FAILED":0,"CANCELLED":0},"task_oldest_age_seconds":0,"delivery_states":{"PENDING":0,"LEASED":0,"ACKED":0,"EXPIRED":0},"delivery_oldest_age_seconds":0,"workflow_states":{"RUNNING":0,"WAITING":0,"CANCELLING":0,"NEEDS_REVIEW":0,"SUCCEEDED":0,"FAILED":0,"CANCELLED":0},"workflow_oldest_age_seconds":0,"inbox_states":{"PENDING":0,"LEASED":0,"PROCESSED":0,"QUARANTINED":0},"inbox_oldest_age_seconds":0,"truncated":false,"recent_events":{},"task_terminal_latency_seconds":0,"task_terminal_latency_max_seconds":0,"workflow_terminal_latency_seconds":0,"workflow_terminal_latency_max_seconds":0,"task_accept_to_start_latency_seconds":0,"task_accept_to_start_latency_max_seconds":0,"task_accept_to_terminal_latency_seconds":0,"task_accept_to_terminal_latency_max_seconds":0,"workflow_start_to_terminal_latency_seconds":0,"workflow_start_to_terminal_latency_max_seconds":0},"sample_limit":10000,"event_window_seconds":300,"scope":"single database node; bounded sample, not a global exact count"}
```
它与 `/metrics` 共享同一有界读取实现,但不会改变指标注册器或业务状态;遇到数据库超时会返回非零状态,调用方应保留上一次成功快照并排查数据库。

View File

@@ -0,0 +1,24 @@
# P6 无外键新装基线验证
验证日期:2026-09-20。此次补齐的是数据库初始化边界:历史迁移文件保持不可变,已有库仍从原账本前向升级;空库由迁移器直接执行当前版本基线,再登记 1–28 的迁移名称和校验和。基线不包含 `FOREIGN KEY`、`REFERENCES`、迁移账本表或固定 `DEFINER`,因此新库初始化不会先创建历史外键。
## 实现
- [迁移入口](../../internal/storage/mysql/migrate.go) 在取得数据库级迁移锁后区分空库和已有 Orbit 表;存在业务表但没有账本,或账本为空而已有业务表时拒绝猜测历史。
- [无外键基线](../../internal/storage/mysql/baseline.sql) 由当前 v28 最终结构生成,保留表、索引、唯一约束、CHECK、生成列和不可变触发器;不包含外键、引用约束或客户端 `DELIMITER` 脚本。
- 基线执行前把所有历史版本写成 `dirty=true`,全部 DDL 成功后统一清除脏标记。中断会阻断后续迁移,必须由运维核对实际结构后处理,不能自动跳过。
- [无外键单元检查](../../internal/storage/mysql/no_foreign_keys_test.go) 禁止基线重新出现引用 DDL,并要求基线版本和迁移末版本一致。
## 实际验证
| 场景 | 结果 | 证据 |
| --- | --- | --- |
| 空 MySQL 8.4 数据库执行 `orbit-migrate up` | PASS | `TestIntegrationFreshInstallUsesNoForeignKeys`,迁移账本 28 条、`information_schema.REFERENTIAL_CONSTRAINTS` 为 0、`CheckSchema` 通过 |
| v7 历史库升级到当前版本 | PASS | `TestIntegrationNoForeignKeysUpgrade`,历史 70 条外键删除为 0,323 个既有索引组件和任务数据保留,手工外键漂移被拒绝 |
| CLI 结构核验 | PASS | `orbit-admin verify` 输出 `schema_version=28`、`migration_count=28`、`foreign_key_count=0`、`complete=true` |
验证命令使用 Docker `golang:1.25` 临时工具链和 MySQL 8.4 一次性容器;不是 Go 1.27.1 标准全量回归,也不代表复制、切换、RPO/RTO 或生产拓扑验收。
## 运维边界
升级旧库仍必须停用不兼容写节点,在维护窗口执行正式迁移并随后运行 `orbit-admin verify`。新装基线只解决初始化阶段的外键约束问题,不会替代应用事务中的命名空间、资源归属和权限校验;数据库关联字段仍由服务端显式核对。

View File

@@ -0,0 +1,816 @@
-- Orbit fresh-install baseline, generated from migration version 28.
-- This file intentionally contains no database-level relation constraints.
CREATE TABLE `orbit_application_resources` (
`application_id` binary(16) NOT NULL COMMENT '所属应用标识及主键,跨命名空间共享额度',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '配置乐观版本,用量变化不增加版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,UTC 微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后配置更新时间,UTC 微秒',
`max_unfinished_tasks` int unsigned NOT NULL DEFAULT '1000000' COMMENT '应用未终结任务上限,降额保留已有任务',
`unfinished_tasks` bigint unsigned NOT NULL DEFAULT '0' COMMENT '等待、预占、运行及待核查任务总数,重试不重复计数',
`max_concurrency` int unsigned NOT NULL DEFAULT '10000' COMMENT '预占与运行尝试总上限,降额不撤销已有尝试',
`active_attempts` bigint unsigned NOT NULL DEFAULT '0' COMMENT '当前预占与运行尝试总数,结果确认或到期恢复释放',
`pending_starts` bigint unsigned NOT NULL DEFAULT '0' COMMENT '已预占但未开始尝试数,包含在活跃尝试中',
`rate_per_second` int unsigned NOT NULL DEFAULT '100000' COMMENT '一秒固定窗口内开始授权上限,不是业务请求速率',
`start_window_until` datetime(6) DEFAULT NULL COMMENT '数据库时间的一秒窗口结束时刻,空表示尚未建立窗口',
`starts_in_window` int unsigned NOT NULL DEFAULT '0' COMMENT '窗口中已确认的开始次数,重复回执不计数',
PRIMARY KEY (`application_id`),
CONSTRAINT `ck_app_resources_limits` CHECK (((`max_unfinished_tasks` > 0) and (`max_concurrency` > 0) and (`rate_per_second` > 0))),
CONSTRAINT `ck_app_resources_pending` CHECK ((`pending_starts` <= `active_attempts`)),
CONSTRAINT `ck_app_resources_revision` CHECK ((`revision` > 0)),
CONSTRAINT `ck_app_resources_window` CHECK (((`start_window_until` is not null) or (`starts_in_window` = 0)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='应用跨命名空间共享额度与持久化用量,无数据库外键';
CREATE TABLE `orbit_applications` (
`id` binary(16) NOT NULL COMMENT '应用标识,全局唯一',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`name` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '可读名称,唯一性范围由对应数据表约束',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用;关闭后拒绝相应的新接入或新操作',
PRIMARY KEY (`id`),
UNIQUE KEY `uq_application_name` (`name`),
CONSTRAINT `ck_orbit_applications_0` CHECK ((`revision` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='接入应用,承载通用应用身份与启停配置';
CREATE TABLE `orbit_audit_events` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`operation_id` binary(16) NOT NULL COMMENT '管理操作的稳定身份,重复操作必须保留同一身份',
`actor_id` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '经认证的操作主体标识,不能直接信任请求自报值',
`action` varchar(64) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '执行的管理动作,例如暂停、取消或核查',
`resource_kind` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '关联资源类型;多态关联需要服务层额外校验',
`resource_id` binary(16) NOT NULL COMMENT '关联资源标识,不能单独作为访问授权依据',
`reason` varchar(1024) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '操作原因或处理说明,需要保留审计信息',
`before` json DEFAULT NULL COMMENT '操作前的受控摘要 JSON,须脱敏',
`after` json DEFAULT NULL COMMENT '操作后的受控摘要 JSON,须脱敏',
`evidence_ref` varchar(512) COLLATE utf8mb4_0900_as_cs DEFAULT NULL COMMENT '业务核查证据的引用;为空表示没有附加引用',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_audit_operation` (`namespace_id`,`operation_id`),
KEY `ix_audit_resource` (`namespace_id`,`resource_kind`,`resource_id`,`created_at`,`id`),
CONSTRAINT `ck_orbit_audit_events_0` CHECK ((`revision` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='不可改写的管理审计,保留操作身份、原因和变更摘要';
CREATE TRIGGER `trg_audit_event_immutable` BEFORE UPDATE ON `orbit_audit_events` FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Audit events are append-only';
CREATE TABLE `orbit_credentials` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`name` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '可读名称,唯一性范围由对应数据表约束',
`secret_digest` binary(32) NOT NULL COMMENT '凭据秘密的 SHA-256 摘要,不保存或输出原文',
`permissions` json NOT NULL COMMENT '授权范围的 JSON 快照,必须由服务端解释和校验',
`expires_at` datetime(6) DEFAULT NULL COMMENT '凭据过期时间,统一 UTC;为空表示没有配置到期时间',
`revoked_at` datetime(6) DEFAULT NULL COMMENT '撤销时间;为空表示尚未撤销',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_credential_name` (`namespace_id`,`name`),
CONSTRAINT `ck_orbit_credentials_0` CHECK ((`revision` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='命名空间凭据,只保存秘密摘要和授权范围';
CREATE TABLE `orbit_deliveries` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`subscription_id` binary(16) NOT NULL COMMENT '目标订阅标识',
`event_id` binary(16) NOT NULL COMMENT '来源持久化事件标识,用于消费去重',
`state` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'PENDING' COMMENT '当前状态;只允许定义的状态转换并配合事务校验',
`available_at` datetime(6) NOT NULL COMMENT '该事件最早允许再次交付的时间,统一 UTC',
`lease_owner` binary(16) DEFAULT NULL COMMENT '租约持有者标识;未持有租约时为空',
`lease_epoch` bigint unsigned NOT NULL DEFAULT '0' COMMENT '递增的执行或消费代次,用于隔离旧授权',
`lease_until` datetime(6) DEFAULT NULL COMMENT '交付租约截止时间;没有交付租约时为空',
`attempts` int unsigned NOT NULL DEFAULT '0' COMMENT '已产生的交付尝试次数,不等同于业务执行次数',
`acknowledged_at` datetime(6) DEFAULT NULL COMMENT '接收方可靠处理后的确认时间;未确认时为空',
`attempt_budget` int unsigned NOT NULL DEFAULT '20' COMMENT '累计允许交付次数,人工追加预算不重置已使用次数和租约代次',
`token_digest` binary(32) DEFAULT NULL COMMENT '最近一次交付秘密的SHA256摘要;从未发出或历史占位记录为空,确认后保留以核对重复确认',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_delivery_event` (`namespace_id`,`subscription_id`,`event_id`),
KEY `ix_delivery_due` (`namespace_id`,`state`,`available_at`,`id`),
KEY `fk_orbit_deliveries_2` (`namespace_id`,`event_id`),
KEY `ix_delivery_subscription_due` (`namespace_id`,`subscription_id`,`state`,`available_at`,`id`),
KEY `ix_delivery_subscription_lease` (`namespace_id`,`subscription_id`,`state`,`lease_until`,`id`),
KEY `ix_delivery_history` (`namespace_id`,`subscription_id`,`created_at`,`id`),
KEY `ix_delivery_state_history` (`namespace_id`,`subscription_id`,`state`,`created_at`,`id`),
KEY `ix_delivery_observe_state_available` (`state`,`available_at`,`id`),
CONSTRAINT `ck_delivery_budget` CHECK (((`attempts` <= `attempt_budget`) and (`attempt_budget` > 0))),
CONSTRAINT `ck_orbit_deliveries_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_orbit_deliveries_1` CHECK ((`state` in (_utf8mb4'PENDING',_utf8mb4'LEASED',_utf8mb4'ACKED',_utf8mb4'EXPIRED'))),
CONSTRAINT `ck_orbit_deliveries_2` CHECK ((((`state` = _utf8mb4'LEASED') and (`lease_owner` is not null) and (`lease_until` is not null)) or ((`state` <> _utf8mb4'LEASED') and (`lease_owner` is null) and (`lease_until` is null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='单个事件向一个订阅的可靠交付记录';
CREATE TABLE `orbit_event_streams` (
`namespace_id` binary(16) NOT NULL COMMENT '结果流所属命名空间;主键同时作为该流序号分配的事务锁',
`last_sequence` bigint unsigned NOT NULL DEFAULT '0' COMMENT '已分配结果序号;从1开始,与结果日志同事务提交,回滚不消耗序号',
`retained_from_sequence` bigint unsigned NOT NULL DEFAULT '1' COMMENT '最早保留序号;首版不清理结果日志,固定为1,供以后安全清理校验',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一UTC,精度微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后分配序号时间,统一UTC,不代替序号',
PRIMARY KEY (`namespace_id`),
CONSTRAINT `ck_event_stream_range` CHECK (((`retained_from_sequence` > 0) and ((`retained_from_sequence` - 1) <= `last_sequence`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='命名空间结果流的提交序号和保留边界';
CREATE TABLE `orbit_events` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`aggregate_kind` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '产生事件的聚合类型,例如任务或流程',
`aggregate_id` binary(16) NOT NULL COMMENT '产生事件的聚合标识',
`aggregate_revision` bigint unsigned NOT NULL COMMENT '来源聚合的状态版本,用于去重与迟到事件判断',
`type` varchar(64) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '事件或信号的稳定类型标识',
`payload` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`occurred_at` datetime(6) NOT NULL COMMENT '事件发生时间,统一 UTC',
`workflow_id` binary(16) GENERATED ALWAYS AS ((case when (`aggregate_kind` = _utf8mb4'workflow') then `aggregate_id` when ((`aggregate_kind` in (_utf8mb4'workflow_step',_utf8mb4'workflow_signal')) and (json_type(json_extract(`payload`,_utf8mb4'$.workflow_id')) = _utf8mb4'STRING') and (length(json_unquote(json_extract(`payload`,_utf8mb4'$.workflow_id'))) = 32) and (length(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(lower(json_unquote(json_extract(`payload`,_utf8mb4'$.workflow_id'))),_utf8mb4'0',_utf8mb4''),_utf8mb4'1',_utf8mb4''),_utf8mb4'2',_utf8mb4''),_utf8mb4'3',_utf8mb4''),_utf8mb4'4',_utf8mb4''),_utf8mb4'5',_utf8mb4''),_utf8mb4'6',_utf8mb4''),_utf8mb4'7',_utf8mb4''),_utf8mb4'8',_utf8mb4''),_utf8mb4'9',_utf8mb4''),_utf8mb4'a',_utf8mb4''),_utf8mb4'b',_utf8mb4''),_utf8mb4'c',_utf8mb4''),_utf8mb4'd',_utf8mb4''),_utf8mb4'e',_utf8mb4''),_utf8mb4'f',_utf8mb4'')) = 0)) then unhex(json_unquote(json_extract(`payload`,_utf8mb4'$.workflow_id'))) else NULL end)) STORED COMMENT '从不可变事件派生的流程查询标识;无范围或非法历史值为空,读取仍核对父资源,不能作为授权依据',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_event_revision` (`namespace_id`,`aggregate_kind`,`aggregate_id`,`aggregate_revision`,`type`),
KEY `ix_event_workflow_history` (`namespace_id`,`workflow_id`,`occurred_at`,`id`),
KEY `ix_event_observe_type_occurred` (`type`,`occurred_at`,`id`),
CONSTRAINT `ck_orbit_events_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_orbit_events_1` CHECK ((`aggregate_revision` > 0)),
CONSTRAINT `ck_orbit_events_payload_json` CHECK (((`payload` is null) or json_valid(`payload`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='追加保存的状态事件,供外部订阅或内部流程接续';
CREATE TRIGGER `trg_event_immutable` BEFORE UPDATE ON `orbit_events` FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'State events are append-only';
CREATE TABLE `orbit_idempotency_keys` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`operation` varchar(64) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '稳定操作类型,用于区分不同接口的幂等作用域',
`scope_key` varchar(64) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT '' COMMENT '资源作用域标识;空字符串表示当前操作的默认作用域',
`key_digest` binary(32) NOT NULL COMMENT '稳定幂等键或资源键的 SHA-256 摘要',
`request_digest` binary(32) NOT NULL COMMENT '规范化请求语义的 SHA-256 摘要,用于识别同键不同内容',
`resource_kind` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '关联资源类型;多态关联需要服务层额外校验',
`resource_id` binary(16) NOT NULL COMMENT '关联资源标识,不能单独作为访问授权依据',
`response` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`expires_at` datetime(6) NOT NULL COMMENT '幂等保留截止时间,不应短于约定的重发与恢复窗口',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_idempotency_scope` (`namespace_id`,`operation`,`scope_key`,`key_digest`),
KEY `ix_idempotency_expiry` (`expires_at`,`namespace_id`,`id`),
CONSTRAINT `ck_orbit_idempotency_keys_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_orbit_idempotency_keys_response_json` CHECK (((`response` is null) or json_valid(`response`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='请求幂等记录,关联语义摘要与首次受理响应';
CREATE TABLE `orbit_namespaces` (
`id` binary(16) NOT NULL COMMENT '命名空间标识,全局唯一',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`application_id` binary(16) NOT NULL COMMENT '所属应用标识',
`name` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '可读名称,唯一性范围由对应数据表约束',
`environment` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '环境标识,用于区分开发、测试和生产等隔离范围',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用;关闭后拒绝相应的新接入或新操作',
PRIMARY KEY (`id`),
UNIQUE KEY `uq_namespace_name` (`application_id`,`environment`,`name`),
CONSTRAINT `ck_orbit_namespaces_0` CHECK ((`revision` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='应用在指定环境中的资源隔离边界';
CREATE TABLE `orbit_result_events` (
`namespace_id` binary(16) NOT NULL COMMENT '结果所属命名空间,参与主键与服务端关联校验',
`id` binary(16) NOT NULL COMMENT '与来源orbit_events记录使用相同标识;跨订阅消费去重依据',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '不可变记录版本,固定为1',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一UTC,精度微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '不可变记录的初始写入时间,统一UTC',
`sequence` bigint unsigned NOT NULL COMMENT '命名空间内事务分配的结果序号;较小序号必先提交或整体回滚',
`queue_id` binary(16) NOT NULL COMMENT '来源任务的队列标识;订阅过滤和授权按该队列检查',
`snapshot` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '版本化结果快照JSON文本,保留数值精度,不含任务输入和租约秘密',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_result_sequence` (`namespace_id`,`sequence`),
KEY `ix_result_queue_sequence` (`namespace_id`,`queue_id`,`sequence`),
CONSTRAINT `ck_result_event` CHECK (((`revision` = 1) and (`sequence` > 0) and json_valid(`snapshot`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='不可变任务结果日志;与任务状态及来源事件一起提交';
CREATE TRIGGER `orbit_result_events_immutable` BEFORE UPDATE ON `orbit_result_events` FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='result events are immutable';
CREATE TABLE `orbit_subscriptions` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`name` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '可读名称,唯一性范围由对应数据表约束',
`credential_id` binary(16) NOT NULL COMMENT '关联的命名空间凭据标识',
`filter` json NOT NULL COMMENT '创建时冻结的结果事件类型数组JSON,不允许修改以免漏消费',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用新交付;暂停保留积压和在途结果,仍可接受有效确认',
`queue_id` binary(16) DEFAULT NULL COMMENT '冻结订阅队列;历史占位配置为空且不可消费,须通过新接口创建有效订阅',
`cursor_sequence` bigint unsigned NOT NULL DEFAULT '0' COMMENT '已扫描并可靠创建交付记录的结果序号;不是消费者确认位置',
`pending_count` int unsigned NOT NULL DEFAULT '0' COMMENT '已物化但未确认数量,包含等待、租约中和耗尽次数的交付',
`max_pending` int unsigned NOT NULL DEFAULT '100' COMMENT '未确认交付上限,1至1000;达到后暂停物化但保留来源结果',
`max_attempts` int unsigned NOT NULL DEFAULT '20' COMMENT '每条新交付的初始发送次数上限,1至100,不包含任务执行业务次数',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_subscription_name` (`namespace_id`,`name`),
KEY `fk_orbit_subscriptions_1` (`namespace_id`,`credential_id`),
CONSTRAINT `ck_orbit_subscriptions_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_subscription_window` CHECK (((`max_pending` between 1 and 1000) and (`max_attempts` between 1 and 100) and (`pending_count` <= `max_pending`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='事件订阅配置,绑定授权凭据及过滤规则';
CREATE TABLE `pulse_attempt_evidence` (
`namespace_id` binary(16) NOT NULL COMMENT '证据所属命名空间,与尝试一致',
`id` binary(16) NOT NULL COMMENT '证据记录标识',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '记录版本;证据追加保存,不覆盖',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '首次收到证据的数据库时间,UTC',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '写入时间,追加记录与创建时间相同',
`attempt_id` binary(16) NOT NULL COMMENT '证据所属执行尝试',
`message_id` binary(16) NOT NULL COMMENT '结果消息身份,重报保持不变',
`outcome` varchar(16) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 'Worker 声明的结果:SUCCEEDED、FAILED、UNKNOWN 或已确认停止且本次无遗留效果的 CANCELLED',
`error_code` varchar(64) COLLATE utf8mb4_0900_as_cs DEFAULT NULL COMMENT '稳定错误分类;无错误时为空,不保存原始异常正文',
`payload` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '结果或证据引用的 JSON 文本,保留精确数字',
`request_digest` binary(32) NOT NULL COMMENT '结果语义摘要,排除会话与秘密',
`reason` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '证据未改写权威结果的原因:LEASE_LOST、NOT_STARTED 或 RESULT_CONFLICT',
`retryable` tinyint(1) NOT NULL DEFAULT '0' COMMENT '原失败报告的安全重试声明,证据本身不触发重试',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_attempt_evidence_message` (`namespace_id`,`attempt_id`,`message_id`),
KEY `ix_evidence_history` (`namespace_id`,`attempt_id`,`created_at`,`id`) COMMENT '执行证据按尝试和时间稳定分页',
CONSTRAINT `ck_attempt_evidence_json` CHECK (json_valid(`payload`)),
CONSTRAINT `ck_attempt_evidence_outcome` CHECK ((`outcome` in (_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'UNKNOWN',_utf8mb4'CANCELLED'))),
CONSTRAINT `ck_attempt_evidence_reason` CHECK ((`reason` in (_utf8mb4'LEASE_LOST',_utf8mb4'NOT_STARTED',_utf8mb4'RESULT_CONFLICT'))),
CONSTRAINT `ck_attempt_evidence_revision` CHECK ((`revision` > 0)),
CONSTRAINT `ck_evidence_retryable` CHECK (((`retryable` in (0,1)) and ((`retryable` = false) or (`outcome` = _utf8mb4'FAILED'))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='迟到与冲突结果的追加证据,不自动改变任务终态';
CREATE TRIGGER `immutable_attempt_evidence` BEFORE UPDATE ON `pulse_attempt_evidence` FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='attempt evidence is immutable';
CREATE TABLE `pulse_attempt_receipts` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`attempt_id` binary(16) NOT NULL COMMENT '关联的独立执行尝试标识',
`operation` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '稳定操作类型,用于区分不同接口的幂等作用域',
`message_id` binary(16) NOT NULL COMMENT '可靠操作消息身份,跨重连重报时保持稳定',
`request_digest` binary(32) NOT NULL COMMENT '规范化请求语义的 SHA-256 摘要,用于识别同键不同内容',
`response` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`expires_at` datetime(6) NOT NULL COMMENT '过期时间,统一 UTC;到期后的行为由对应资源策略决定',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_attempt_receipt` (`namespace_id`,`attempt_id`,`operation`,`message_id`),
KEY `ix_result_receipt` (`namespace_id`,`attempt_id`,`operation`,`request_digest`) COMMENT '同一结果换消息身份重报时读取原始已应用回执',
CONSTRAINT `ck_pulse_attempt_receipts_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_attempt_receipts_response_json` CHECK (((`response` is null) or json_valid(`response`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='执行操作的持久化确认,用于断线或确认丢失后的幂等补取';
CREATE TABLE `pulse_attempts` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`task_id` binary(16) NOT NULL COMMENT '关联的 Pulse 任务标识',
`epoch` bigint unsigned NOT NULL COMMENT '该任务的授权代次,同一任务内唯一且递增',
`session_id` binary(16) NOT NULL COMMENT '当前执行归属的 Worker 会话标识',
`token_digest` binary(32) NOT NULL COMMENT '执行秘密令牌的 SHA-256 摘要,不输出原令牌',
`state` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'LEASED' COMMENT '当前执行尝试的状态,不能代替整个任务的状态',
`lease_until` datetime(6) NOT NULL COMMENT '已确认的租约截止时间,必须用数据库时间核验',
`authorized_at` datetime(6) DEFAULT NULL COMMENT '平台允许处理器开始的时间;不能证明业务副作用已经发生',
`ended_at` datetime(6) DEFAULT NULL COMMENT '本次尝试结束时间;尚未结束时为空',
`error_code` varchar(64) COLLATE utf8mb4_0900_as_cs DEFAULT NULL COMMENT '稳定错误分类,供恢复策略判断;不得写入敏感原始错误',
`result` mediumtext COLLATE utf8mb4_0900_as_cs COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`result_digest` binary(32) DEFAULT NULL COMMENT '结果语义摘要,用于识别确认重报与冲突结果',
`retryable` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Worker 明确失败时声明可安全重试,不用于未知效果或证明实际已安排重试',
`concurrency_token` bigint unsigned NOT NULL DEFAULT '0' COMMENT '当前资源键的单调许可序号,零表示未启用;重连不变,新尝试递增,不可单独证明执行授权',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_attempt_epoch` (`namespace_id`,`task_id`,`epoch`),
UNIQUE KEY `uq_attempt_task_id` (`namespace_id`,`task_id`,`id`),
KEY `ix_attempt_session_state` (`namespace_id`,`session_id`,`state`),
CONSTRAINT `ck_attempt_lifecycle` CHECK ((((`state` in (_utf8mb4'LEASED',_utf8mb4'RUNNING')) and (`ended_at` is null)) or ((`state` not in (_utf8mb4'LEASED',_utf8mb4'RUNNING')) and (`ended_at` is not null)))),
CONSTRAINT `ck_attempt_retryable` CHECK (((`retryable` in (0,1)) and ((`retryable` = false) or (`state` = _utf8mb4'FAILED')))),
CONSTRAINT `ck_attempt_start` CHECK ((((`state` = _utf8mb4'LEASED') and (`authorized_at` is null)) or ((`state` = _utf8mb4'RUNNING') and (`authorized_at` is not null)) or (`state` not in (_utf8mb4'LEASED',_utf8mb4'RUNNING')))),
CONSTRAINT `ck_pulse_attempts_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_attempts_1` CHECK ((`state` in (_utf8mb4'LEASED',_utf8mb4'RUNNING',_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED',_utf8mb4'EXPIRED',_utf8mb4'UNKNOWN'))),
CONSTRAINT `ck_pulse_attempts_2` CHECK ((`epoch` > 0)),
CONSTRAINT `ck_pulse_attempts_result_json` CHECK (((`result` is null) or json_valid(`result`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='一次任务执行尝试,保存租约、授权代次与独立结果';
CREATE TABLE `pulse_concurrency_limits` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`scope_key` varchar(64) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '资源作用域标识;空字符串表示当前操作的默认作用域',
`key_digest` binary(32) NOT NULL COMMENT '稳定幂等键或资源键的 SHA-256 摘要',
`max_permits` int unsigned NOT NULL COMMENT '该共享资源允许占用的许可上限',
`used_permits` int unsigned NOT NULL DEFAULT '0' COMMENT '已占用的有效许可数,必须与许可记录同事务维护',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_limit_key` (`namespace_id`,`scope_key`,`key_digest`),
CONSTRAINT `ck_pulse_concurrency_limits_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_concurrency_limits_1` CHECK (((`max_permits` > 0) and (`used_permits` <= `max_permits`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='跨节点共享的并发限制,实际额度更新必须处于事务内';
CREATE TABLE `pulse_execution_gates` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`paused` tinyint(1) NOT NULL DEFAULT '0' COMMENT '暂停标记;停止新的受控工作,不表示在途请求已停止',
`control_revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '暂停、继续等控制操作的版本,避免旧操作覆盖新决定',
`reason` varchar(1024) COLLATE utf8mb4_0900_as_cs DEFAULT NULL COMMENT '操作原因或处理说明,需要保留审计信息',
PRIMARY KEY (`namespace_id`,`id`),
CONSTRAINT `ck_pulse_execution_gates_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_execution_gates_1` CHECK ((`control_revision` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='通用执行门控,协调暂停与新执行授权的竞争';
CREATE TABLE `pulse_permits` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`limit_id` binary(16) NOT NULL COMMENT '关联的并发限制记录标识',
`attempt_id` binary(16) NOT NULL COMMENT '关联的独立执行尝试标识',
`lease_until` datetime(6) NOT NULL COMMENT '已确认的租约截止时间,必须用数据库时间核验',
`released_at` datetime(6) DEFAULT NULL COMMENT '许可释放时间;为空表示尚未记录释放',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_permit_attempt` (`namespace_id`,`limit_id`,`attempt_id`),
KEY `ix_permit_expiry` (`released_at`,`lease_until`,`namespace_id`,`id`),
KEY `fk_pulse_permits_2` (`namespace_id`,`attempt_id`),
CONSTRAINT `ck_pulse_permits_0` CHECK ((`revision` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='一次执行尝试占用的并发许可及释放历史';
CREATE TABLE `pulse_queues` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`name` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '可读名称,唯一性范围由对应数据表约束',
`paused` tinyint(1) NOT NULL DEFAULT '0' COMMENT '暂停标记;停止新的受控工作,不表示在途请求已停止',
`max_concurrency` int unsigned NOT NULL COMMENT '允许同时持有的有效执行许可上限,必须大于零',
`rate_per_second` int unsigned NOT NULL COMMENT '每秒新执行的速率配置;实际限流由共享协调逻辑落实',
`policy` json NOT NULL COMMENT '队列资源与执行配置的 JSON 文档',
`max_unfinished_tasks` int unsigned NOT NULL DEFAULT '100000' COMMENT '队列允许积压的未终结任务上限;降额不删除已有任务',
`unfinished_tasks` bigint unsigned NOT NULL DEFAULT '0' COMMENT '未终结任务数,包含待核查;受理增加、首次进入终态减少,重试不重复计数',
`start_window_until` datetime(6) DEFAULT NULL COMMENT '数据库时间的一秒开始限流窗口结束时刻;空表示尚未开始计数',
`starts_in_window` int unsigned NOT NULL DEFAULT '0' COMMENT '当前窗口已提交的开始授权次数,回执重报不重复计数,降额不清零',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_queue_name` (`namespace_id`,`name`),
CONSTRAINT `ck_pulse_queues_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_queues_1` CHECK ((`max_concurrency` > 0)),
CONSTRAINT `ck_pulse_queues_2` CHECK ((`rate_per_second` > 0)),
CONSTRAINT `ck_queue_start_window` CHECK (((`start_window_until` is not null) or (`starts_in_window` = 0))),
CONSTRAINT `ck_queue_unfinished_limit` CHECK ((`max_unfinished_tasks` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='任务路由与资源控制队列,不要求每个租户单独创建队列';
CREATE TABLE `pulse_schedule_occurrences` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`schedule_id` binary(16) NOT NULL COMMENT '所属调度计划标识',
`schedule_version` bigint unsigned NOT NULL COMMENT '该触发点采用的计划版本号',
`scheduled_at` datetime(6) NOT NULL COMMENT '原计划触发时间,统一 UTC;参与触发去重',
`decision` varchar(16) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '触发处理决定:已触发、已跳过或已合并',
`task_id` binary(16) DEFAULT NULL COMMENT '关联的 Pulse 任务标识',
`covered_until` datetime(6) DEFAULT NULL COMMENT '合并或跳过范围的排除边界,范围为原计划时间至该边界,不包含边界',
`reason` varchar(1024) COLLATE utf8mb4_0900_as_cs DEFAULT NULL COMMENT '操作原因或处理说明,需要保留审计信息',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_occurrence_time` (`namespace_id`,`schedule_id`,`scheduled_at`),
UNIQUE KEY `uq_occurrence_task` (`namespace_id`,`task_id`),
KEY `fk_pulse_schedule_occurrences_1` (`namespace_id`,`schedule_id`,`schedule_version`),
CONSTRAINT `ck_occurrence_range` CHECK (((`covered_until` is null) or (`covered_until` > `scheduled_at`))),
CONSTRAINT `ck_pulse_schedule_occurrences_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_schedule_occurrences_1` CHECK ((`decision` in (_utf8mb4'FIRED',_utf8mb4'SKIPPED',_utf8mb4'COALESCED'))),
CONSTRAINT `ck_pulse_schedule_occurrences_2` CHECK ((((`decision` = _utf8mb4'SKIPPED') and (`task_id` is null)) or ((`decision` in (_utf8mb4'FIRED',_utf8mb4'COALESCED')) and (`task_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='一个原计划触发点的处理记录,包含触发、跳过或合并决定';
CREATE TABLE `pulse_schedule_versions` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`schedule_id` binary(16) NOT NULL COMMENT '所属调度计划标识',
`version` bigint unsigned NOT NULL COMMENT '不可变发布版本;同版本不同内容必须拒绝',
`effective_at` datetime(6) NOT NULL COMMENT '该计划版本开始生效的时间',
`timezone` varchar(64) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '计划计算使用的 IANA 时区名称',
`specification` json NOT NULL COMMENT '版本化规则定义 JSON;发布后禁止原地修改',
`task_template` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`misfire_policy` varchar(16) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '漏跑策略:跳过、合并或有界补跑',
`overlap_policy` varchar(16) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '上次未完成时的策略:并行、串行或跳过',
`catchup_limit` int unsigned NOT NULL COMMENT '单次漏跑补偿上限;补跑策略必须配置正数',
`catchup_window_ms` bigint unsigned NOT NULL DEFAULT '86400000' COMMENT '未处理触发点最大保留时长,单位毫秒,过旧范围记录跳过',
`grace_ms` bigint unsigned NOT NULL DEFAULT '5000' COMMENT '正常派发允许延迟的毫秒数,超过后按漏跑策略处理',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_schedule_version` (`namespace_id`,`schedule_id`,`version`),
CONSTRAINT `ck_pulse_schedule_versions_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_schedule_versions_1` CHECK ((`version` > 0)),
CONSTRAINT `ck_pulse_schedule_versions_2` CHECK ((`misfire_policy` in (_utf8mb4'SKIP',_utf8mb4'COALESCE',_utf8mb4'CATCH_UP'))),
CONSTRAINT `ck_pulse_schedule_versions_3` CHECK ((`overlap_policy` in (_utf8mb4'PARALLEL',_utf8mb4'SERIAL',_utf8mb4'SKIP'))),
CONSTRAINT `ck_pulse_schedule_versions_4` CHECK (((`misfire_policy` <> _utf8mb4'CATCH_UP') or (`catchup_limit` > 0))),
CONSTRAINT `ck_pulse_schedule_versions_task_template_json` CHECK (((`task_template` is null) or json_valid(`task_template`))),
CONSTRAINT `ck_schedule_window` CHECK (((`catchup_window_ms` between 1000 and 2592000000) and (`grace_ms` between 1000 and 60000) and (`grace_ms` <= `catchup_window_ms`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='已发布的不可变计划版本,记录规则与任务模板';
CREATE TRIGGER `trg_schedule_version_immutable` BEFORE UPDATE ON `pulse_schedule_versions` FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Published schedule versions are immutable';
CREATE TABLE `pulse_schedules` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`name` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '可读名称,唯一性范围由对应数据表约束',
`current_version` bigint unsigned NOT NULL COMMENT '当前生效的计划版本号,发布时与版本记录原子更新',
`paused` tinyint(1) NOT NULL DEFAULT '0' COMMENT '暂停标记;停止新的受控工作,不表示在途请求已停止',
`next_fire_at` datetime(6) DEFAULT NULL COMMENT '下一计划触发时间;没有后续触发时为空',
`queue_id` binary(16) DEFAULT NULL COMMENT '计划终身绑定队列;旧结构人工行为空,不允许执行',
`processing_version` bigint unsigned NOT NULL DEFAULT '1' COMMENT '当前处理积压的不可变版本,不因发布新版本丢弃旧进度',
`check_at` datetime(6) DEFAULT NULL COMMENT '下一可扫描时间,UTC;补跑批次之间持久化至少一秒间隔,为空表示没有扫描工作',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_schedule_name` (`namespace_id`,`name`),
KEY `ix_schedule_due` (`paused`,`next_fire_at`,`namespace_id`,`id`),
KEY `ix_schedule_scan` (`paused`,`check_at`,`namespace_id`,`id`),
KEY `ix_schedule_list` (`namespace_id`,`queue_id`,`created_at`,`id`),
CONSTRAINT `ck_pulse_schedules_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_schedules_1` CHECK ((`current_version` > 0)),
CONSTRAINT `ck_schedule_cursor` CHECK (((`processing_version` > 0) and (`processing_version` <= `current_version`))),
CONSTRAINT `ck_schedule_enabled_scan` CHECK (((`queue_id` is not null) or (`check_at` is null)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='调度计划的当前控制状态,规则内容由不可变版本保存';
CREATE TABLE `pulse_tasks` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`queue_id` binary(16) NOT NULL COMMENT '任务所属队列,必须属于同一命名空间',
`gate_id` binary(16) DEFAULT NULL COMMENT '可选执行门控;为空表示没有流程级门控,仍受队列与权限控制',
`state` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'WAITING' COMMENT '任务状态;与执行尝试状态分开,未知业务效果进入待核查',
`handler` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务 Worker 注册的处理器名称',
`handler_version` varchar(64) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '处理器兼容版本,受理后不得静默替换',
`payload_version` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '输入参数的解释版本',
`payload` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`result` mediumtext COLLATE utf8mb4_0900_as_cs COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`policy_snapshot` json NOT NULL COMMENT '受理时冻结的实际生效策略 JSON',
`recovery_safety` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'RECONCILE_ONLY' COMMENT '恢复安全等级;未知值按需要核查处理,不能默认自动重跑',
`business_key` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL COMMENT '可空业务关联键,精确匹配并区分大小写及尾部空格,不自动提供业务幂等',
`owner_kind` varchar(32) COLLATE utf8mb4_0900_as_cs DEFAULT NULL COMMENT '来源资源类型,例如流程激活;为空表示无来源关联',
`owner_id` binary(16) DEFAULT NULL COMMENT '来源资源标识,必须与来源类型同时有值或同时为空',
`priority` smallint NOT NULL DEFAULT '0' COMMENT '冻结任务优先级,0至9;每级带来30秒排序优势,不改变最早执行时间',
`available_at` datetime(6) NOT NULL COMMENT '最早可被领取或处理的时间,统一 UTC',
`start_deadline_at` datetime(6) DEFAULT NULL COMMENT '最晚允许新尝试开始的时间;为空时仅受总时限约束',
`deadline_at` datetime(6) NOT NULL COMMENT '总截止时间,统一 UTC;到期不能证明外部业务没有生效',
`attempt_timeout_ms` bigint unsigned NOT NULL COMMENT '单次尝试的超时时长,单位毫秒',
`max_attempts` int unsigned NOT NULL COMMENT '最多执行尝试数,包含首次尝试',
`attempt_count` int unsigned NOT NULL DEFAULT '0' COMMENT '已产生的执行尝试数,恢复时不得重置',
`current_attempt_id` binary(16) DEFAULT NULL COMMENT '当前关联的执行尝试,必须属于本任务',
`lease_epoch` bigint unsigned NOT NULL DEFAULT '0' COMMENT '递增的执行或消费代次,用于隔离旧授权',
`lease_until` datetime(6) DEFAULT NULL COMMENT '当前执行授权截止时间;没有有效授权时为空',
`cancel_requested_at` datetime(6) DEFAULT NULL COMMENT '取消意图的持久化时间;不等于取消已完成',
`retry_of` binary(16) DEFAULT NULL COMMENT '人工重试的原任务标识,同命名空间每个原任务最多一个直接后继;空表示非人工派生',
`completed_at` datetime(6) DEFAULT NULL COMMENT '终结时间;非终态为空,进入终态时必须写入',
`effect_uncertain` tinyint(1) NOT NULL DEFAULT '0' COMMENT '曾有已授权执行的效果尚未核实,重试与耗尽不清除,仅权威成功或核查可解除',
`concurrency_key` varchar(191) COLLATE utf8mb4_0900_as_cs DEFAULT NULL COMMENT '显式互斥资源键,同命名空间跨队列共享;空表示不启用,不代替业务幂等',
`concurrency_key_digest` binary(32) DEFAULT NULL COMMENT '资源键精确 UTF-8 字节的域分隔 SHA-256;与资源键同时为空或有值',
`dispatch_at` datetime(6) GENERATED ALWAYS AS ((`available_at` + interval (-(30) * `priority`) second)) STORED COMMENT '派发排序时间,由最早执行时间减优先级乘30秒生成;不是执行授权或可用时间',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_task_retry_parent` (`namespace_id`,`retry_of`) COMMENT '防止不同操作身份并发重新发起同一原任务',
KEY `ix_task_dispatch` (`namespace_id`,`queue_id`,`state`,`available_at`,`priority`,`id`),
KEY `ix_task_lease_recovery` (`state`,`lease_until`,`namespace_id`,`id`),
KEY `ix_task_business` (`namespace_id`,`business_key`,`created_at`,`id`),
KEY `fk_pulse_tasks_2` (`namespace_id`,`gate_id`),
KEY `fk_pulse_tasks_4` (`namespace_id`,`id`,`current_attempt_id`),
KEY `ix_task_created` (`namespace_id`,`queue_id`,`created_at`,`id`),
KEY `ix_task_state_created` (`namespace_id`,`queue_id`,`state`,`created_at`,`id`),
KEY `ix_task_deadline` (`state`,`deadline_at`,`namespace_id`,`id`),
KEY `ix_task_start_deadline` (`state`,`start_deadline_at`,`namespace_id`,`id`),
KEY `ix_task_owner_state` (`namespace_id`,`owner_kind`,`owner_id`,`state`,`id`),
KEY `ix_task_priority_dispatch` (`namespace_id`,`queue_id`,`state`,`dispatch_at`,`available_at`,`id`),
KEY `ix_task_queue_business_created` (`namespace_id`,`queue_id`,`business_key`,`created_at`,`id`) COMMENT '队列内按业务键及创建时间分页',
KEY `ix_task_queue_business_state_created` (`namespace_id`,`queue_id`,`business_key`,`state`,`created_at`,`id`) COMMENT '队列内按业务键和状态及创建时间分页',
KEY `ix_task_workflow_owner` (`namespace_id`,`owner_kind`,`owner_id`,`gate_id`,`id`),
KEY `ix_task_observe_state_available` (`state`,`available_at`,`id`),
KEY `ix_task_observe_terminal_completed` (`state`,`completed_at`,`id`),
CONSTRAINT `ck_pulse_tasks_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_tasks_1` CHECK ((`state` in (_utf8mb4'WAITING',_utf8mb4'LEASED',_utf8mb4'RUNNING',_utf8mb4'NEEDS_REVIEW',_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED'))),
CONSTRAINT `ck_pulse_tasks_2` CHECK ((`recovery_safety` in (_utf8mb4'IDEMPOTENT',_utf8mb4'RECONCILE_ONLY'))),
CONSTRAINT `ck_pulse_tasks_3` CHECK (((`max_attempts` > 0) and (`attempt_count` <= `max_attempts`))),
CONSTRAINT `ck_pulse_tasks_4` CHECK ((`attempt_timeout_ms` > 0)),
CONSTRAINT `ck_pulse_tasks_5` CHECK ((`deadline_at` > `available_at`)),
CONSTRAINT `ck_pulse_tasks_6` CHECK (((`start_deadline_at` is null) or (`start_deadline_at` <= `deadline_at`))),
CONSTRAINT `ck_pulse_tasks_7` CHECK ((((`state` in (_utf8mb4'LEASED',_utf8mb4'RUNNING')) and (`current_attempt_id` is not null) and (`lease_until` is not null)) or ((`state` not in (_utf8mb4'LEASED',_utf8mb4'RUNNING')) and (`lease_until` is null)))),
CONSTRAINT `ck_pulse_tasks_8` CHECK ((((`state` in (_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED')) and (`completed_at` is not null)) or ((`state` not in (_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED')) and (`completed_at` is null)))),
CONSTRAINT `ck_pulse_tasks_9` CHECK (((`owner_kind` is null) = (`owner_id` is null))),
CONSTRAINT `ck_pulse_tasks_payload_json` CHECK (((`payload` is null) or json_valid(`payload`))),
CONSTRAINT `ck_pulse_tasks_result_json` CHECK (((`result` is null) or json_valid(`result`))),
CONSTRAINT `ck_task_concurrency_key` CHECK (((`concurrency_key` is null) = (`concurrency_key_digest` is null))),
CONSTRAINT `ck_task_effect_uncertain` CHECK (((`effect_uncertain` in (0,1)) and ((`effect_uncertain` = false) or (`state` not in (_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED'))))),
CONSTRAINT `ck_task_priority` CHECK ((`priority` between 0 and 9)),
CONSTRAINT `ck_task_retry_not_self` CHECK (((`retry_of` is null) or (`retry_of` <> `id`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='任务主记录,执行尝试另存历史,终态不自动回退';
CREATE TABLE `pulse_worker_instances` (
`namespace_id` binary(16) NOT NULL COMMENT '进程所属命名空间,参与授权和所有关联',
`id` binary(16) NOT NULL COMMENT 'Worker 进程实例标识,进程重启必须换新值',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '持久化变更版本,从一开始递增',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '首次注册时间,UTC 微秒精度',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最近变更时间,不替代会话代次',
`credential_id` binary(16) NOT NULL COMMENT '初始注册凭据,进程生命周期内固定',
`queue_id` binary(16) NOT NULL COMMENT '绑定队列,切换队列需创建新进程实例',
`secret_digest` binary(32) NOT NULL COMMENT '随机进程恢复秘密的 SHA-256 摘要,不保存原文',
`registration_digest` binary(32) NOT NULL COMMENT '队列及处理器版本集合的语义摘要,重连必须匹配',
`session_epoch` bigint unsigned NOT NULL DEFAULT '0' COMMENT '最大已分配连接代次,注册前为零',
`current_session_id` binary(16) DEFAULT NULL COMMENT '当前会话,注册事务建立会话前为空',
`draining` tinyint(1) NOT NULL DEFAULT '0' COMMENT '持久化排空标记,重连不能清除',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_worker_credential` (`namespace_id`,`id`,`credential_id`),
KEY `fk_worker_instance_credential` (`namespace_id`,`credential_id`),
KEY `fk_worker_instance_queue` (`namespace_id`,`queue_id`),
KEY `fk_worker_instance_session` (`namespace_id`,`id`,`current_session_id`),
CONSTRAINT `ck_worker_instance_revision` CHECK ((`revision` > 0)),
CONSTRAINT `ck_worker_instance_session` CHECK ((((`session_epoch` = 0) and (`current_session_id` is null)) or ((`session_epoch` > 0) and (`current_session_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='Worker 进程恢复身份和当前会话头,用于跨节点隔离旧连接';
CREATE TABLE `pulse_worker_sessions` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`worker_instance_id` binary(16) NOT NULL COMMENT 'Worker 进程实例标识,重连保留,进程重启应更换',
`credential_id` binary(16) NOT NULL COMMENT '关联的命名空间凭据标识',
`node_id` binary(16) NOT NULL COMMENT '承载当前连接的平台节点标识',
`session_epoch` bigint unsigned NOT NULL COMMENT '该 Worker 实例的连接代次,用于隔离旧会话',
`capabilities` json NOT NULL COMMENT '已登记的处理器版本、队列等能力快照',
`capacity` int unsigned NOT NULL COMMENT 'Worker 声明的执行容量,仍受平台权限和配额约束',
`capacity_sequence` bigint unsigned NOT NULL DEFAULT '0' COMMENT '会话内容量消息序号,重复上报不得重复增加额度',
`draining` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否正在排空连接;排空时不接收新任务',
`expires_at` datetime(6) NOT NULL COMMENT '过期时间,统一 UTC;到期后的行为由对应资源策略决定',
`concurrency_keys` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否明确支持资源键互斥与序号交付;未声明的旧 Worker 不领取带资源键任务',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_worker_epoch` (`namespace_id`,`worker_instance_id`,`session_epoch`),
UNIQUE KEY `uq_session_instance_id` (`namespace_id`,`worker_instance_id`,`id`),
KEY `ix_worker_expiry` (`expires_at`,`namespace_id`,`id`),
KEY `fk_pulse_worker_sessions_1` (`namespace_id`,`credential_id`),
KEY `fk_worker_session_instance` (`namespace_id`,`worker_instance_id`,`credential_id`),
CONSTRAINT `ck_pulse_worker_sessions_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_pulse_worker_sessions_1` CHECK ((`session_epoch` > 0)),
CONSTRAINT `ck_worker_session_capacity` CHECK ((`capacity` <= 256))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='一次 Worker 连接的持久化身份与能力快照';
CREATE TABLE `weave_activations` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`workflow_id` binary(16) NOT NULL COMMENT '所属流程实例标识',
`step_id` binary(16) NOT NULL COMMENT '所属逻辑步骤标识',
`sequence` int unsigned NOT NULL COMMENT '同一步骤的激活序号,人工恢复递增并保留旧记录',
`state` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'READY' COMMENT '当前状态;只允许定义的状态转换并配合事务校验',
`task_id` binary(16) DEFAULT NULL COMMENT '关联的 Pulse 任务标识',
`input` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`output` mediumtext COLLATE utf8mb4_0900_as_cs COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`completed_at` datetime(6) DEFAULT NULL COMMENT '终结时间;非终态为空,进入终态时必须写入',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_activation_sequence` (`namespace_id`,`step_id`,`sequence`),
UNIQUE KEY `uq_activation_workflow_id` (`namespace_id`,`workflow_id`,`id`),
UNIQUE KEY `uq_activation_step_id` (`namespace_id`,`workflow_id`,`step_id`,`id`),
UNIQUE KEY `uq_activation_task` (`namespace_id`,`task_id`),
CONSTRAINT `ck_weave_activations_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_activations_1` CHECK ((`state` in (_utf8mb4'BLOCKED',_utf8mb4'READY',_utf8mb4'ACTIVE',_utf8mb4'WAITING',_utf8mb4'NEEDS_REVIEW',_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED',_utf8mb4'SKIPPED'))),
CONSTRAINT `ck_weave_activations_2` CHECK ((`sequence` > 0)),
CONSTRAINT `ck_weave_activations_input_json` CHECK (((`input` is null) or json_valid(`input`))),
CONSTRAINT `ck_weave_activations_output_json` CHECK (((`output` is null) or json_valid(`output`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='步骤的一次具体激活,人工恢复通过新激活保留历史';
CREATE TABLE `weave_definition_versions` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`definition_id` binary(16) NOT NULL COMMENT '稳定流程定义标识',
`version` varchar(64) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '不可变发布版本;同版本不同内容必须拒绝',
`schema_version` int unsigned NOT NULL COMMENT '流程定义文档的结构版本',
`specification` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`content_digest` binary(32) NOT NULL COMMENT '不可变定义内容的 SHA-256 摘要',
`published_by` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '发布版本的认证主体,用于追溯发布来源',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_definition_version` (`namespace_id`,`definition_id`,`version`),
UNIQUE KEY `uq_definition_version_id` (`namespace_id`,`definition_id`,`id`),
CONSTRAINT `ck_weave_definition_versions_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_definition_versions_1` CHECK ((`schema_version` > 0)),
CONSTRAINT `ck_weave_definition_versions_specification_json` CHECK (((`specification` is null) or json_valid(`specification`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='已发布的不可变流程定义版本';
CREATE TRIGGER `trg_definition_version_immutable` BEFORE UPDATE ON `weave_definition_versions` FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Published workflow versions are immutable';
CREATE TABLE `weave_definitions` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`name` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '可读名称,唯一性范围由对应数据表约束',
`description` varchar(1024) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT '' COMMENT '定义用途说明,不承载运行状态',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_definition_name` (`namespace_id`,`name`),
CONSTRAINT `ck_weave_definitions_0` CHECK ((`revision` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='流程定义的稳定身份,不保存可直接执行的业务代码';
CREATE TABLE `weave_inbox` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`workflow_id` binary(16) NOT NULL COMMENT '所属流程实例标识',
`event_id` binary(16) NOT NULL COMMENT '来源持久化事件标识,用于消费去重',
`state` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'PENDING' COMMENT '当前状态;只允许定义的状态转换并配合事务校验',
`available_at` datetime(6) NOT NULL COMMENT '最早可被领取或处理的时间,统一 UTC',
`lease_owner` binary(16) DEFAULT NULL COMMENT '租约持有者标识;未持有租约时为空',
`lease_epoch` bigint unsigned NOT NULL DEFAULT '0' COMMENT '递增的执行或消费代次,用于隔离旧授权',
`lease_until` datetime(6) DEFAULT NULL COMMENT '内部消费租约截止时间;没有消费租约时为空',
`processed_at` datetime(6) DEFAULT NULL COMMENT '内部事件完成消费的时间;尚未消费时为空',
`error_code` varchar(64) COLLATE utf8mb4_0900_as_cs DEFAULT NULL COMMENT '稳定错误分类,供恢复策略判断;不得写入敏感原始错误',
`source_task_id` binary(16) DEFAULT NULL COMMENT '来源Pulse任务标识;仅迁移前无法核实的历史记录允许为空',
`source_activation_id` binary(16) DEFAULT NULL COMMENT '事件发生时的Weave激活标识,保留旧激活归属;仅未核实历史记录可空',
`source_revision` bigint unsigned DEFAULT NULL COMMENT '来源任务的状态版本,必须大于零;仅未核实历史记录可空',
`snapshot` mediumtext COLLATE utf8mb4_0900_as_cs COMMENT 'schema_version版本化结果JSON文本,保留精确数字与当时状态;仅未核实历史记录可空',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_inbox_event` (`namespace_id`,`event_id`),
UNIQUE KEY `uq_weave_task_result` (`namespace_id`,`source_task_id`,`source_revision`),
KEY `ix_inbox_due` (`state`,`available_at`,`namespace_id`,`id`),
KEY `fk_weave_inbox_1` (`namespace_id`,`workflow_id`),
CONSTRAINT `ck_weave_inbox_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_inbox_1` CHECK ((`state` in (_utf8mb4'PENDING',_utf8mb4'LEASED',_utf8mb4'PROCESSED',_utf8mb4'QUARANTINED'))),
CONSTRAINT `ck_weave_inbox_2` CHECK ((((`state` = _utf8mb4'LEASED') and (`lease_owner` is not null) and (`lease_until` is not null)) or ((`state` <> _utf8mb4'LEASED') and (`lease_owner` is null) and (`lease_until` is null)))),
CONSTRAINT `ck_weave_inbox_source` CHECK ((((`state` in (_utf8mb4'PROCESSED',_utf8mb4'QUARANTINED')) and (`source_task_id` is null) and (`source_activation_id` is null) and (`source_revision` is null) and (`snapshot` is null)) or ((`source_task_id` is not null) and (`source_activation_id` is not null) and (`source_revision` is not null) and (`source_revision` > 0) and (`snapshot` is not null) and (length(`snapshot`) <= 524288) and json_valid(`snapshot`))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='Weave 内部事件消费记录,和流程推进一起确认消费';
CREATE TABLE `weave_instances` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`definition_id` binary(16) NOT NULL COMMENT '稳定流程定义标识',
`definition_version_id` binary(16) NOT NULL COMMENT '实例固定使用的定义版本记录,不能随新版本发布而变化',
`gate_id` binary(16) NOT NULL COMMENT '执行门控标识,用于检查是否允许新的领取与开始',
`state` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'RUNNING' COMMENT '流程执行状态,与独立的暂停标记同时展示',
`paused` tinyint(1) NOT NULL DEFAULT '0' COMMENT '暂停标记;停止新的受控工作,不表示在途请求已停止',
`control_revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '暂停、继续等控制操作的版本,避免旧操作覆盖新决定',
`input` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`output` mediumtext COLLATE utf8mb4_0900_as_cs COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`policy_snapshot` json NOT NULL COMMENT '受理时冻结的实际生效策略 JSON',
`business_key` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL COMMENT '精确业务追踪键,保留尾部空格,不提供业务幂等',
`deadline_at` datetime(6) NOT NULL COMMENT '流程总截止时间,暂停默认不延长此业务边界',
`cancel_requested_at` datetime(6) DEFAULT NULL COMMENT '取消意图的持久化时间;不等于取消已完成',
`completed_at` datetime(6) DEFAULT NULL COMMENT '终结时间;非终态为空,进入终态时必须写入',
`retry_of` binary(16) DEFAULT NULL COMMENT '来源记录标识,人工重新发起时用于保留历史关联',
`stop_kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL COMMENT '首次停止原因:CANCEL为用户取消,FAILURE为固定失败策略;为空表示尚未要求结清',
`stop_requested_at` datetime(6) DEFAULT NULL COMMENT '首次停止意图的数据库时间,重报、进入核查或后来取消均不覆盖',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_workflow_gate` (`namespace_id`,`gate_id`),
KEY `ix_workflow_state` (`namespace_id`,`state`,`updated_at`,`id`),
KEY `ix_workflow_business` (`namespace_id`,`business_key`,`created_at`,`id`),
KEY `fk_weave_instances_1` (`namespace_id`,`definition_id`,`definition_version_id`),
KEY `fk_weave_instances_3` (`namespace_id`,`retry_of`),
KEY `ix_workflow_created` (`namespace_id`,`created_at`,`id`),
KEY `ix_workflow_state_created` (`namespace_id`,`state`,`created_at`,`id`),
KEY `ix_workflow_business_state_created` (`namespace_id`,`business_key`,`state`,`created_at`,`id`),
KEY `ix_workflow_definition_created` (`namespace_id`,`definition_id`,`created_at`,`id`),
KEY `ix_workflow_definition_state_created` (`namespace_id`,`definition_id`,`state`,`created_at`,`id`),
KEY `ix_workflow_definition_business_created` (`namespace_id`,`definition_id`,`business_key`,`created_at`,`id`),
KEY `ix_workflow_definition_business_state_created` (`namespace_id`,`definition_id`,`business_key`,`state`,`created_at`,`id`),
KEY `ix_workflow_observe_state_updated` (`state`,`updated_at`,`id`),
KEY `ix_workflow_observe_terminal_completed` (`state`,`completed_at`,`id`),
CONSTRAINT `ck_weave_instances_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_instances_1` CHECK ((`state` in (_utf8mb4'RUNNING',_utf8mb4'WAITING',_utf8mb4'CANCELLING',_utf8mb4'NEEDS_REVIEW',_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED'))),
CONSTRAINT `ck_weave_instances_2` CHECK ((`control_revision` > 0)),
CONSTRAINT `ck_weave_instances_3` CHECK ((((`state` in (_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED')) and (`completed_at` is not null)) or ((`state` not in (_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED')) and (`completed_at` is null)))),
CONSTRAINT `ck_weave_instances_input_json` CHECK (((`input` is null) or json_valid(`input`))),
CONSTRAINT `ck_weave_instances_output_json` CHECK (((`output` is null) or json_valid(`output`))),
CONSTRAINT `ck_workflow_stop_intent` CHECK ((((`stop_kind` is null) and (`stop_requested_at` is null)) or ((`stop_kind` is not null) and (`stop_kind` in (_ascii'CANCEL',_ascii'FAILURE')) and (`stop_requested_at` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='流程运行实例,固定定义版本,暂停与执行状态分别保存';
CREATE TABLE `weave_pending_advances` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`workflow_id` binary(16) NOT NULL COMMENT '所属流程实例标识',
`step_id` binary(16) NOT NULL COMMENT '所属逻辑步骤标识',
`expected_step_revision` bigint unsigned NOT NULL COMMENT '恢复推进时要求的步骤版本,避免使用过期检查点',
`checkpoint` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`available_at` datetime(6) NOT NULL COMMENT '最早可被领取或处理的时间,统一 UTC',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_advance_step` (`namespace_id`,`workflow_id`,`step_id`),
KEY `ix_advance_due` (`available_at`,`namespace_id`,`id`),
CONSTRAINT `ck_weave_pending_advances_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_pending_advances_1` CHECK ((`expected_step_revision` > 0)),
CONSTRAINT `ck_weave_pending_advances_checkpoint_json` CHECK (((`checkpoint` is null) or json_valid(`checkpoint`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='暂停或分批推进后的续接检查点,避免仅依赖内存唤醒';
CREATE TABLE `weave_poll_rounds` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`workflow_id` binary(16) NOT NULL COMMENT '所属流程实例标识',
`activation_id` binary(16) NOT NULL COMMENT '所属步骤激活,区分旧轮次和人工恢复后的新轮次',
`round` int unsigned NOT NULL COMMENT '业务查询轮次编号,从首次激活起连续计数;人工恢复同一轮时保留编号,由激活身份区分对应任务',
`task_id` binary(16) NOT NULL COMMENT '关联的 Pulse 任务标识',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_poll_round` (`namespace_id`,`workflow_id`,`activation_id`,`round`),
UNIQUE KEY `uq_poll_task` (`namespace_id`,`task_id`),
CONSTRAINT `ck_weave_poll_rounds_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_poll_rounds_1` CHECK ((`round` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='一次业务查询轮次,一轮对应一个 Pulse 任务';
CREATE TABLE `weave_poll_states` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`workflow_id` binary(16) NOT NULL COMMENT '所属流程实例标识',
`activation_id` binary(16) NOT NULL COMMENT '所属步骤激活,区分旧轮次和人工恢复后的新轮次',
`max_rounds` int unsigned NOT NULL COMMENT '业务查询最多轮次,不等于单轮请求重试次数',
`used_rounds` int unsigned NOT NULL DEFAULT '0' COMMENT '已创建的查询轮次,重启或暂停继续不得重置',
`max_attempts_per_round` int unsigned NOT NULL COMMENT '每轮 Pulse 任务最多尝试数,包含该轮首次请求',
`interval_ms` bigint unsigned NOT NULL COMMENT '相邻业务查询轮次的等待间隔,单位毫秒',
`deadline_at` datetime(6) NOT NULL COMMENT '查询节点总截止时间,轮次和单轮重试都受此边界限制',
`next_poll_at` datetime(6) DEFAULT NULL COMMENT '最早允许下一轮查询的时间;为空表示当前不能直接推进',
`current_task_id` binary(16) DEFAULT NULL COMMENT '当前在途查询任务;非空时禁止重叠开启下一轮',
`resolved_at` datetime(6) DEFAULT NULL COMMENT '等待或查询节点结清的时间;尚未结清时为空',
`first_round` int unsigned NOT NULL DEFAULT '1' COMMENT '本激活保存的首个业务查询轮次;人工恢复沿用原轮次,不从1重新计数',
`first_round_attempts_used` int unsigned NOT NULL DEFAULT '0' COMMENT '首个业务轮次在旧激活中已消耗的执行尝试数;新任务只获得原单轮预算的剩余部分',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_poll_activation` (`namespace_id`,`workflow_id`,`activation_id`),
KEY `fk_weave_poll_states_2` (`namespace_id`,`current_task_id`),
CONSTRAINT `ck_poll_recovery_budget` CHECK (((`first_round` between 1 and `max_rounds`) and (`first_round_attempts_used` < `max_attempts_per_round`) and (((`used_rounds` = 0) and (`first_round` = 1) and (`first_round_attempts_used` = 0)) or (`first_round` <= `used_rounds`)))),
CONSTRAINT `ck_weave_poll_states_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_poll_states_1` CHECK (((`max_rounds` > 0) and (`used_rounds` <= `max_rounds`))),
CONSTRAINT `ck_weave_poll_states_2` CHECK (((`max_attempts_per_round` > 0) and (`interval_ms` > 0)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='有界查询节点的持久化预算与恢复检查点';
CREATE TABLE `weave_signals` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`workflow_id` binary(16) NOT NULL COMMENT '所属流程实例标识',
`key_digest` binary(32) NOT NULL COMMENT '稳定幂等键或资源键的 SHA-256 摘要',
`request_digest` binary(32) NOT NULL COMMENT '规范化请求语义的 SHA-256 摘要,用于识别同键不同内容',
`type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL COMMENT '固定定义中的信号名称,新受理限制为128个ASCII字符',
`target_step_key` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '信号目标步骤键,用于早到信号的后续匹配',
`correlation_key` varbinary(764) NOT NULL COMMENT '精确UTF8操作关联,保留尾部空格;新信号最多512字节,历史兼容764字节',
`payload` mediumtext COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '业务无关 JSON 文本,保留原始数值精度,由版本化契约解释',
`actor_id` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '经认证的操作主体标识,不能直接信任请求自报值',
`state` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'PENDING' COMMENT '当前状态;只允许定义的状态转换并配合事务校验',
`accepted_at` datetime(6) NOT NULL COMMENT '信号可靠受理时间,不能用稍后的消费时间替代',
`expires_at` datetime(6) NOT NULL COMMENT '过期时间,统一 UTC;到期后的行为由对应资源策略决定',
`consumed_wait_id` binary(16) DEFAULT NULL COMMENT '消费该信号的等待记录;尚未消费时为空',
`payload_version` varchar(32) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL COMMENT '信号内容契约版本;为空仅表示未核实的历史记录',
`target_activation_sequence` int unsigned DEFAULT NULL COMMENT '固定目标步骤激活序号,早到首轮为1;为空仅表示未核实历史',
`evidence_ref` varchar(512) COLLATE utf8mb4_0900_as_cs DEFAULT NULL COMMENT '可选的受控证据引用;不保存秘密,也不由平台请求该地址',
`disposition` varchar(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL COMMENT '稳定消费或拒绝原因;PENDING时为空,不包含业务参数',
`available_at` datetime(6) NOT NULL COMMENT '未匹配信号下次到期检查时间;故障退避可延后检查,但不改变原截止',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_signal_identity` (`namespace_id`,`workflow_id`,`key_digest`),
KEY `ix_signal_match` (`namespace_id`,`workflow_id`,`target_step_key`,`correlation_key`,`state`,`accepted_at`),
KEY `fk_weave_signals_2` (`namespace_id`,`workflow_id`,`consumed_wait_id`),
KEY `ix_signal_expiry` (`state`,`available_at`,`namespace_id`,`id`),
KEY `ix_signal_activation` (`namespace_id`,`workflow_id`,`target_step_key`,`target_activation_sequence`,`state`,`accepted_at`,`id`),
KEY `ix_signal_history` (`namespace_id`,`workflow_id`,`accepted_at`,`id`),
CONSTRAINT `ck_signal_pending_identity` CHECK (((`state` <> _utf8mb4'PENDING') or ((`payload_version` is not null) and (`target_activation_sequence` is not null) and (`disposition` is null)))),
CONSTRAINT `ck_signal_version_sequence` CHECK ((((`payload_version` is null) and (`target_activation_sequence` is null)) or ((`payload_version` is not null) and (`payload_version` <> _ascii'') and (`target_activation_sequence` is not null) and (`target_activation_sequence` > 0)))),
CONSTRAINT `ck_weave_signals_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_signals_1` CHECK ((`state` in (_utf8mb4'PENDING',_utf8mb4'CONSUMED',_utf8mb4'EXPIRED',_utf8mb4'REJECTED'))),
CONSTRAINT `ck_weave_signals_2` CHECK ((((`state` = _utf8mb4'CONSUMED') and (`consumed_wait_id` is not null)) or ((`state` <> _utf8mb4'CONSUMED') and (`consumed_wait_id` is null)))),
CONSTRAINT `ck_weave_signals_payload_json` CHECK (((`payload` is null) or json_valid(`payload`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='已可靠受理的外部信号,受理与消费是两个阶段';
CREATE TABLE `weave_steps` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`workflow_id` binary(16) NOT NULL COMMENT '所属流程实例标识',
`key` varchar(128) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '定义中的稳定步骤键,在同一流程实例内唯一',
`kind` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '节点或等待类型,决定由哪类推进规则处理',
`state` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'BLOCKED' COMMENT '当前状态;只允许定义的状态转换并配合事务校验',
`current_activation_id` binary(16) DEFAULT NULL COMMENT '当前激活记录,必须属于本实例的本步骤',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_step_key` (`namespace_id`,`workflow_id`,`key`),
UNIQUE KEY `uq_step_workflow_id` (`namespace_id`,`workflow_id`,`id`),
KEY `fk_weave_steps_2` (`namespace_id`,`workflow_id`,`id`,`current_activation_id`),
CONSTRAINT `ck_weave_steps_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_steps_1` CHECK ((`state` in (_utf8mb4'BLOCKED',_utf8mb4'READY',_utf8mb4'ACTIVE',_utf8mb4'WAITING',_utf8mb4'NEEDS_REVIEW',_utf8mb4'SUCCEEDED',_utf8mb4'FAILED',_utf8mb4'CANCELLED',_utf8mb4'SKIPPED'))),
CONSTRAINT `ck_weave_steps_2` CHECK ((`kind` in (_utf8mb4'TASK',_utf8mb4'BRANCH',_utf8mb4'PARALLEL',_utf8mb4'JOIN',_utf8mb4'SIGNAL',_utf8mb4'TIMER',_utf8mb4'POLL')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='实例中的逻辑步骤,指向该步骤当前的激活记录';
CREATE TABLE `weave_version_scopes` (
`namespace_id` binary(16) NOT NULL COMMENT '命名空间,参与版本身份及查询授权',
`definition_version_id` binary(16) NOT NULL COMMENT '固定定义版本标识,范围包含所有未执行分支',
`definition_id` binary(16) NOT NULL COMMENT '所属定义标识,服务端核对其与版本及实例的归属',
`content_digest` binary(32) NOT NULL COMMENT '原不可变定义摘要,防止跨版本使用范围',
`queue_ids` json NOT NULL COMMENT '去重队列标识数组,空数组表示无任务队列;只用于查询筛选,返回前仍需核验原定义',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '查询索引建立时间,UTC;不能替代原定义发布时间',
PRIMARY KEY (`namespace_id`,`definition_version_id`),
KEY `ix_version_scope_definition` (`namespace_id`,`definition_id`,`definition_version_id`),
CONSTRAINT `ck_version_scope_queues` CHECK (json_schema_valid(_utf8mb4'{"type":"array","maxItems":128,"uniqueItems":true,"items":{"type":"string","pattern":"^[0-9a-f]{32}$"}}',`queue_ids`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='不可变流程版本的查询队列范围,供实例列表在数据库内筛选当前权限';
CREATE TRIGGER `trg_version_scope_immutable` BEFORE UPDATE ON `weave_version_scopes` FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Workflow query scopes are immutable';
CREATE TABLE `weave_waits` (
`namespace_id` binary(16) NOT NULL COMMENT '资源所属命名空间,参与主键、授权与关联校验',
`id` binary(16) NOT NULL COMMENT '记录标识;与命名空间一起定位运行资源',
`revision` bigint unsigned NOT NULL DEFAULT '1' COMMENT '乐观并发版本,从 1 开始;更新时必须核对预期版本',
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间,统一 UTC,数据库精度为微秒',
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '最后写入时间,统一 UTC;不能代替并发版本',
`workflow_id` binary(16) NOT NULL COMMENT '所属流程实例标识',
`activation_id` binary(16) NOT NULL COMMENT '所属步骤激活,区分旧轮次和人工恢复后的新轮次',
`kind` varchar(16) COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT '节点或等待类型,决定由哪类推进规则处理',
`state` varchar(32) COLLATE utf8mb4_0900_as_cs NOT NULL DEFAULT 'WAITING' COMMENT '当前状态;只允许定义的状态转换并配合事务校验',
`correlation_key` varbinary(764) NOT NULL COMMENT '精确UTF8业务关联且保留尾部空格;新受理最多512字节,兼容历史191字符最多764字节,TIMER为空串',
`deadline_at` datetime(6) NOT NULL COMMENT '等待截止时间,用可靠受理时间判断信号是否按时到达',
`wake_task_id` binary(16) DEFAULT NULL COMMENT '内部定时唤醒任务;无需定时唤醒时为空',
`resolved_at` datetime(6) DEFAULT NULL COMMENT '等待或查询节点结清的时间;尚未结清时为空',
`signal_name` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL COMMENT '冻结信号名称;TIMER及待核实历史记录为空',
`payload_version` varchar(32) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL COMMENT '冻结信号内容契约版本;TIMER及待核实历史记录为空',
`winner_signal_id` binary(16) DEFAULT NULL COMMENT '胜出的外部信号标识;仅时间或查询结果结清时为空,所有者由同事务校验',
PRIMARY KEY (`namespace_id`,`id`),
UNIQUE KEY `uq_wait_workflow_id` (`namespace_id`,`workflow_id`,`id`),
UNIQUE KEY `uq_wait_winner_signal` (`namespace_id`,`winner_signal_id`),
KEY `ix_wait_deadline` (`state`,`deadline_at`,`namespace_id`,`id`),
KEY `fk_weave_waits_1` (`namespace_id`,`workflow_id`,`activation_id`),
KEY `fk_weave_waits_2` (`namespace_id`,`wake_task_id`),
CONSTRAINT `ck_weave_waits_0` CHECK ((`revision` > 0)),
CONSTRAINT `ck_weave_waits_1` CHECK ((`state` in (_utf8mb4'WAITING',_utf8mb4'SATISFIED',_utf8mb4'EXPIRED',_utf8mb4'CANCELLED'))),
CONSTRAINT `ck_weave_waits_2` CHECK ((`kind` in (_utf8mb4'SIGNAL',_utf8mb4'TIMER',_utf8mb4'POLL')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_cs COMMENT='可恢复的信号或时间等待,不长期占用业务 Worker';

View File

@@ -19,6 +19,15 @@ import (
//go:embed migrations/*.up.sql
var migrationFiles embed.FS
// freshInstallBaseline is the current schema without the historical foreign
// key DDL. Existing databases keep using the immutable forward migration
// chain; only an empty database takes this path.
//
//go:embed baseline.sql
var freshInstallBaseline string
const freshInstallBaselineVersion uint64 = 28
type Migration struct {
Version uint64
Name string
@@ -95,6 +104,16 @@ func Migrate(ctx context.Context, db *sql.DB) error {
_ = conn.Raw(func(any) error { return driver.ErrBadConn })
}
}()
var ledgerExists, businessTables int
if err := conn.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='orbit_schema_migrations'`).Scan(&ledgerExists); err != nil {
return err
}
if err := conn.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND LEFT(TABLE_NAME,6) IN ('orbit_','pulse_','weave_') AND TABLE_NAME<>'orbit_schema_migrations'`).Scan(&businessTables); err != nil {
return err
}
if ledgerExists == 0 && businessTables > 0 {
return errors.New("Orbit tables exist without migration ledger; refuse to guess schema history")
}
if _, err := conn.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS orbit_schema_migrations (
version BIGINT UNSIGNED PRIMARY KEY,
name VARCHAR(191) NOT NULL,
@@ -122,6 +141,12 @@ func Migrate(ctx context.Context, db *sql.DB) error {
if err != nil {
return err
}
if len(applied) == 0 && businessTables > 0 {
return errors.New("Orbit tables exist with an empty migration ledger; reconcile schema history before proceeding")
}
if len(applied) == 0 && businessTables == 0 {
return applyFreshInstallBaseline(ctx, conn, migrations)
}
known := map[uint64]Migration{}
for _, m := range migrations {
known[m.Version] = m
@@ -166,6 +191,41 @@ func Migrate(ctx context.Context, db *sql.DB) error {
return nil
}
// applyFreshInstallBaseline creates a new database directly at the current
// schema version. It records every historical migration as dirty before DDL so
// an interrupted baseline remains blocked for operator reconciliation, just
// like an interrupted ordinary migration. The baseline is intentionally
// limited to an empty database; it never rewrites or reinterprets an upgrade.
func applyFreshInstallBaseline(ctx context.Context, conn *sql.Conn, migrations []Migration) error {
if len(migrations) == 0 || migrations[len(migrations)-1].Version != freshInstallBaselineVersion {
return errors.New("fresh-install baseline is out of date; regenerate it before release")
}
statements, err := splitSQL(freshInstallBaseline)
if err != nil {
return fmt.Errorf("fresh-install baseline: %w", err)
}
for _, statement := range statements {
upper := strings.ToUpper(statement)
if strings.Contains(upper, "FOREIGN KEY") || strings.Contains(upper, " REFERENCES ") {
return errors.New("fresh-install baseline contains forbidden foreign-key DDL")
}
}
for _, migration := range migrations {
if _, err := conn.ExecContext(ctx, "INSERT INTO orbit_schema_migrations (version,name,checksum,dirty) VALUES (?,?,?,TRUE)", migration.Version, migration.Name, migration.Checksum); err != nil {
return err
}
}
for i, statement := range statements {
if _, err := conn.ExecContext(ctx, statement); err != nil {
return fmt.Errorf("fresh-install baseline statement %d failed (migration ledger remains dirty): %w", i+1, err)
}
}
if _, err := conn.ExecContext(ctx, "UPDATE orbit_schema_migrations SET dirty=FALSE, applied_at=UTC_TIMESTAMP(6)"); err != nil {
return err
}
return nil
}
func Status(ctx context.Context, db *sql.DB) ([]MigrationStatus, error) {
rows, err := db.QueryContext(ctx, "SELECT version,name,checksum,dirty FROM orbit_schema_migrations ORDER BY version")
if err != nil {

View File

@@ -17,6 +17,64 @@ import (
mysqldriver "github.com/go-sql-driver/mysql"
)
// A new database must use the generated current-schema baseline. The baseline
// path is what makes the no-foreign-key rule true during initialization, not
// only after the historical upgrade migration has run.
func TestIntegrationFreshInstallUsesNoForeignKeys(t *testing.T) {
dsn := os.Getenv("ORBIT_TEST_MYSQL_DSN")
if dsn == "" {
t.Skip("requires disposable MySQL")
}
ctx, stop := context.WithTimeout(context.Background(), 60*time.Second)
defer stop()
admin, err := Open(ctx, dsn, 2, 1)
if err != nil {
t.Fatal(err)
}
defer admin.Close()
name := "orbit_fresh_no_fk_" + string(executionID(t))
if _, err := admin.ExecContext(ctx, "CREATE DATABASE `"+name+"`"); err != nil {
t.Fatal(err)
}
defer func() {
cleanup, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := admin.ExecContext(cleanup, "DROP DATABASE `"+name+"`"); err != nil {
t.Error(err)
}
}()
cfg, err := mysqldriver.ParseDSN(dsn)
if err != nil {
t.Fatal("invalid test DSN")
}
cfg.DBName = name
db, err := Open(ctx, cfg.FormatDSN(), 4, 2)
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err := Migrate(ctx, db); err != nil {
t.Fatal(err)
}
var foreignKeys int
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=DATABASE()`).Scan(&foreignKeys); err != nil {
t.Fatal(err)
}
if foreignKeys != 0 {
t.Fatalf("fresh-install baseline introduced %d foreign keys", foreignKeys)
}
status, err := Status(ctx, db)
if err != nil {
t.Fatal(err)
}
if len(status) != int(freshInstallBaselineVersion) {
t.Fatalf("fresh-install migration count=%d, want %d", len(status), freshInstallBaselineVersion)
}
if err := CheckSchema(ctx, db); err != nil {
t.Fatal(err)
}
}
// 重建历史 v7,验证正式升级删除所有外键且保留索引、唯一约束和已有任务。
func TestIntegrationNoForeignKeysUpgrade(t *testing.T) {
dsn := os.Getenv("ORBIT_TEST_MYSQL_DSN")

View File

@@ -2,6 +2,7 @@ package mysql
import (
"regexp"
"strings"
"testing"
)
@@ -27,3 +28,26 @@ func TestMigrationsAfterForeignKeyRemovalDoNotReintroduceReferences(t *testing.T
}
}
}
func TestFreshInstallBaselineContainsNoReferenceDDL(t *testing.T) {
statements, err := splitSQL(freshInstallBaseline)
if err != nil {
t.Fatal(err)
}
for _, statement := range statements {
upper := strings.ToUpper(statement)
if strings.Contains(upper, "FOREIGN KEY") || strings.Contains(upper, " REFERENCES ") {
t.Fatalf("fresh-install baseline contains forbidden reference DDL: %s", statement)
}
if strings.Contains(upper, "ORBIT_SCHEMA_MIGRATIONS") {
t.Fatalf("fresh-install baseline must not own the migration ledger: %s", statement)
}
}
migrations, err := Migrations()
if err != nil {
t.Fatal(err)
}
if len(migrations) == 0 || migrations[len(migrations)-1].Version != freshInstallBaselineVersion {
t.Fatalf("baseline version=%d, migrations=%d", freshInstallBaselineVersion, len(migrations))
}
}

View File

@@ -15,45 +15,45 @@ const operationalMetricEventWindow = 5 * time.Minute
// Counts 只包含固定状态的有界计数;达到上限时 Truncated 为 true,不能解释为精确总量。
type OperationalSnapshot struct {
// TaskStates 是 Pulse 任务按状态的有界计数。
TaskStates map[string]uint64
TaskStates map[string]uint64 `json:"task_states"`
// TaskOldestAgeSeconds 是运行中任务中最早 available_at 的年龄。
TaskOldestAgeSeconds float64
TaskOldestAgeSeconds float64 `json:"task_oldest_age_seconds"`
// DeliveryStates 是结果交付按状态的有界计数。
DeliveryStates map[string]uint64
DeliveryStates map[string]uint64 `json:"delivery_states"`
// DeliveryOldestAgeSeconds 是待处理交付中最早 available_at 的年龄。
DeliveryOldestAgeSeconds float64
DeliveryOldestAgeSeconds float64 `json:"delivery_oldest_age_seconds"`
// WorkflowStates 是 Weave 实例按状态的有界计数。
WorkflowStates map[string]uint64
WorkflowStates map[string]uint64 `json:"workflow_states"`
// WorkflowOldestAgeSeconds 是未终结流程中最早 updated_at 的年龄。
WorkflowOldestAgeSeconds float64
WorkflowOldestAgeSeconds float64 `json:"workflow_oldest_age_seconds"`
// InboxStates 是 Weave 内部事件按状态的有界计数。
InboxStates map[string]uint64
InboxStates map[string]uint64 `json:"inbox_states"`
// InboxOldestAgeSeconds 是待消费内部事件中最早 available_at 的年龄。
InboxOldestAgeSeconds float64
InboxOldestAgeSeconds float64 `json:"inbox_oldest_age_seconds"`
// Truncated 表示至少一个状态达到有界采样上限。
Truncated bool
Truncated bool `json:"truncated"`
// RecentEvents 是固定事件类别在最近窗口内的有界数量。
RecentEvents map[string]uint64
RecentEvents map[string]uint64 `json:"recent_events"`
// TaskTerminalLatencySeconds 是最近窗口终态任务从创建到完成的平均耗时样本。
TaskTerminalLatencySeconds float64
TaskTerminalLatencySeconds float64 `json:"task_terminal_latency_seconds"`
// TaskTerminalLatencyMaxSeconds 是最近窗口终态任务样本的最大耗时。
TaskTerminalLatencyMaxSeconds float64
TaskTerminalLatencyMaxSeconds float64 `json:"task_terminal_latency_max_seconds"`
// WorkflowTerminalLatencySeconds 是最近窗口终态流程从创建到完成的平均耗时样本。
WorkflowTerminalLatencySeconds float64
WorkflowTerminalLatencySeconds float64 `json:"workflow_terminal_latency_seconds"`
// WorkflowTerminalLatencyMaxSeconds 是最近窗口终态流程样本的最大耗时。
WorkflowTerminalLatencyMaxSeconds float64
WorkflowTerminalLatencyMaxSeconds float64 `json:"workflow_terminal_latency_max_seconds"`
// TaskAcceptToStartLatencySeconds 是最近窗口任务从受理事件到开始事件的平均耗时样本。
TaskAcceptToStartLatencySeconds float64
TaskAcceptToStartLatencySeconds float64 `json:"task_accept_to_start_latency_seconds"`
// TaskAcceptToStartLatencyMaxSeconds 是最近窗口任务受理到开始耗时样本的最大值。
TaskAcceptToStartLatencyMaxSeconds float64
TaskAcceptToStartLatencyMaxSeconds float64 `json:"task_accept_to_start_latency_max_seconds"`
// TaskAcceptToTerminalLatencySeconds 是最近窗口任务从受理到确定终态事件的平均耗时样本。
TaskAcceptToTerminalLatencySeconds float64
TaskAcceptToTerminalLatencySeconds float64 `json:"task_accept_to_terminal_latency_seconds"`
// TaskAcceptToTerminalLatencyMaxSeconds 是最近窗口任务受理到确定终态耗时样本的最大值。
TaskAcceptToTerminalLatencyMaxSeconds float64
TaskAcceptToTerminalLatencyMaxSeconds float64 `json:"task_accept_to_terminal_latency_max_seconds"`
// WorkflowStartToTerminalLatencySeconds 是最近窗口流程从开始到停止事件的平均耗时样本。
WorkflowStartToTerminalLatencySeconds float64
WorkflowStartToTerminalLatencySeconds float64 `json:"workflow_start_to_terminal_latency_seconds"`
// WorkflowStartToTerminalLatencyMaxSeconds 是最近窗口流程开始到停止耗时样本的最大值。
WorkflowStartToTerminalLatencyMaxSeconds float64
WorkflowStartToTerminalLatencyMaxSeconds float64 `json:"workflow_start_to_terminal_latency_max_seconds"`
}
var (

View File

@@ -46,6 +46,20 @@ ORBIT_TEST_SERVER_BINARY="$TASK_TEST_DIR/orbit" "$TASK_GO" test -race -count=1 -
(cd sdk/go && GOWORK=off "$TASK_GO" build -o "$TASK_TEST_DIR/orbit-load" ./cmd/orbit-load)
ORBIT_MYSQL_DSN="$ORBIT_TEST_MYSQL_DSN" "$TASK_TEST_DIR/orbit-migrate" status
ORBIT_MYSQL_DSN="$ORBIT_TEST_MYSQL_DSN" "$TASK_TEST_DIR/orbit-admin" verify
TASK_SNAPSHOT_JSON=$(ORBIT_MYSQL_DSN="$ORBIT_TEST_MYSQL_DSN" "$TASK_TEST_DIR/orbit-admin" observe snapshot)
SNAPSHOT_JSON="$TASK_SNAPSHOT_JSON" python3 - <<'PY'
import json
import os
value = json.loads(os.environ["SNAPSHOT_JSON"])
assert value["sample_limit"] == 10000
assert value["event_window_seconds"] == 300
assert value["scope"] == "single database node; bounded sample, not a global exact count"
snapshot = value["snapshot"]
for key in ("task_states", "delivery_states", "workflow_states", "inbox_states", "recent_events"):
assert isinstance(snapshot[key], dict), key
assert isinstance(snapshot["truncated"], bool)
PY
ORBIT_MYSQL_DSN="$ORBIT_TEST_MYSQL_DSN" "$TASK_TEST_DIR/orbit-admin" retention inventory --before 2000-01-01T00:00:00Z
ORBIT_MYSQL_DSN="$ORBIT_TEST_MYSQL_DSN" "$TASK_TEST_DIR/orbit-admin" bootstrap --application sdk-example --namespace default --environment test --queue files --output "$TASK_TEST_DIR/sdk.credentials.json"
ORBIT_MYSQL_DSN="$ORBIT_TEST_MYSQL_DSN" "$TASK_TEST_DIR/orbit-admin" bootstrap --application sdk-load --namespace default --environment test --queue load --output "$TASK_TEST_DIR/load.credentials.json"