七个竞品复刻与 Lumen 最终设计

七个竞品复刻与 Lumen 最终设计

每个竞品:总览与架构、Agent 交互过程设计、ER 图、表结构、接口清单、代码与下载。最后是我们自己的设计,以及每一项为什么这样更好。

一页读懂

这份方案做了三件事:把七个竞品逐一复刻并实测,画出它们的 ER 图、表结构、接口清单和 Agent 交互,再吸收各家最有效的做法,得出我们自己的设计(Lumen)。技术看架构、ER、接口与代码;产品看竞品对比与取舍原因。

重点30 秒结论
  • 七家后端几乎一样,差别只在入口和外观 → Lumen 只做一个内核,品牌和入口做成配置(D17)。
  • 三个最关键的设计:先报价、批准带哈希,不会错扣或重复扣(D2);积分复式账本,先冻结再结算(D3);Agent 产出落成可编辑的项目文档,不留在聊天记录里(D1、D6)。
  • 已证明:7 个测试站真实跑通对话与视频生成。未证明:最终技术栈(PostgreSQL + Temporal)还没实现,见“技术方案自审”。
  • 怎么读:产品看下面的竞品对比;开工看“我们的最终设计 → 落地指南”;试用看“在线试用与部署”。

七个竞品,各自做对了什么

竞品它的做法Agent 交互特点我们借鉴 / 不采纳决策
TopView商品链接 → 广告片;同一项目有两种编辑模式审批有两种:每次先问(Ask First)/ 始终允许(Always Allow)Ask First 的审批思路;同项目多视图D1 D2
Pollo营销批量生成 + 大模板库(63 个技能)流式事件,可停止,刷新后恢复事件带序号可续读;模板与技能做成数据D8 D9
CreaTok缺信息时弹结构化表单;方案是 3 镜头表格只改第 2 镜头,其余不动澄清卡(必填/单选)和镜头局部修改D5 D6
Flova画布 / 分镜 / 时间轴三视图发送前就提示积分够不够发送前说明准入;三视图共用一份项目D1 D7
LibTV画布 + 故事板 + Agent;技能社区技能固定版本绑定会话;预算只提醒技能版本固定。不采纳“只提醒不拦截”的预算D1 D9
invideo AI一句话让 Agent 出片,再进编辑器Agent 产物可在编辑器继续改Agent 产出落成可编辑对象D1
CreatifyURL → 视频,入口最短入口多,内核同一套多个入口汇成同一个“创作意图”D11

“决策”编号对应最终设计里的取舍表(D1–D17),点“我们的最终设计 → 设计取舍与原因”可查每一条。

最终设计:六条原则,每条都有原因

原则怎么做为什么好
1. 一个核心,多个入口七家后端结构实测几乎一样(24 张表、55 个接口形状),差别只在入口与外观。内核只做一份,品牌和入口是配置与适配器,新增产品不重写后端。
2. 对话、画布、时间线共用一份项目文档Agent 的产出落成节点(storyboard → scene → shot),不留在聊天记录里。用户改画布,Agent 能看见;只改第 2 镜头不会误伤别的;撤销与版本只做一次。
3. 先报价,再批准,批准带哈希生成前给带有效期的报价;批准必须带请求哈希与报价哈希;重复批准只返回同一任务。用户看到的价格和提示词,与实际执行的不可能不一致;也不会重复扣费。预算是硬上限,不是提醒。
4. 积分用复式账本:先冻结,再结算充值、赠送、冻结、结算、释放都是只追加的分录。每一分有来源和去向;失败的任务不会吞积分(明确未受理的失败释放冻结,状态不明的标“待核对”);日额度与积分分开建模。
5. 缺信息用结构化澄清卡,可恢复运行进入等待状态,下发带 ID 的卡片;提交带卡片哈希,恢复同一个 Run。刷新、断线后卡片还在;答案可校验;重复提交不会恢复两次。
6. 模型靠“注册表 + 适配器”接入模型、版本、价格、约束放数据里;适配器按 key 注册;每个模型独立 worker。我们实测:加一个模型只需登记一份数据,再起一个带环境变量的独立 worker,核心代码不动;Seedance 2.0 已真实出片。

架构

flowchart TB
  subgraph Client[客户端]
    WEB[Web 工作台<br/>对话 · 画布 · 时间线]
    EXT[外部 Agent / MCP / API]
  end
  subgraph Core[核心服务 Go 模块化单体]
    PRJ[项目 · 文档<br/>doc_version]
    AGT[Agent 编排接入]
    BIL[账务 · 报价 · 冻结]
    AST[素材]
    INT[创作意图 intent<br/>入口适配器]
  end
  subgraph Runtime[运行时]
    RUN[Agent Runtime Python]
    TMP[Temporal 工作流]
    GWY[模型网关<br/>注册表 + 适配器]
    RND[渲染集群]
  end
  WEB --> PRJ
  WEB --> AGT
  EXT --> AGT
  AGT --> RUN
  INT --> AGT
  RUN -->|同一套工具面| PRJ
  RUN --> BIL
  BIL --> TMP
  TMP --> GWY
  TMP --> RND
  GWY --> M[(视频 / 图片 / 声音模型)]
  PRJ --- DB[(PostgreSQL)]
  BIL --- DB
  AST --- DB

一次生成的完整过程

sequenceDiagram
  autonumber
  participant U as 用户
  participant S as 业务后端
  participant A as Agent
  participant M as 模型适配器
  U->>S: 发送前:查额度与准入
  U->>S: 发消息(client_action_id)
  S->>A: 启动 Run
  A-->>U: 缺信息?经后端下发澄清卡
  U->>S: 回答卡片(带卡片哈希)
  S->>A: 恢复同一个 Run
  A->>S: 方案落成镜头节点
  U->>S: 点“生成”,请求报价
  S-->>U: 报价(含有效期与两个哈希)
  U->>S: 批准(请求哈希 + 报价哈希)
  S->>S: 冻结积分并建任务
  S->>M: 提交生成(幂等键)
  M-->>S: 结果 / 失败
  S->>S: 结算或释放
  S-->>U: 事件流(序号可续读)

核心 ER(26 张表,取自最终设计;线上是基数 1:N 一对多、0..1:N 父可选、1:1 一对一,N:M 为多对多;完整的 46 张和逐条关系在“我们的最终设计 → ER 图”)

默认缩放到页面宽度,先看整体结构;点一下图切换到原始大小(看清表名,在框内上下左右滚动),再点一下缩回。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":28,"rankSpacing":60}}}%%
flowchart LR
  subgraph sg_tenant["租户与成员"]
    direction TB
    t_app_user["app_user ◆"]
    t_workspace["workspace"]
    t_member["⇄ member"]
  end
  subgraph sg_canvas["画布 · 时间轴(项目文档)"]
    direction TB
    t_project["project"]
    t_project_asset_ref["⇄ project_asset_ref ◆"]
    t_node["node"]
    t_node_version["node_version"]
    t_edge["⇄ edge"]
    t_timeline["timeline"]
    t_export_job["export_job"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_agent_session["agent_session"]
    t_agent_run["agent_run"]
    t_run_event["run_event"]
    t_interaction["interaction"]
    t_run_action["run_action ★"]
    t_skill["skill ◆"]
    t_skill_version["skill_version ◆"]
  end
  subgraph sg_gen["生成 · 报价批准 · 模型"]
    direction TB
    t_model_catalog["model_catalog"]
    t_model_version["model_version ★"]
    t_generation_quote["generation_quote ★"]
    t_generation_approval["generation_approval ★"]
    t_generation_task["generation_task"]
  end
  subgraph sg_asset["素材"]
    direction TB
    t_asset["asset"]
  end
  subgraph sg_billing["积分账本 · 订阅 · 额度"]
    direction TB
    t_ledger_entry["ledger_entry"]
    t_point_lot["point_lot"]
    t_hold["hold"]
  end
  t_asset -->|"0..1:N"| t_project
  t_project -->|"1:N"| t_node
  t_node -->|"0..1:N"| t_node
  t_node -->|"1:N"| t_node_version
  t_asset -->|"0..1:N"| t_node_version
  t_generation_task -->|"0..1:N"| t_node_version
  t_project -->|"1:N"| t_edge
  t_node -->|"1:N"| t_edge
  t_node -->|"1:N"| t_edge
  t_project -->|"1:N"| t_timeline
  t_timeline -->|"1:N"| t_export_job
  t_asset -->|"0..1:N"| t_export_job
  t_hold -->|"0..1:1"| t_export_job
  t_project -->|"1:N"| t_agent_session
  t_agent_session -->|"1:N"| t_agent_run
  t_agent_run -->|"1:N"| t_run_event
  t_agent_run -->|"1:N"| t_interaction
  t_project -->|"0..1:N"| t_generation_task
  t_node -->|"0..1:N"| t_generation_task
  t_agent_run -->|"0..1:N"| t_generation_task
  t_model_catalog -->|"0..1:N"| t_generation_task
  t_hold -->|"0..1:1"| t_generation_task
  t_generation_task -->|"0..1:N"| t_asset
  t_point_lot -->|"0..1:N"| t_ledger_entry
  t_generation_quote -->|"1:N"| t_generation_approval
  t_generation_task -->|"0..1:N"| t_generation_approval
  t_model_version -->|"1:N"| t_generation_quote
  t_model_catalog -->|"1:N"| t_model_version
  t_agent_run -->|"1:N"| t_run_action
  t_app_user -->|"1:N"| t_member
  t_app_user -->|"1:N"| t_agent_session
  t_app_user -->|"1:N"| t_project
  t_skill -->|"1:N"| t_skill_version
  t_skill -->|"1:N"| t_agent_session
  t_agent_run -->|"0..1:N"| t_generation_quote
  t_project -->|"1:N"| t_project_asset_ref
  t_asset -->|"1:N"| t_project_asset_ref
  t_node <-.->|"N:M · 经 edge"| t_node
  t_workspace <-.->|"N:M · 经 member"| t_app_user
  t_project <-.->|"N:M · 经 project_asset_ref"| t_asset
classDef tenant fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef canvas fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef gen fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef billing fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef platform fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
class t_app_user,t_workspace,t_member tenant
class t_project_asset_ref,t_project,t_node,t_node_version,t_edge,t_timeline,t_export_job canvas
class t_skill,t_skill_version,t_agent_session,t_agent_run,t_run_event,t_interaction,t_run_action agent
class t_model_catalog,t_model_version,t_generation_quote,t_generation_approval,t_generation_task gen
class t_asset asset
class t_point_lot,t_ledger_entry,t_hold billing
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_project_asset_ref,t_member,t_edge junction
  style sg_tenant fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_canvas fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  style sg_gen fill:none,stroke:#22c55e,stroke-width:1px
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px
  style sg_billing fill:none,stroke:#eab308,stroke-width:1px
  linkStyle 13,14,15,16,19,28,30,32,33,34 stroke:#f97316,stroke-width:2.5px

核心接口(12 条;完整的 55 条按领域分组,在“我们的最终设计 → 接口清单”)

路径作用状态
POST/projects新建项目(可带 kind、输入)原有
POST/projects/{id}/sessions创建会话(选 skill)原有
POST/sessions/{id}/messages请求头 / body 带 client_action_id;返回 202 + run_id;同 ID 不同内容 409调整
GET/sessions/{id}/events (SSE)事件流,支持 Last-Event-ID原有
POST/runs/{id}:stop幂等停止;回执 status=terminal_observed | unknown;已输出的文字保存为一条 status=stopped 的助手消息;不承诺供应商侧已取消计费调整
POST/interactions/{id}:respond带 card_sha256;答案按卡片 schema 校验;已恢复过的卡再提交返回原回执调整
POST/generations:quote报价,不扣费;返回 quote_id、request_sha256、quote_sha256、expires_at调整
POST/generations批准并创建任务:body 带 quote_id、client_approval_id、两个哈希;重放返回同一任务;报价过期返回 409 quote_expired调整
GET/generations/{id}查询任务原有
GET/billing/balance余额、冻结、即将过期原有
GET/models?capability=模型清单:版本、约束、价格表(来自 model_version)调整
POST/projects/{id}/graph:patch批量变更节点/边(画布拖拽、Agent 写入)原有

已实测 / 未实测

复刻是否完美?审计结论 388 项能力逐项核对,展开查看

复刻是否完美?审计结论(2026-10-19)

没有一个品牌是完美复刻。七个竞品官方资料里列出的 388 项能力,在复刻页上端到端实测跑通的只有 13 项(另有 1 项只在隔离环境跑通),59 项后端有接口但从没在该品牌页面上跑过,308 项复刻里根本没有后端。真正能用的是一条主线:登录 → 新建项目 / 会话 → Agent 对话 → 停止;Pollo 另外跑通了报价 → 批准 → 文生视频、切换模型、刷新恢复、素材页。

为什么不可能做到“完美”:竞品的源码和后端都不公开,能拿到的只有界面、公开文档和自己账号下看到的行为。复刻的前端是按真实页面的可见结构与样式重做的静态页,加一套七个品牌共用的脚本;后端是我们自己的一套实现,七个品牌库的表结构完全相同(各 24 张表)—— 所以各竞品页里的 ER 图和接口清单描述的是“我们的复刻后端”,不是竞品真实的后端。

品牌官方能力实测跑通只有通用对话后端有、没跑过没有后端真实页面的控件文字,复刻页找到可点控件点了有效只提示“正在接入”没反应 / 只变样式
TopView631095315/38(画布编辑器(新建项目后,已登录))517383
Pollo805 +1 隔离056915/18(首页(已登录))4714283
invideo AI5811114517/18(首页(已登录 Agent Two))123201089
LibTV343292029/59(首页(已登录))4515260
Flova6320184311/17(首页)367211
CreaTok451323955/65(工作台首页(已登录))784642
Creatify45015393/20(Agent 首页(已登录))274153

口径:能力来自各家的功能目录(官方说明、公开代码、实际观察);“实测跑通”只认在该品牌复刻页上真实跑过的记录(真实 Maa、真实生成)。控件对照用的是真实产品已登录时采集的 DOM。“可点控件”是登录后首页上所有可见的按钮、链接和可点元素逐个点击的结果(发送、批准、删除、退出、停止这类不点)。脚本都在下载包的 verification/ 里,可以重跑。

在线试用与部署

重点试用方法
  • 7 个站各自是一整套独立部署(前端、BFF、Maa 智能体、数据库、生成 worker),地址见下表。
  • 注册任意邮箱即可登录对话;生成视频需要先发测试积分(运维脚本 grant_credits.sh)。
  • 7 个站都已真实跑通:注册 → Agent 对话 → 停止 → 报价 → 批准 → 出片。

七个复刻站各自是一整套独立部署:自己的前端、BFF、Maa 智能体、MySQL 库(身份库 + 7 个档案库)、Postgres 库(Maa)和 3 个视频生成 worker(Seedance 2.5 / 2.0 Fast / 2.0 Mini)。注册任意邮箱即可试用,测试积分由运维脚本发放。这是测试环境,不对外宣传,页面带 noindex 和“内部复刻研究”标识。

全部代码:all-brands-code.zip · 后端与合同:backend-and-contracts.zip

部署结构

层做法原因
入口nginx,每个站一个端口:http://119.23.106.60:8101–8107,文档 8108;纯 HTTP,同一页面、同一 BFFIP 入口使用单独的非 Secure 会话 Cookie(studio_ip_session),仅允许配置里列出的 IP:端口;HTTP 下密码与登录态明文传输,只用于测试
前端复刻页面原样部署,只改后端地址为同源、加 noindex 与标识;内联图片和字体在部署时外置成按内容寻址的文件CreaTok 页面原本 12 MB,服务器出口慢,首屏要 100 秒;外置后 HTML 3.2 MB、2.8 秒
BFF同一个 Go 二进制,7 份配置(域名、库名、品牌档案)决策 D17:一个共享内核加品牌配置
Maa同一个镜像(b75bc95),7 个实例,各连自己的 Postgres 库会话、运行、澄清卡互不串号
网络每个品牌一个 socat “pod”,BFF 与 Maa 共用它的网络命名空间BFF 只接受回环地址的监听、数据库和 Maa 地址
视频生成每品牌每模型一个 worker,走模型网关;输出只允许火山引擎 TOS 域名与正式环境同一套准入、报价、批准、结算流程
资源每个容器有内存上限;MySQL 关闭 performance_schema、缓冲池 256 MB同机还有原有的 grafana / zabbix,不能被挤掉
备份每天 04:07 导出 MySQL 与 Postgres,保留 7 天测试数据也要能恢复

在部署上实际跑通的流程

流程范围结果
注册、登录、建项目、建会话、绑定 Agent 模型、发消息、收到回复7 个站已验证 7/7,回复 14–25 秒
真实图片生成:报价 → 批准 → 任务 → 出图Pollo、TopView、LibTV、CreaTok、Flova,各自的图片入口已验证 5/5 出图 1024×1024,2 积分/张(Qwen-Image 2.1 Turbo)
真实视频生成:报价 → 批准 → 任务 → 成片7 个站,Seedance 2.0 Mini 5 秒已验证 7/7 任务成功,约 2 MB MP4(Pollo 抽检 h264 720×1280 5 秒)
停止正在运行的回复Pollo、TopView已验证 运行状态变为 cancelled
图片附件参与对话Pollo已验证 上传红色方图,Agent 回答“红色”
澄清卡:缺信息时提问,回答后同一次运行继续Pollo已验证 询问 product_name,回答后同一 run 完成
页面引用的脚本与样式全部存在7 个站已验证 部署前自动校验,缺一个就不发布

技术方案自审

用线上部署去检验技术方案:方案里的每条关键决策,在部署上有没有被真实流程证明;部署过程中暴露的问题,哪些已经修掉、哪些留作待办。

重点自审结论
  • 17 条决策:6 条已验证,5 条部分验证,6 条没有在线验证(多为最终设计独有、复刻站没有的能力)。
  • 最大风险:复刻站和最终设计是两套技术栈。复刻证明了交互与业务合同,没证明 PostgreSQL + Temporal 的选型;落地时要在新栈上重跑同一套用例。
  • 部署中发现的问题大多已修复;仍待办的是充值对账没验证、最终技术栈没实现。

决策逐条核对

编号决策状态依据
D1一个项目、三种视图共用一份文档未验证部署的是复刻页面,三视图共用一份文档的编辑器没有在线验证。
D2先报价,再批准,批准带哈希已验证每次生成都先报价,批准时带 request_sha256 与 quote_sha256;7 个站都按此流程生成成功。
D3积分用复式账本,先冻结再结算部分验证积分先校验再扣(不足时返回 credits_insufficient);冻结与结算的账本分录没有逐条对账。
D4文字推理成本和媒体成本分开显示未验证界面没有分开展示推理成本和媒体成本。
D5缺信息时用结构化澄清卡已验证Pollo 上澄清卡提问、回答后同一次运行继续。
D6方案是结构化实体,不是一大段文字部分验证Agent 回复里有分镜方案,但方案没有作为结构化实体落库展示。
D7发送前就说明准入条件部分验证积分不足、模型不可用在提交时就被拒;发送前的提示文案没有逐一检查。
D8事件流带游标,可停止、可恢复部分验证停止已验证;断线后按游标续读事件没有单独测试。
D9Skill 与模板有版本,会话固定绑定版本未验证Skill / 模板版本绑定没有在部署上验证。
D10模型靠注册表和适配器接入已验证模型来自注册表(studio_media_models):3 个 Seedance 视频模型 + 2 个 Qwen-Image 图片模型,新增图片模型只加数据和 worker;其余 3 个视频模型禁用。
D11不同入口汇成同一个「创作意图」未验证多入口汇成同一“创作意图”没有在线验证。
D12素材归用户,项目只引用部分验证附件以素材 ID 引用已验证;素材归属与项目引用的完整规则没有测试。
D13修订可恢复未验证修订恢复没有在线验证。
D14能力对内外一致:Agent、API、MCP 用同一套工具面未验证MCP 对外工具面没有部署。
D15不展示做不到的功能已验证没有后端的入口保持为明确的占位,不假装可用。
D16登录与会话状态要有可见入口已验证未登录、会话过期都有可见入口(Pollo 过期横幅已验证)。
D17一个共享内核加品牌配置,而不是七套代码已验证7 个站是同一个 BFF 二进制和同一个 Maa 镜像,只有配置不同。

发现的问题

级别问题影响处理状态
高复刻站和最终设计用的是两套技术栈:复刻站跑在现有的 Go BFF + MySQL + Maa 上,最终设计是 PostgreSQL + Temporal + 模型网关 gRPC。复刻站证明的是交互与业务合同(报价/批准、澄清卡、停止、附件、生成),不能证明最终设计的存储和工作流选型。落地时先在最终栈上实现同一组合同测试(e2e.cjs / gen.cjs 的流程),两边跑同一套用例再切换。待办
高首次部署只拷贝了部分样式目录,Pollo 有 7 个样式层 404,页面能用但样式不完整。线上效果与本地验收不一致,之前的视觉比对结论对线上不成立。同步时拷贝全部 CSS/JS;新增 check_refs.py,引用缺失就不发布。已修复
高容器没有内存上限,同机的 grafana / zabbix 可能被挤掉(可用内存一度只剩约 2 GB)。违反“不要动服务器上原来的服务”。所有容器加内存上限;MySQL 关闭 performance_schema、缓冲池 256 MB(1.3 GB 降到 0.4 GB)。已修复
中CreaTok 页面 12 MB(大量内联 base64 图片),服务器出口约 120 KB/s,首屏约 100 秒。试用体验差,容易被误判为站点挂了。开启 gzip;部署时把内联图片和字体外置为按内容寻址的文件并长缓存;页面运行时对素材表做兼容。已修复
中数据库没有备份。测试数据和会话无法恢复。每日逻辑备份,保留 7 天。已修复
中测试积分由脚本直接写入授权,没有走支付。计费链路只验证了“报价—冻结—扣减”,没有验证充值与对账。正式环境接支付后补对账测试。待办
中BFF 调 Maa 的服务凭据有效期 365 天(2026-10 签发)。到期后所有站对话会返回 401。每天自动检查凭据和证书到期,剩 30 天写警告,状态公开在 http://119.23.106.60:8108/status.txt;到期前用 issue_service_tokens.sh 重新签发。已修复
低生成验收脚本读取成片素材 ID 的字段不对,报告为“asset none”;LibTV 有一次成片接口返回 404。成片已生成(任务 succeeded、文件存在),但脚本没有自动核对素材下载。原因是素材 ID 是 32 位十六进制而不是 UUID,加上成片读取比任务完成略晚;脚本已修正并重试,复测 TopView 视频与 5 个站的图片都读到成片。已修复
中图片生成原先没有接:整条生成链路写死为视频,Image 模式只提示未接入。图片能力无法演示。后端支持图片模型(同一套报价、批准、结算;图片只能是图片任务的产出),接入 Qwen-Image 2.1;5 个有图片入口的站点都能真实出图。已修复
中Agent 运行时界面只显示“Agent is working…”,一轮常常要几分钟(30 多次工具调用),用户看不到进度。容易被当成卡死。BFF 新增只读的进度接口(只放出工具名和待办标题,经过隐私过滤),7 个站在输入框上方显示“任务进度 n/m”和当前步骤,与官方 Pollo 一致。已修复
中Pollo 澄清卡没有输入框,答案要在下方输入框里填,但界面没有任何提示。用户以为无法回答。卡片标签旁加说明,输入框加橙色描边和“Enter the product name…”提示;真实界面回答后同一个 Run 继续。已修复
中交给其他同事的编码 Agent 开发时,接口没有数据结构,仓库里还有一份冲突的旧接口规格。编码 Agent 会被带偏或无从下手。发布 lumen-handoff.zip:OpenAPI 3.1(61 个操作,已校验)、可执行建表脚本、契约、状态机、验收用例、待拍板事项。已修复
中部署新后端时 28 个容器同时重连,MySQL 超出 1 GB 内存上限被系统杀掉,服务中断约 5 分钟。测试环境短暂不可用。MySQL 上限 1.8 GB,最大连接数限到 450(常驻约 280),内存有上界;批量重启分两批。1.6 GB 时又被杀过一次,加连接上限后稳定。已修复
中二级页面与官方差异大:TopView 画布顶栏积分只显示“积分”、四个工具图标重复、工作区切换器压住按钮;Creatify 项目列表和 Ad Flow 的下拉、按钮、侧栏图标与官方不符。用户一眼看出不是同一个产品。BFF 新增只读积分余额接口,画布与 Upgrade 显示真实余额;对照官方截图重做 TopView 画布和 Creatify 项目列表、Ad Flow。已修复
高服务器出口带宽约 1 Mbps,CreaTok 首页 4.7 MB(单张 PNG 1.8 MB),首屏 39 秒;运行时核对登录的请求排在图片后面超过 15 秒被中止,页面显示“创作服务暂时未准备好”。用户打开 CreaTok 就看到报错,只能刷新。部署时大图转 WebP(宽度最大 1600 px),首屏外的图片懒加载。CreaTok 首屏降到 4.5 秒(0.7 MB),7 个站单独打开都不再出错。已修复
中CreaTok 画布、LibTV 画布、invideo 时间线显示官方采集时的样例项目名;LibTV 画布顶栏还写着“实际AI/生成/支付未接”(生成已接通);新建项目弹窗的按钮被套上首页大按钮样式。看起来不是自己的项目,说明文字与实际不符。三处标题改为当前项目名(未选时显示“未命名项目”/“Untitled project”),去掉过时说明,画布积分显示真实余额;弹窗按钮统一为一行。已修复
中登录核对请求超过 15 秒就整页报错,只能手动刷新;网络慢或多人同时打开时仍会出现。偶发“创作服务暂时未准备好”。部署时给启动加自动重试(最多 3 次,间隔 2 秒);模拟首个请求挂起 20 秒,页面自动恢复登录,不再报错。已修复
中“我的项目”弹窗只能列项目、不能新建,空账号打开后无路可走(Flova 等);Flova 工作区标题为空、没有积分、项目/会话切换器挤在聊天框底部。新用户走不通。弹窗加“新建项目”按钮(调用运行时自己的新建流程);Flova 工作区显示当前项目名、真实积分,切换器移到项目名旁。已修复
中Flova 工作区“时间线”标签点了没反应;Creatify 视频编辑器左栏图标重复、右上图标是文字符号、积分未显示;Creatify 的项目/会话切换器偶发显示成“ / ”。时间线无法查看;切换器看不出当前项目。时间线标签接到运行时自带的时间线视图,布局与高亮跟随当前视图;编辑器换成独立图标并显示真实积分。切换器空白的原因是运行时把“项目标题”绑定到了 Ad Flow 标题,刷新时清空切换器文字后不再写回;现在文字为空时从同名控件取值补上,连测 3 次都正常。已修复
高Agent 方案里没有消息表,历史只能从事件拼;刷新后恢复不了停止键和写到一半的回复;停止后半截回复丢失。实测 Pollo 与复刻都有这些问题。刷新或停止后,用户看到的和服务端保存的不一致。方案新增 agent_message 表、会话列表 / 快照 / 消息翻页 3 个接口;事件带 session_id 和步骤标题,接流时用 assistant.snapshot 续接;新增 payment_required 卡片;补 9 条验收用例(AGT-11~19)。复刻前后端还没按此改造。进行中
中复刻的 Agent 回复写法与各竞品不同;聊天区不渲染表格,分镜表显示成一行行竖线。对话内容一眼看出不是同一个产品。每站 maa 拼入按实测整理的回复版式(结构、口吻、结尾),并禁止编造数字和促销;新增 Markdown 渲染(标题、表格、列表、分隔线)。7 站用同一任务对照,结构一致。已修复
低LibTV 首页倒计时每秒改文本,运行时随即重设属性,空闲时每秒约 70 次 DOM 变动。空闲耗电、页面偶有闪动。倒计时数字放进 Shadow DOM,文档级观察器看不到,空闲变动降为 0。已修复
低6 个统一档案站点的旧会话可能打不开(新会话正常)。只影响部署前创建的会话。保持现状,测试环境不迁移旧数据。接受
低访问入口改为 IP + 端口的纯 HTTP。密码和登录态明文传输,同一 IP 只能靠端口区分站点。BFF 只对配置列出的 IP:端口放行,并给它们单独的非 Secure Cookie;仅作测试环境,不放真实账号。接受

没有覆盖的范围

TopView · 总览

重点TopView 看这三行就够
  • 它是什么:商品链接 → 广告片;同一项目有两种编辑模式;Agent 交互:审批有两种:每次先问(Ask First)/ 始终允许(Always Allow)。
  • 我们借鉴:Ask First 的审批思路;同项目多视图(决策 D1 D2)。
  • 复刻现状:4 项已验证,在线试用 http://119.23.106.60:8101/;后端与其余六个品牌共用一套(24 张表、55 个接口),所以 ER / 表结构 / 接口页的通用部分默认折叠。

围绕 Canvas Agent、Drama Studio、Board 和广告工作流,覆盖 3D 镜头预演、团队共编及 API/MCP/Skill/Plugin;另有广告收藏扩展和原生应用。主要证据来自官方公开说明和 API 合同,尚无完整登录媒体实测。

证据:W 官方说明,A 官方接口合同,C 客户端代码,O 实测。“未实测”= 只核对了官方页面,没有上传素材或生成。

能力目录
63 项,分 12 类
入口
首页(Video Agent / Canvas / Drama Studio)
复刻后端
24 张表 · 34 个外键 · 85 个索引

能力目录

Agent 与场景 3 项
能力说明证据限制
创作 Agent输入想法、脚本、参考视频或商品链接,Agent 规划镜头和生成素材。W官网产品说明;当前 Agent 登录请求和完整媒体任务未实测。
场景 Skill官方 Skill 覆盖商品短视频、短剧、试穿、痛点广告和电影风格等流程。W官网列出的内置场景;未逐个调用,场景名称不等于独立模型。
计划审阅与逐步推进Drama Studio 先产出计划,允许修改和批准后再进入分镜与生成。W官网工作流;批准记录、撤销和失效规则未验证。
项目与会话 2 项
能力说明证据限制
结构化剧情工作台大纲、集数、人物、地点、道具、剧本和 beat sheet 可在聊天外编辑。W官网界面说明;实体键、版本与并发保存协议未知。
Campaign Workspace研究、策略和生成资产保留在同一活动工作区,作为后续创作上下文。W官方说明;持久化对象、Web 项目映射和隔离权限未验证。
视频生成 9 项
能力说明证据限制
文生视频与模型比较文字生成视频,可将同一提示词交给多个模型比较结果。W多模型对比入口已说明;规格、时长与成本依模型变化,未生成。
图生视频与参考创作图片作为视频起点,并通过参考或提示词控制画面方向。W官网功能;参考类型和一致性效果未逐模型验证。
参考动作控制角色图片配参考视频,迁移动作、表情和身体运动。W未实测
视频角色全身替换以参考角色替换目标视频中的人物身体,保留动作。W未实测
视频放大提升视频分辨率和清晰度。W未实测
视频水印移除移除视频中的覆盖标记,可选择处理区域。W未实测
视频字幕移除移除已有烧录字幕。W未实测
唇形同步官方工具目录提供将人物口型与输入音频匹配的入口。W官方导航可见入口;具体页面本次访问失败,未验证参数或生成结果。
视频延长官方 Plugin 提供继续生成已有视频后续内容的工具。W官网插件说明;Web 独立入口、时长与输出质量未验证。
图片生成与编辑 13 项
能力说明证据限制
图片生成与模型比较文字生成图片,可比较不同模型结果并继续编辑。W未实测
蒙版局部重绘涂抹需要更改的区域,移除、替换或添加物体。W官网说明;未测试边缘质量与未选择区域的保持程度。
图片角度编辑以已有图片为起点调整观察方向或构图。W未实测
图片换脸用面部参考替换目标图片中的脸。W未实测
图片角色替换以人物参考替换目标照片中的整个人物。W未实测
图片放大扩大图片并增强画面清晰度。W未实测
图片文字编辑修改图片中已存在的文字内容。W未实测
图片重新打光调整人物或物体图片的光照方向与氛围。W未实测
商品摄影将商品参考制作成营销展示照片。W未实测
图片色板提取从参考图片提取颜色,供后续视觉创作使用。W未实测
图片反推提示词根据参考图片生成描述提示词。W未实测
图片去背景MCP 创作流程提供从图片移除背景的步骤。W官网示例流程可见;未调用,不确认独立 Web 入口或透明格式。
图片裁剪Plugin 提供裁剪参考图片的工具。W官网插件说明;尺寸限制和 Web 编辑能力对应未验证。
角色与音频 6 项
能力说明证据限制
说话数字人照片或预设角色配文字或音频生成口播视频。W官网三类入口;角色训练、口型和商品持有关系未亲测,时长上限不跨工具通用。
多语言配音输入脚本选择语言和音色,生成可用于视频的旁白音频。W未实测
声音克隆参考音频用于生成可复用的声音。W未上传声音;授权、样本要求和支持语言须确认。
歌曲与背景音乐文字、歌词或音乐参考生成歌曲或纯音乐,并调整风格和情绪。W官网称可商业使用,但许可范围以条款和素材来源为准;未生成试听。
商品数字人以商品参考生成数字人展示内容。W未实测
自建数字人形象官方提供 Design My Avatar 入口,用于创建自己的角色形象。W官方入口与页面标题已核实;具体创建输入、角色训练和复用范围未验证。
画布与时间轴 7 项
能力说明证据限制
自由画布与场景组织在画布上摆放故事板、参考和生成片段,组织多场景与局部修改。W官网说明;画布保存、快照、权限和导出协议未知。
节点连接、分组与导航插件展示节点连接、吸附对齐、分组和 minimap,便于组织创作依赖。W官网插件说明;当前 Web 与插件的节点 Schema 和功能对应未实测。
绘制编辑与参考卡片Draw to Edit 可标记修改区域,参考卡片可配合裁剪、调色和提示词反推。W官网工具入口;操作质量与卡片同步一致性未验证。
剧本到镜头分镜脚本或想法与参考图生成镜头分镜,表达构图、机位和情绪。W未实测
分集镜头生产与版本比较按 beat 生成镜头,保留参考与候选版本,再继续生成、后处理和导出。W官网流程;版本不可变性、输出格式与耗时未实测。
3D 镜头预演摆放人物和道具,先调整空间位置和相机,再将构图用于生成。W空间规划是官网能力;不代表生成模型保证人物身份或物理关系。
可漫游 3D 世界文字、图片或短视频生成可漫游场景,截取机位图返回 Canvas。W官方说明产物为 Gaussian Splat/SPZ,并非 GLB/STL 网格;未生成或导出。
素材与品牌 3 项
能力说明证据限制
统一 Board 素材库集中管理图片、视频、音频和数字人,供结果检查、比较与复用。W官网目录说明;Board 页面正文有限,用户级/团队级归属与素材版本未知。
签名上传与文件检查API 先获取上传凭证和 fileId,直传存储后检查上传结果。A官方合同未调用;链接有效期、上传限制和服务端鉴权未验证。
团队共享 Brand Kit团队套餐支持共享素材库、Board 和 Brand Kit,供成员复用。W官网 Team 权益;Brand Kit 字段、自动应用范围及权限继承未验证。
模板与灵感 3 项
能力说明证据限制
作品示例和场景模板官网展示营销、短剧、改编、社交和图片设计案例;移动端有一键视频/图片模板。W公开案例与商店说明;投稿、搜索、互动与模板收费未实测。
广告灵感与脚本分析官方扩展和移动端提供参考广告收集、灵感查找、脚本分析和改编起点。W仅官方商店说明;当前广告覆盖、来源条款和自动分析质量未验证。
浏览器广告收藏与 Board 分享扩展将 TikTok 或 Facebook 广告保存到 Board,官方描述可通过链接分享。C2026-10-12 静态检查历史 0.0.4 包证实 folder/收藏逻辑;分享来自商店声明。未安装或执行,不输出包内密钥。
营销批量创作 5 项
能力说明证据限制
商品链接转广告商品页提取背景后进入 Canvas Agent;可继续改文案、重排镜头、替换画面与字幕。W官网流程;未访问商品链接或生成,抓取完整性与素材授权未知。
广告变体与参考改编改变受众、卖点或风格形成 A/B 变体,并有批量电商与参考视频改编流程。W官方说明;并发、数量上限、失败扣费和批量导出策略未知。
商品试穿图片将服装商品和人物参考组合,生成穿搭展示。W未实测
市场与广告研究MCP 提供社交平台与电商信息检索,整理受众痛点、竞品差距和内容方向。W官网声称覆盖 TikTok、YouTube、Amazon、Shopee;数据授权、时效和覆盖率未验证。
活动策略和内容 Brief研究后形成受众洞察、Message Map、Campaign Plan 和 Content Brief。W官网工作流;策略准确性与输出 Schema 未调用验证。
分享与团队 3 项
能力说明证据限制
Canvas 与 Storyline 实时共编Team 页面明确列出 Canvas、Storyline 实时共编和团队内容访问。W官方权益声明;未建立团队,编辑冲突协议、版本控制和邀请权限未知。
团队席位和积分控制团队可管理席位、成员用量和积分控制,并提供发票等管理入口。W官网套餐说明;管理员角色、审计和结算实现未验证。
企业身份与共享工作区企业方案列出共享工作区、积分池、管理分析和 SSO/SAML。W官方销售权益;是否启用、数据条款及身份接入合同须商务确认。
订阅与权益 2 项
能力说明证据限制
订阅积分、加购与并发权益个人、团队和企业套餐按积分、并发及附加权益区分。W不固化动态价格和额度;未购买、升级或验证扣费规则。
Unlimited 与自动化权益边界Unlimited 有模型和排队限制,不能等同于 MCP、CLI、API 或自动化不限量。W官方明确自动化边界;实际账号适用模型和排队条件须确认。
开放集成与客户端 7 项
能力说明证据限制
广告任务、脚本候选与导出 API提交链接或素材生成广告;可取脚本候选、修改脚本并指定 scriptId 导出。A官方合同未调用;修改与导出形成独立步骤,不证明当前 Web 使用相同接口。
Board 批量 ZIP 下载 API按明确任务或筛选集合发起异步 ZIP 下载,返回 downloadTaskId。A官方合同未调用;下载任务 TTL、文件权限与完成通知机制未验证。
远程 MCP 与自然语言工具调用授权后由外部 AI 助手调用研究与创作工具,并管理生成结果。W未连接;身份、任务失败和工具调用保证未验证。
官方 Skill 与本地参考文件外部助手通过官方 Skill 调用生成工具,自动上传本地参考并管理结果。W未安装;支持的客户端、文件上传和状态查询保证未执行验证。
iOS、Android 与移动广告创作官方有双端下载入口;Android 说明商品参考、广告改编、多语言和模板能力。W未安装;iOS 具体功能、Canvas/3D/企业管理与 Web 同步范围未知。
官方 Plugin 与 3D 软件工作流Plugin 连接外部助手,并提供 Blender、Maya、C4D、3ds Max 等创作流程。W官方工作流列表;未安装,不推断通用格式兼容或所有软件都有独立原生插件。
异步视频生成与任务查询 API公开合同描述图生视频、文生视频和参考生成任务,结果可关联 Board 记录。A复用 2026-10-12 已有官方合同研究;本次复查页面访问失败。未调用,不当作现行 Web 实际请求。

尚不清楚的部分

竞品技术栈观察

Topview 仅历史 Chrome 扩展 runtime 可确证 React 版本;当前 Web 栈、Nginx、后端与源站未知。官方 plugins/skill 是外部集成资料,不能当生产网站源码。

项目观察到的内容状态置信度
Nginx 产品及准确版本未确认使用 Nginx;准确版本未知。unknownlow
浏览器运行语言历史 Chrome 扩展使用 JavaScript;当前 Web 的发布运行时本轮未取得。observedhigh
前端原始源码语言当前 Web 与历史扩展原始源码语言均未证实;已发布 JS 不能区分 JS/TS/TSX。unknownlow
前端框架及版本仅扩展 0.0.4:React 18.2.0;react-dom 18.2.0-next-9e3b772b8-20220608。当前 Web 未知。observedhigh
构建器、SSR 与渲染模式当前 Web SSR/Bundler 未知;历史扩展可见 ES module 拆包,构建器及版本未披露。unknownlow
后端语言未知:当前 Web/API/Agent/生成任务的后端语言均未证实。unknownlow
后端框架及版本未知:官方 API/MCP 合同没有证明实际后端框架或其版本。unknownlow
CDN、边缘与源站界限当前 CDN 与源站未知;本机 198.18.x.x 为保留地址,不能定位服务器。unknownlow
前后端仓库与目录关系公开 plugins/skill 仓库是集成资料;生产网站前后端是否同仓、同目录未知。unknownlow
竞品服务调用图

公开 API 主线为签名上传 → 广告预览任务 → 候选脚本修改 → 指定版本导出 → 查询交付。Canvas 和历史扩展另外标注,不把公开合同当作 Web 抓包。

节点层职责证据
API调用方client按官方合同编排文件、广告任务、脚本与导出请求的调用方。A
Canvas客户端client官方 Canvas 产品呈现创作、场景、素材和局部编辑过程。W
历史Chrome扩展client0.0.4 包含广告收藏、目录和 Chrome 消息转发逻辑。C
公开API入口entryapi.topview.ai 的上传、m2v、脚本、导出与 Board 公开合同边界。A
Web入口待核实entry当前 www 域的私有 Web/Agent 接口未取得;旧扩展曾准备调用此域 tRPC 收藏接口。U
文件接口职责domain公开 credential/check 路由提供 fileId、签名 URL 与上传确认。A
广告任务职责domain公开 m2v submit/query 合同组织任务输入和查询身份。A
脚本候选职责domain公开 list/update 路由组织任务内候选与修改内容。A
导出处理职责runtime选择 taskId/scriptId 后产生最终交付,可能有独立于预览的处理阶段。I
Board接口职责domain公开 ZIP 打包接口使用 Board 与任务集合,单独返回 downloadTaskId。A
异步生成职责runtime任务提交与后续查询分开,可能有生成调度和异步执行职责。I
状态保存职责data任务和下载作业能按身份查询,可能有持有状态与结果的介质。I
签名对象上传data合同由调用方 PUT 字节至 uploadUrl;S3 是公开文档的具体示例。A
模型执行边界external生成任务合同和 Canvas 说明表明模型生成能力,可能依赖内部或外部模型执行。I

增长与运营机制

比较页与细分工具页获客,MCP 接入扩展渠道,联盟推荐转付费。(官方正文;源元数据未知)

复刻状态

能力状态依据
工作区条、登录框、新建项目框已验证令牌生效;弹窗背景 #1c1c1c、主按钮 #eee
Agent 对话:新建项目与会话、自动选默认模型、发送收回复已验证真实 Maa,收到回复;注意输入框是 contenteditable,脚本直接写 value 无效,需真实输入
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
首页、Canvas、Video Agent 布局已加载沿用早期静态页
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8101 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
前端入口:agent-integration/apps-production-brand-v14/topview/candidate-8875-unified-v22.html
复刻的架构 七个品牌相同,展开查看
flowchart LR
  U[浏览器] -->|静态页| FE[品牌前端<br/>令牌 + 通用层 + 运行时脚本]
  FE -->|/v1/* Cookie + CSRF| BFF[BFF 业务服务 Go]
  BFF --> MY[(MySQL<br/>共享身份库 + 品牌库)]
  BFF -->|会话 / 消息 / 事件| MAA[Maa Agent]
  BFF -->|任务| WK[媒体 worker]
  WK -->|submit_video| GW[模型网关]
  GW --> MOD[视频模型]
  MAA --> GW
  BFF --> OBJ[(素材 / 导出存储)]
所有七个复刻共用同一套后端:24 张表的品牌库 + 15 张表的共享身份库,55 个公开接口(路径里用 ?profile=topview 区分品牌)。品牌差异在前端:令牌(颜色、圆角、字体)、入口与页面结构。
为什么这样设计七个竞品的后端结构在我们的实测里几乎一致,差别只在入口、模板和视觉。所以复刻把差异收敛到前端令牌,后端只维护一份;这也是最终设计里「共享内核 + 品牌配置」的来源(决策 D17)。

TopView · Agent 交互过程

竞品里观察到的

NOT_RUN_COST_GATE 已确认登录工作台与空测试工程;按用户明确“只做明确免费Agent对话、不能花现有积分”的限制,没有发送任何Agent消息。

费用与准入

项值
selected_modelGPT 5.6 Terra
observed_price当前模型Cost 2.5x;菜单最低可见GPT-6 Luna Cost 0.1x。仅倍率,无单回合上限。
available_credits7.23
exact_per_turn_costunknown
approval用户已明确只做确认零积分费用的Agent对话;本轮不得消耗现有积分,先前10积分预算询问不构成授权。
action_takenno_send
reason真实模型菜单显示非零Cost倍率,未有普通文字消息零积分保证;Ask First控制生成许可,不能证明文字推理免费。
free_only_requirementtrue
zero_credit_cost_confirmedfalse

竞品公开的调用流程

步骤调用关系操作证据
取得上传身份API调用方 → 文件接口GET https://api.topview.ai/v1/upload/credential?format=...&needAccelerateUrl=...;请求头 Topview-Uid、Authorization;返回 fileId/uploadUrl。A
直传文件字节API调用方 → 对象存储PUT <返回的 uploadUrl>,Body 为本地文件字节;公开文档示例为 S3 签名 URL。A
确认文件可用API调用方 → 文件接口GET https://api.topview.ai/v1/upload/check?fileId=...;合同示例检查 result=true。A
提交预览任务API调用方 → 广告接口POST https://api.topview.ai/v1/m2v/task/submit;productLink 或 fileIds、preview=true;返回 taskId。A
轮询预览结果API调用方 → 广告接口GET https://api.topview.ai/v1/m2v/task/query?taskId=...&needCloudFrontUrl=...。A
审阅修改脚本API调用方 → 脚本接口GET https://api.topview.ai/v1/m2v/script/list?taskId=...;选择候选后 POST /v1/m2v/script/update,Body:{taskId,scriptId,scriptContents}。A
选定版本导出API调用方 → 导出接口POST https://api.topview.ai/v1/m2v/export;Body:{taskId,scriptId}。A
查询最终交付API调用方 → 广告接口按 taskId 继续使用 GET https://api.topview.ai/v1/m2v/task/query 查询任务状态与结果;最终资源地址以实际合同响应为准。A

在本品牌复刻上的测试结果

能力状态依据
Agent 对话:新建项目与会话、自动选默认模型、发送收回复已验证真实 Maa,收到回复;注意输入框是 contenteditable,脚本直接写 value 无效,需真实输入
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
首页、Canvas、Video Agent 布局已加载沿用早期静态页
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8101 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
复刻里的 Agent 交互设计(时序图、状态机、规则) 七个品牌相同,展开查看
sequenceDiagram
  autonumber
  participant B as 浏览器
  participant S as BFF
  participant DB as MySQL
  participant M as Maa Agent
  participant W as 媒体 worker
  participant G as 模型网关
  B->>S: POST /projects, /conversations
  B->>S: POST maa/binding
  S->>DB: maa_bindings dispatching
  S->>M: 创建原生会话
  M-->>S: native_cid
  S->>DB: maa_bindings bound
  B->>S: POST maa/messages {client_action_id, content}
  S->>DB: maa_message_actions 幂等回执
  S-->>B: 202 + run_id
  S->>M: 提交消息
  S->>DB: maa_runs queued 到 running
  loop 事件游标
    B->>S: GET runs/{id}/events
    S-->>B: assistant.delta / assistant.message
  end
  M-->>S: run.completed
  S->>DB: 记录助手内容 SHA-256
  B->>S: POST media/requests/from-maa {source_run_id, sha256}
  S-->>B: 报价 + 有效期 + quote_sha256
  B->>S: POST approve {client_approval_id, request_sha256, quote_sha256}
  S->>DB: 冻结积分 + 建任务
  W->>G: submit_video
  G-->>W: 视频
  W->>DB: 产出入库 + 扣费
  B->>S: GET media/jobs/{id}

Run 状态机

stateDiagram-v2
  [*] --> queued: POST maa/messages 202
  queued --> running
  running --> waiting_interaction: 需要澄清卡
  waiting_interaction --> running: respondInteraction
  running --> completed
  running --> failed
  running --> cancelled: stopRun
  waiting_interaction --> cancelled
  completed --> [*]
  failed --> [*]
  cancelled --> [*]

媒体任务状态机

stateDiagram-v2
  [*] --> quoted
  quoted --> expired: 约 5 分钟
  quoted --> approved: approve
  approved --> held: 冻结积分
  held --> queued
  queued --> running: worker 领取
  running --> succeeded: 产出入库
  succeeded --> captured: 扣费确认
  running --> failed
  failed --> 待核对: 供应商已受理后失败,冻结保留,界面写「费用待核对」

交互规则

环节规则落库
绑定会话bindMaa 传 agent_model 或 use_default;先 dispatching,拿到原生会话号后才 bound(约束保证二者一致)maa_bindings
发消息client_action_id 幂等;返回 202 + run_id;同一个动作 ID 内容不同返回 409maa_message_actions, maa_runs
事件按 seq 游标读取;服务端先清洗路径、内部地址和令牌再公开maa_runs.last_event_seq
完成证据完成时记录助手内容的两份 SHA-256 与字节数maa_runs
澄清卡(HITL)Run 进入 waiting_interaction;respondInteraction 带 card_sha256;不能恢复两次maa_interactions, maa_interaction_actions
停止stopRun 幂等,回执只说「已观测到终态」;对话里保留「已停止回复」maa_run_stop_actions
切换模型必须带 expected_revision;有未决切换时拒绝发新消息maa_model_change_actions
生成视频from-maa → 报价 → 批准 → 冻结 → worker → 入库 → 扣费;批准带请求哈希与报价哈希studio_media_*, 积分账本
报价过期批准被拒,不建任务不扣费;界面提示重新报价studio_media_requests.expires_at
失败后的积分供应商已受理后失败:冻结保留,界面写「费用待核对」,不自动释放;本部署的账本里还没出现过释放分录(12 笔冻结、8 笔扣费、其余 4 笔仍冻结)studio_credit_ledger_entries
会话过期每 8 秒检查登录状态;失效时横幅提示,登录后恢复—
为什么这样设计把事件做成带序号的流、把动作做成带幂等键的回执,是因为长回复一定会遇到断线、刷新和重复点击。序号让续读不丢不重;幂等让重复点击安全;诚实的回执(只说「已观测到终态」)避免把「已停止」说成「不收费」。我们还在复刻里修掉了一个停止按钮从不发请求的缺陷,这说明每条交互路径都需要端到端测试(决策 D8)。

TopView · ER 图

品牌库 studio_profile_topview:24 张表、34 个真实外键、66 个检查约束、22 个触发器。图由实际表结构生成,箭头从父表指向子表。品牌库与共享身份库之间的 owner_id、asset_id 靠应用层归属检查,不画成外键。

共 61 条关系:一对多 37 条,一对一 10 条,一对多(父可选) 10 条,一对一(父可选) 4 条;多对多 2 组(经关联表)。

用户与登录 素材 积分账本 支付与订阅 模型目录 项目与会话 画布 · 时间轴 · 导出 Agent 交互 生成 · 媒体 · 交付

基数标注(线上的文字,箭头从父表指向子表):1:N 一对多,子表外键必填;0..1:N 一对多,但外键可空,所以父是可选的;1:1 一对一,子表外键上有唯一键;0..1:1 一对一且父可选。红色粗双向线 = N:M 多对多,经带 ⇄ 的关联表(它的主键就是两个外键)。实线 = 数据库真实外键;灰色虚线 = 跨库关系(两个库之间数据库无法建外键,由应用层检查);橙色粗框与粗线 = Agent 交互相关。每张表都带 owner_id(指向用户),为了图清楚没有逐条画线。

全景图:全部实体与关系

默认缩放到页面宽度,先看整体结构;点一下图切换到原始大小(看清表名,在框内上下左右滚动),再点一下缩回。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":26,"rankSpacing":80}}}%%
flowchart LR
  subgraph sg_i_user["用户与登录"]
    direction TB
    t_studio_users["users"]
    t_studio_sessions["sessions"]
  end
  subgraph sg_i_asset["素材"]
    direction TB
    t_studio_assets["assets"]
  end
  subgraph sg_i_credit["积分账本"]
    direction TB
    t_studio_credit_accounts["credit_accounts"]
    t_studio_credit_lots["credit_lots"]
    t_studio_credit_authorizations["credit_authorizations"]
    t_studio_credit_authorization_allocations["⇄ credit_authorization_allocations"]
    t_studio_credit_ledger_entries["credit_ledger_entries"]
    t_studio_credit_plan_versions["credit_plan_versions"]
    t_studio_billing_cycles["billing_cycles"]
  end
  subgraph sg_i_pay["支付与订阅"]
    direction TB
    t_studio_payment_orders["payment_orders"]
    t_studio_payment_events["payment_events"]
    t_studio_subscriptions["subscriptions"]
  end
  subgraph sg_i_model["模型目录"]
    direction TB
    t_studio_media_models["media_models"]
    t_studio_media_model_versions["media_model_versions"]
  end
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  subgraph sg_media["生成 · 媒体 · 交付"]
    direction TB
    t_studio_media_requests["media_requests"]
    t_studio_media_jobs["media_jobs"]
    t_studio_media_submission_attempts["media_submission_attempts"]
    t_studio_media_outputs["media_outputs"]
    t_generation_jobs["gen_jobs"]
    t_generation_reservations["gen_reservations"]
    t_generation_deliveries["gen_deliveries"]
    t_generation_development_budgets["gen_development_budgets"]
    t_studio_credit_recovery_intents["credit_recovery_intents"]
  end
  t_studio_users -->|"1:N"| t_studio_assets
  t_studio_payment_orders -->|"1:1"| t_studio_billing_cycles
  t_studio_subscriptions -->|"1:N"| t_studio_billing_cycles
  t_studio_users -->|"1:1"| t_studio_credit_accounts
  t_studio_credit_authorizations -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_lots -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_authorizations
  t_studio_credit_authorization_allocations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_authorizations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_lots -->|"1:N"| t_studio_credit_ledger_entries
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_lots
  t_studio_billing_cycles -->|"0..1:1"| t_studio_credit_lots
  t_studio_payment_orders -->|"0..1:1"| t_studio_credit_lots
  t_studio_media_models -->|"1:N"| t_studio_media_model_versions
  t_studio_media_model_versions -->|"0..1:1"| t_studio_media_models
  t_studio_payment_orders -->|"0..1:N"| t_studio_payment_events
  t_studio_subscriptions -->|"0..1:N"| t_studio_payment_events
  t_studio_users -->|"1:N"| t_studio_payment_events
  t_studio_credit_plan_versions -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_sessions
  t_studio_credit_plan_versions -->|"1:N"| t_studio_subscriptions
  t_studio_users -->|"1:N"| t_studio_subscriptions
  t_generation_jobs -->|"1:1"| t_generation_deliveries
  t_studio_projects -->|"1:N"| t_generation_deliveries
  t_studio_conversations -->|"1:N"| t_generation_deliveries
  t_generation_development_budgets -->|"1:N"| t_generation_jobs
  t_studio_projects -->|"1:N"| t_generation_jobs
  t_studio_conversations -->|"1:N"| t_generation_jobs
  t_generation_jobs -->|"1:1"| t_generation_reservations
  t_studio_conversations -->|"1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"0..1:1"| t_maa_bindings
  t_maa_interactions -->|"1:1"| t_maa_interaction_actions
  t_maa_runs -->|"1:N"| t_maa_interactions
  t_maa_bindings -->|"1:N"| t_maa_message_actions
  t_maa_runs -->|"0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"1:N"| t_maa_model_change_actions
  t_maa_runs -->|"1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"1:N"| t_maa_runs
  t_studio_canvases -->|"1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"1:1"| t_studio_canvases
  t_studio_projects -->|"1:N"| t_studio_conversations
  t_studio_media_jobs -->|"1:N"| t_studio_credit_recovery_intents
  t_studio_projects -->|"1:N"| t_studio_export_jobs
  t_studio_media_requests -->|"1:1"| t_studio_media_jobs
  t_studio_media_jobs -->|"1:N"| t_studio_media_outputs
  t_studio_conversations -->|"1:N"| t_studio_media_requests
  t_maa_runs -->|"0..1:N"| t_studio_media_requests
  t_studio_media_jobs -->|"1:N"| t_studio_media_submission_attempts
  t_studio_projects -->|"1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"1:1"| t_studio_video_documents
  t_studio_assets -.->|"1:N"| t_studio_project_asset_refs
  t_studio_assets -.->|"1:N"| t_studio_media_outputs
  t_studio_assets -.->|"1:N"| t_generation_deliveries
  t_studio_media_model_versions -.->|"1:N"| t_studio_media_requests
  t_studio_media_models -.->|"1:N"| t_studio_media_requests
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_media_jobs
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_credit_recovery_intents
  t_studio_credit_authorizations <-.->|"N:M · 经 credit_authorization_allocations"| t_studio_credit_lots
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_users,t_studio_sessions user
class t_studio_assets asset
class t_studio_credit_accounts,t_studio_credit_lots,t_studio_credit_authorizations,t_studio_credit_authorization_allocations,t_studio_credit_ledger_entries,t_studio_credit_plan_versions,t_studio_billing_cycles credit
class t_studio_payment_orders,t_studio_payment_events,t_studio_subscriptions pay
class t_studio_media_models,t_studio_media_model_versions model
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
class t_studio_media_requests,t_studio_media_jobs,t_studio_media_submission_attempts,t_studio_media_outputs,t_generation_jobs,t_generation_reservations,t_generation_deliveries,t_generation_development_budgets,t_studio_credit_recovery_intents media
class t_studio_credit_authorization_allocations,t_studio_project_asset_refs junction
  style sg_i_user fill:none,stroke:#6b7280,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_asset fill:none,stroke:#06b6d4,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_credit fill:none,stroke:#eab308,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_pay fill:none,stroke:#ec4899,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_model fill:none,stroke:#6366f1,stroke-width:1px,stroke-dasharray:5 4
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  style sg_media fill:none,stroke:#22c55e,stroke-width:1px
  linkStyle 30,31,32,33,34,35,36,37,38,48 stroke:#f97316,stroke-width:2.5px
  linkStyle 54,55,56,57,58,59,60 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 61,62 stroke:#dc2626,stroke-width:3px

多对多(N:M)

数据库里没有“多对多”这种列,它总是靠一张关联表表达。下面这几张表的主键由两个外键组成,所以它们两端是多对多:

多对多的两端关联表关联表的主键 / 唯一键
studio_credit_authorizations ⇄ studio_credit_lotsstudio_credit_authorization_allocationsowner_id, authorization_id, lot_id
studio_projects ⇄ studio_assetsstudio_project_asset_refsproject_id, asset_id

Agent 交互相关(圈出来)

绑定 → Run → 消息 / 澄清卡 / 切换模型 / 停止,每个动作一张回执表;线上是“外键列 · 基数”。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  t_studio_conversations -->|"conversation_id · 1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"pending_model_change_id · 0..1:1"| t_maa_bindings
  t_maa_interactions -->|"interaction_id · 1:1"| t_maa_interaction_actions
  t_maa_runs -->|"public_run_id · 1:N"| t_maa_interactions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_message_actions
  t_maa_runs -->|"public_run_id · 0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_model_change_actions
  t_maa_runs -->|"public_run_id · 1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_runs
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  linkStyle 0,1,2,3,4,5,6,7,8 stroke:#f97316,stroke-width:2.5px

画布与时间轴

项目下是一份视频文档,画布、画布版本、时间线版本和导出都挂在这份文档上;素材放在共享身份库,项目只记引用,项目与素材是多对多,经 project_asset_refs。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_asset["素材(共享身份库)"]
    direction TB
    t_studio_assets["assets"]
  end
  t_studio_canvases -->|"project_id · 1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"restored_from_revision · 0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_canvases
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
  t_studio_projects -->|"project_id · 1:N"| t_studio_export_jobs
  t_studio_projects -->|"project_id · 1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"project_id · 1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"restored_from_revision · 0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_video_documents
  t_studio_assets -.->|"asset_id · 1:N"| t_studio_project_asset_refs
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_studio_assets asset
class t_studio_project_asset_refs junction
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px
  linkStyle 9 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 10 stroke:#dc2626,stroke-width:3px
关系清单 逐条 61 条,含基数
父表子表外键列(子表里)基数约束域
generation_jobsgeneration_deliveriesjob_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_assetsgeneration_deliveriesasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_conversationsgeneration_deliveriesowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_deliveriesowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_development_budgetsgeneration_jobsowner_id, budget_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_conversationsgeneration_jobsowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_jobsgeneration_reservationsjob_id, budget_id, owner_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_model_change_actionsmaa_bindingsowner_id, project_id, conversation_id, id, pending_model_change_id0..1:1 一对一(父可选)DB 外键Agent 交互 → Agent 交互
studio_conversationsmaa_bindingsowner_id, project_id, conversation_id1:1 一对一DB 外键项目与会话 → Agent 交互
maa_interactionsmaa_interaction_actionsowner_id, project_id, conversation_id, binding_id, public_run_id, interaction_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_runsmaa_interactionsowner_id, project_id, conversation_id, binding_id, public_run_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_message_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_message_actionsowner_id, project_id, conversation_id, binding_id, public_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_model_change_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_run_stop_actionsowner_id, project_id, conversation_id, binding_id, public_run_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_runsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
studio_usersstudio_assetsowner_id1:N 一对多DB 外键用户与登录 → 素材
studio_payment_ordersstudio_billing_cyclesowner_id, payment_order_id, plan_version_id1:1 一对一DB 外键支付与订阅 → 积分账本
studio_subscriptionsstudio_billing_cyclesowner_id, subscription_id, provider_key, provider_account1:N 一对多DB 外键支付与订阅 → 积分账本
studio_canvas_revisionsstudio_canvas_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_canvasesstudio_canvas_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_canvasesowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_projectsstudio_conversationsowner_id, project_id1:N 一对多DB 外键项目与会话 → 项目与会话
studio_usersstudio_credit_accountsowner_id1:1 一对一DB 外键用户与登录 → 积分账本
studio_credit_authorizationsstudio_credit_authorization_allocationsowner_id, authorization_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_authorization_allocationsowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_authorizationsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_authorization_allocationsstudio_credit_ledger_entriesowner_id, authorization_id, lot_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_authorizationsstudio_credit_ledger_entriesowner_id, authorization_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_ledger_entriesowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_billing_cyclesstudio_credit_lotsowner_id, source_cycle_id0..1:1 一对一(父可选)DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_lotsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_payment_ordersstudio_credit_lotsowner_id, source_order_id0..1:1 一对一(父可选)DB 外键支付与订阅 → 积分账本
studio_credit_authorizationsstudio_credit_recovery_intentsauthorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_jobsstudio_credit_recovery_intentsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_projectsstudio_export_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_credit_authorizationsstudio_media_jobscredit_authorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_requestsstudio_media_jobsowner_id, project_id, conversation_id, request_id, request_sha256, quote_sha2561:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_model_versionsmodel_id1:N 一对多DB 外键模型目录 → 模型目录
studio_media_model_versionsstudio_media_modelsid, published_version_id0..1:1 一对一(父可选)DB 外键模型目录 → 模型目录
studio_assetsstudio_media_outputsasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_outputsowner_id, project_id, conversation_id, job_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_runsstudio_media_requestsowner_id, project_id, conversation_id, source_binding_id, source_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → 生成 · 媒体 · 交付
studio_conversationsstudio_media_requestsowner_id, project_id, conversation_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_media_model_versionsstudio_media_requestsmodel_version_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_requestsmodel_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_submission_attemptsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_payment_ordersstudio_payment_eventsowner_id, order_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_subscriptionsstudio_payment_eventsowner_id, subscription_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_usersstudio_payment_eventsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_credit_plan_versionsstudio_payment_ordersplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_payment_ordersowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_assetsstudio_project_asset_refsasset_id1:N 一对多跨库(应用层检查)素材 → 画布 · 时间轴 · 导出
studio_projectsstudio_project_asset_refsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_usersstudio_sessionsuser_id1:N 一对多DB 外键用户与登录 → 用户与登录
studio_credit_plan_versionsstudio_subscriptionsplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_subscriptionsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_timeline_revisionsstudio_timeline_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_video_documentsstudio_timeline_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_video_documentsowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
完整 ER:39 张表的全部列,带主键 / 外键与基数符号(鸦脚)
erDiagram
  studio_assets {
    CHAR id PK
    CHAR owner_id FK
    CHAR sha256
    JSON metadata_json
    VARCHAR storage_key
    DATETIME created_at
    VARCHAR publication_state
  }
  studio_billing_cycles {
    CHAR id PK
    CHAR owner_id FK
    CHAR subscription_id FK
    CHAR plan_version_id FK
    CHAR payment_order_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_invoice_id
    DATETIME period_start
    DATETIME period_end
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_accounts {
    CHAR owner_id PK
    VARCHAR unit
    VARCHAR status
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_credit_authorization_allocations {
    CHAR owner_id FK
    CHAR authorization_id PK
    CHAR lot_id PK
    BIGINT_UNSIGNED allocated_credits
    DATETIME created_at
  }
  studio_credit_authorizations {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR profile
    CHAR project_id
    CHAR conversation_id
    CHAR job_id
    CHAR request_id
    CHAR request_sha256
    CHAR quote_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    MEDIUMBLOB reservation_bytes
    CHAR reservation_sha256
    MEDIUMBLOB policy_bytes
    CHAR policy_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_ledger_entries {
    CHAR id PK
    CHAR owner_id FK
    CHAR lot_id FK
    CHAR authorization_id FK
    VARCHAR operation_key
    VARCHAR kind
    BIGINT grant_delta
    BIGINT held_delta
    BIGINT captured_delta
    MEDIUMBLOB facts_bytes
    CHAR facts_sha256
    DATETIME created_at
  }
  studio_credit_lots {
    CHAR id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED grant_credits
    BIGINT_UNSIGNED held_credits
    BIGINT_UNSIGNED captured_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    CHAR source_order_id FK
    CHAR source_cycle_id FK
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_plan_versions {
    CHAR id PK
    VARCHAR plan_key
    VARCHAR version_key
    VARCHAR kind
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    INT_UNSIGNED credit_validity_seconds
    INT_UNSIGNED billing_interval_months
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    DATETIME published_at
    DATETIME retired_at
  }
  studio_media_model_versions {
    CHAR id PK
    CHAR model_id FK
    VARCHAR version_key
    MEDIUMBLOB config_bytes
    CHAR config_sha256
    DATETIME published_at
  }
  studio_media_models {
    CHAR id PK
    VARCHAR model_key
    VARCHAR kind
    VARCHAR display_name
    VARCHAR status
    CHAR published_version_id FK
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_payment_events {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_event_id
    CHAR order_id FK
    CHAR subscription_id FK
    MEDIUMBLOB body_bytes
    CHAR body_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_payment_orders {
    CHAR id PK
    CHAR owner_id FK
    CHAR client_action_id
    CHAR plan_version_id FK
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_idempotency_key
    VARCHAR provider_order_id
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_sessions {
    CHAR token_hash PK
    CHAR user_id FK
    DATETIME created_at
    DATETIME expires_at
    DATETIME revoked_at
  }
  studio_subscriptions {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_subscription_id
    CHAR plan_version_id FK
    VARCHAR state
    DATETIME period_start
    DATETIME period_end
    TINYINT cancel_at_period_end
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_users {
    CHAR id PK
    VARCHAR email
    VARBINARY password_hash
    TINYINT enabled
    DATETIME created_at
  }
  generation_deliveries {
    CHAR job_id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR asset_id FK
    CHAR request_sha256
    CHAR maa_source_sha256
    CHAR provider_source_sha256
    VARCHAR model
    VARCHAR tool_name
    CHAR input_sha256
    JSON metadata_json
    DATETIME created_at
  }
  generation_development_budgets {
    CHAR id PK
    CHAR owner_id
    VARCHAR policy_version
    BIGINT_UNSIGNED credits_limit
    BIGINT_UNSIGNED credits_reserved
    INT_UNSIGNED calls_limit
    INT_UNSIGNED calls_reserved
    DATETIME expires_at
  }
  generation_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR native_conversation_id
    CHAR native_run_id
    CHAR source_sha256
    CHAR budget_id FK
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    VARCHAR status
    INT_UNSIGNED attempts
    MEDIUMBLOB result_bytes
    DATETIME updated_at
  }
  generation_reservations {
    CHAR job_id PK
    CHAR budget_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED credits
    INT_UNSIGNED calls
    VARCHAR status
  }
  maa_bindings {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR provider
    VARCHAR model_name
    VARCHAR status
    VARCHAR native_cid
    DATETIME created_at
    BIGINT_UNSIGNED model_revision
    CHAR pending_model_change_id FK
  }
  maa_interaction_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR interaction_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR client_action_id
    CHAR card_sha256
    CHAR request_sha256
    CHAR content_sha256
    VARCHAR response_action
    JSON values_payload
    VARCHAR status
    DATETIME created_at
  }
  maa_interactions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR card_sha256
    JSON card_payload
    DATETIME created_at
  }
  maa_message_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_action_id
    CHAR request_sha256
    MEDIUMTEXT content
    VARCHAR status
    CHAR public_run_id FK
    CHAR proposed_run_id
    DATETIME created_at
  }
  maa_model_change_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    CHAR client_action_id
    CHAR request_sha256
    CHAR native_request_sha256
    BIGINT_UNSIGNED expected_revision
    VARCHAR from_provider
    VARCHAR from_model_name
    VARCHAR target_provider
    VARCHAR target_model_name
    VARCHAR status
    BIGINT_UNSIGNED dispatch_attempt
    JSON receipt_payload
    DATETIME created_at
    DATETIME updated_at
  }
  maa_run_stop_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    VARCHAR native_rid
    CHAR client_action_id
    CHAR request_sha256
    VARCHAR stop_action
    VARCHAR status
    BIGINT_UNSIGNED attempt
    VARCHAR terminal_run_status
    DATETIME created_at
    DATETIME updated_at
  }
  maa_runs {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_rid
    VARCHAR status
    DATETIME created_at
    CHAR native_assistant_sha256
    CHAR public_assistant_sha256
    BIGINT_UNSIGNED native_assistant_bytes
    BIGINT_UNSIGNED assistant_event_seq
    BIGINT_UNSIGNED last_event_seq
  }
  studio_canvas_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_canvases {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_conversations {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    VARCHAR title
    DATETIME created_at
  }
  studio_credit_recovery_intents {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256
    VARCHAR kind
    CHAR authorization_id FK
    MEDIUMBLOB intent_bytes
    CHAR intent_sha256
    VARCHAR state
    CHAR lease_token_sha256
    DATETIME lease_until
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_export_jobs {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED document_revision
    CHAR input_hash
    VARCHAR status
    JSON input_json
    JSON output_json
    VARCHAR error_code
    CHAR lease_token_hash
    DATETIME lease_until
    INT_UNSIGNED attempts
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR request_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256 FK
    CHAR client_approval_id
    CHAR approval_sha256
    CHAR credit_authorization_id FK
    VARCHAR authorization_mode
    DATETIME authorization_expires_at
    VARCHAR status
    BIGINT_UNSIGNED revision
    CHAR lease_token_sha256
    DATETIME lease_until
    VARCHAR provider_task_id
    CHAR client_cancel_id
    CHAR cancel_request_sha256
    DATETIME cancel_requested_at
    CHAR staged_asset_id
    CHAR staged_asset_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_outputs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED ordinal
    CHAR asset_id FK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    CHAR provider_result_sha256
    DATETIME created_at
  }
  studio_media_requests {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_request_id
    CHAR model_id FK
    CHAR model_version_id FK
    CHAR model_config_sha256
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    MEDIUMBLOB quote_bytes
    CHAR quote_sha256
    BIGINT_UNSIGNED quoted_credits
    DATETIME expires_at
    VARCHAR origin
    CHAR source_binding_id FK
    CHAR source_run_id FK
    CHAR source_assistant_sha256
    MEDIUMBLOB source_snapshot_bytes
    DATETIME created_at
  }
  studio_media_submission_attempts {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED attempt_no
    CHAR request_sha256 FK
    VARCHAR provider_idempotency_key
    VARCHAR stage
    DATETIME dispatch_started_at
    VARCHAR provider_task_id
    CHAR receipt_sha256
    CHAR terminal_receipt_sha256
    CHAR reconciliation_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_project_asset_refs {
    CHAR project_id PK
    CHAR owner_id FK
    CHAR asset_id PK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
    DATETIME detached_at
  }
  studio_projects {
    CHAR id PK
    CHAR owner_id
    VARCHAR title
    DATETIME created_at
  }
  studio_timeline_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_video_documents {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_users ||--o{ studio_assets : "owner_id"
  studio_payment_orders ||--o| studio_billing_cycles : "plan_version_id"
  studio_subscriptions ||--o{ studio_billing_cycles : "provider_account"
  studio_users ||--o| studio_credit_accounts : "owner_id"
  studio_credit_authorizations ||--o{ studio_credit_authorization_allocations : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_authorization_allocations : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_authorizations : "owner_id"
  studio_credit_authorization_allocations |o--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_authorizations |o--o{ studio_credit_ledger_entries : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_lots : "owner_id"
  studio_billing_cycles |o--o| studio_credit_lots : "source_cycle_id"
  studio_payment_orders |o--o| studio_credit_lots : "source_order_id"
  studio_media_models ||--o{ studio_media_model_versions : "model_id"
  studio_media_model_versions |o--o| studio_media_models : "published_version_id"
  studio_payment_orders |o--o{ studio_payment_events : "provider_account"
  studio_subscriptions |o--o{ studio_payment_events : "provider_account"
  studio_users ||--o{ studio_payment_events : "owner_id"
  studio_credit_plan_versions ||--o{ studio_payment_orders : "plan_version_id"
  studio_users ||--o{ studio_payment_orders : "owner_id"
  studio_users ||--o{ studio_sessions : "user_id"
  studio_credit_plan_versions ||--o{ studio_subscriptions : "plan_version_id"
  studio_users ||--o{ studio_subscriptions : "owner_id"
  generation_jobs ||--o| generation_deliveries : "job_id"
  studio_projects ||--o{ generation_deliveries : "project_id"
  studio_conversations ||--o{ generation_deliveries : "session_id"
  generation_development_budgets ||--o{ generation_jobs : "budget_id"
  studio_projects ||--o{ generation_jobs : "project_id"
  studio_conversations ||--o{ generation_jobs : "session_id"
  generation_jobs ||--o| generation_reservations : "budget_id"
  studio_conversations ||--o| maa_bindings : "conversation_id"
  maa_model_change_actions |o--o| maa_bindings : "pending_model_change_id"
  maa_interactions ||--o| maa_interaction_actions : "interaction_id"
  maa_runs ||--o{ maa_interactions : "public_run_id"
  maa_bindings ||--o{ maa_message_actions : "binding_id"
  maa_runs |o--o{ maa_message_actions : "public_run_id"
  maa_bindings ||--o{ maa_model_change_actions : "binding_id"
  maa_runs ||--o| maa_run_stop_actions : "public_run_id"
  maa_bindings ||--o{ maa_runs : "binding_id"
  studio_canvases ||--o{ studio_canvas_revisions : "project_id"
  studio_canvas_revisions |o--o{ studio_canvas_revisions : "restored_from_revision"
  studio_projects ||--o| studio_canvases : "project_id"
  studio_projects ||--o{ studio_conversations : "project_id"
  studio_media_jobs ||--o{ studio_credit_recovery_intents : "job_id"
  studio_projects ||--o{ studio_export_jobs : "project_id"
  studio_media_requests ||--o| studio_media_jobs : "request_id"
  studio_media_jobs ||--o{ studio_media_outputs : "job_id"
  studio_conversations ||--o{ studio_media_requests : "conversation_id"
  maa_runs |o--o{ studio_media_requests : "source_run_id"
  studio_media_jobs ||--o{ studio_media_submission_attempts : "job_id"
  studio_projects ||--o{ studio_project_asset_refs : "project_id"
  studio_video_documents ||--o{ studio_timeline_revisions : "project_id"
  studio_timeline_revisions |o--o{ studio_timeline_revisions : "restored_from_revision"
  studio_projects ||--o| studio_video_documents : "project_id"
  studio_assets ||--o{ studio_project_asset_refs : "asset_id"
  studio_assets ||--o{ studio_media_outputs : "asset_id"
  studio_assets ||--o{ generation_deliveries : "asset_id"
  studio_media_model_versions ||--o{ studio_media_requests : "model_version_id"
  studio_media_models ||--o{ studio_media_requests : "model_id"
  studio_credit_authorizations |o--o{ studio_media_jobs : "credit_authorization_id"
  studio_credit_authorizations |o--o{ studio_credit_recovery_intents : "authorization_id"
自动生成的整体图(按真实表结构,备查)
TopView 品牌库 ER 图

共享身份库 ER 图

共享身份库 ER 图
实测验证与设计原因 七个品牌相同,展开查看

实测验证(本机 MySQL 实库)

对照本机真实运行的数据库 studio_profile_topview 和 studio_platform_identity,逐项核对本页的表、列、关系与说明。18 / 18 项通过。

查看全部核对项
核对项结果
表数 = 发布值 24通过
列数 = 289通过
外键数 = 34通过
检查约束数 = 66通过
触发器数 = 22通过
索引数(含主键) = 85通过
列定义与 pollo 逐列一致通过
外键与 pollo 完全一致通过
索引与 pollo 一致通过
触发器与 pollo 一致通过
外键 CSV 行数 = 实库外键数通过
mmd 实体 = 实库表通过
svg 标题 24 tables / 34 actual foreign keys = 实库通过
参考 SQL 表集合与列顺序 = 实库通过
15 张表通过
外键 CSV 行数 = 实库外键数通过
svg 标题与实库一致通过
参考 SQL 表集合与列顺序 = 实库通过

实库里的关系是这样的

  • 34 个外键里 31 个是复合外键,且都带着 owner_id,其余 3 个是单列外键;22 张子表都带 owner 外键。所以一行数据不可能指向别的用户的项目或会话。
  • 删除与更新规则全部是 RESTRICT / NO ACTION,没有任何级联:被引用的行不能被删除。
  • 品牌库与共享身份库之间的关系(owner_id、asset_id、model_id + model_version_id)没有数据库外键,靠应用层检查;我们逐表核对了现有数据,没有孤儿。

主动破坏测试(在事务里尝试,全部回滚)

尝试数据库的反应
外键:导出任务.project_id 改成不存在的项目被拒绝拒绝
复合外键:项目素材引用.project_id 改成别的用户的项目被拒绝(owner 与 project 必须成对)拒绝
触发器:已提交的消息动作不可改写拒绝
触发器:媒体产出不可改写拒绝
外键 RESTRICT:被会话引用的项目不能删除拒绝
复合外键:被引用的会话不能把 owner 改成别人拒绝
检查约束:Run 状态只能是约定的六个值拒绝
触发器:账本分录不可修改拒绝
触发器:账本分录不可删除拒绝
验证范围的说明:这几个库里的数据量很小(每个品牌库约 70–270 行),孤儿检查能证明现有数据没有违反关系,不能证明大数据量下的表现;主动破坏测试在 Pollo 库上执行,另外六个品牌库的列、外键、索引、触发器已逐项核对与 Pollo 完全一致。
为什么这样设计外键用真实数据库约束表达,复合外键把 owner/project/conversation 一起带上,所以任何一行数据都不可能指向别人的项目。跨库关系不画外键而用应用层检查,是因为不同库之间数据库本身保证不了,画出来反而误导(决策 D12、D17)。

TopView · 表结构

MySQL 8,字符集 utf8mb4,ID 为 char(36) ascii_bin。点击展开 DDL。

按职责分组

项目与会话 2 张
  • studio_projects:用户拥有的创作项目
  • studio_conversations:项目内的对话会话
Agent执行与并发控制 7 张
  • maa_bindings:业务会话与Native Maa会话映射、模型版本与未决操作
  • maa_runs:公开Run映射、状态与原生/公开助手证据摘要
  • maa_message_actions:原消息动作及幂等回执
  • maa_interactions:澄清卡片及Run归属
  • maa_interaction_actions:原卡片回答/取消动作
  • maa_model_change_actions:带预期模型版本的切换动作
  • maa_run_stop_actions:停止原Run的幂等动作与观测终态
画布、时间轴与素材引用 5 张
  • studio_canvases:项目当前画布文档
  • studio_canvas_revisions:历史画布修订
  • studio_video_documents:项目当前时间轴文档
  • studio_timeline_revisions:历史时间轴修订
  • studio_project_asset_refs:项目对用户级素材的引用;不复制素材所有权
媒体生成、结算恢复与导出 6 张
  • studio_media_requests:用户输入/Agent来源、报价、批准摘要与准入确认
  • studio_media_jobs:已批准媒体任务及状态机
  • studio_media_submission_attempts:一次提交的任务证据与不确定结果
  • studio_media_outputs:受验证的输出与素材映射
  • studio_credit_recovery_intents:积分结算恢复意图;不是用户钱包全账本
  • studio_export_jobs:时间轴导出队列和发布输出
既有generation兼容路径 22 张
  • generation_jobs:既有generation任务,不等同新PublicMedia任务
  • generation_reservations:既有任务费用预留
  • generation_development_budgets:既有开发测试预算
  • generation_deliveries:既有生成交付回执
  • owner_id:归属用户;不是允许跨用户查询的参数。
  • model_revision:并发准入版本;数据库默认0不能代替Native权威读回。
  • pending_model_change_id:未决模型切换引用,阻止其他writer绕过。
  • studio_users:用户账号记录;不导出密码哈希或任何业务行
  • studio_sessions:登录会话;这里只公开字段结构,不公开会话值
  • studio_assets:用户级素材元数据与存储键结构,项目仅引用素材ID
  • studio_media_models:公开媒体模型目录结构
  • studio_media_model_versions:媒体模型不可变版本/计价能力结构
  • studio_subscriptions:订阅状态持久化
  • studio_billing_cycles:订阅账期
  • studio_payment_orders:订单记录
  • studio_payment_events:支付事件幂等记录;不代表已有公开支付路由
  • studio_credit_accounts:用户积分账户
  • studio_credit_lots:积分批次
  • studio_credit_authorizations:费用预留与结算授权
  • studio_credit_authorization_allocations:授权对批次的分配
  • studio_credit_ledger_entries:积分流水
  • studio_credit_plan_versions:积分方案不可变版本

品牌库 studio_profile_topview(24 张)

generation_deliveries 既有生成交付回执
CREATE TABLE `generation_deliveries` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `maa_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `tool_name` varchar(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_delivery_owner_project` (`owner_id`,`project_id`,`session_id`,`job_id`),
  KEY `generation_delivery_owned_asset` (`owner_id`,`asset_id`),
  KEY `generation_delivery_session_fk` (`session_id`),
  CONSTRAINT `generation_delivery_job_fk` FOREIGN KEY (`job_id`) REFERENCES `generation_jobs` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`, `job_id`) REFERENCES `generation_jobs` (`owner_id`, `project_id`, `session_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_session_fk` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_model_tool` CHECK ((((`model` = _ascii'seedance-2.0') and (`tool_name` = _ascii'seedance2_video_generator')) or ((`model` in (_ascii'minimax-h3',_ascii'seedance-2.5')) and (`tool_name` = _ascii'insai_video_generate'))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_development_budgets 既有开发测试预算
CREATE TABLE `generation_development_budgets` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_version` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits_limit` bigint unsigned NOT NULL,
  `credits_reserved` bigint unsigned NOT NULL DEFAULT '0',
  `calls_limit` int unsigned NOT NULL,
  `calls_reserved` int unsigned NOT NULL DEFAULT '0',
  `expires_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_budget_owner` (`owner_id`,`id`),
  CONSTRAINT `generation_budget_limits` CHECK (((`credits_limit` between 1 and 1000000000) and (`credits_reserved` <= `credits_limit`) and (`calls_limit` between 1 and 100) and (`calls_reserved` <= `calls_limit`))),
  CONSTRAINT `generation_budget_policy` CHECK ((`policy_version` = _latin1'operator-test/v1'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_jobs 既有generation任务,不等同新PublicMedia任务
CREATE TABLE `generation_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_conversation_id` char(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_run_id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `result_bytes` mediumblob,
  `updated_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_job_budget_owner` (`id`,`budget_id`,`owner_id`),
  UNIQUE KEY `generation_job_owned_scope` (`owner_id`,`project_id`,`session_id`,`id`),
  KEY `generation_job_owner` (`owner_id`,`project_id`,`session_id`),
  KEY `generation_job_session` (`session_id`),
  KEY `generation_job_budget` (`owner_id`,`budget_id`),
  CONSTRAINT `generation_job_budget` FOREIGN KEY (`owner_id`, `budget_id`) REFERENCES `generation_development_budgets` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_project` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_session` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_attempt` CHECK ((((`status` = _ascii'APPROVED') and (`attempts` = 0) and (`result_bytes` is null)) or ((`status` = _ascii'SUBMITTING') and (`attempts` = 1) and (`result_bytes` is null)) or ((`status` in (_ascii'SUCCEEDED',_ascii'FAILED',_ascii'UNKNOWN')) and (`attempts` = 1) and (`result_bytes` is not null)))),
  CONSTRAINT `generation_job_credit` CHECK ((`credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_reservations 既有任务费用预留
CREATE TABLE `generation_reservations` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `calls` int unsigned NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_reservation_job` (`job_id`,`budget_id`,`owner_id`),
  CONSTRAINT `generation_reservation_job` FOREIGN KEY (`job_id`, `budget_id`, `owner_id`) REFERENCES `generation_jobs` (`id`, `budget_id`, `owner_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_reservation_fixed` CHECK (((`credits` between 1 and 1000000000) and (`calls` = 1) and (`status` = _latin1'HELD')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_bindings 业务会话与Native Maa会话映射、模型版本与未决操作
CREATE TABLE `maa_bindings` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `model_revision` bigint unsigned NOT NULL DEFAULT '0',
  `pending_model_change_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_binding_business_scope` (`owner_id`,`project_id`,`conversation_id`),
  UNIQUE KEY `maa_binding_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `maa_binding_native_identity` (`native_cid`),
  KEY `maa_model_change_pending_fk` (`owner_id`,`project_id`,`conversation_id`,`id`,`pending_model_change_id`),
  CONSTRAINT `maa_binding_owned_conversation_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_pending_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `id`, `pending_model_change_id`) REFERENCES `maa_model_change_actions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_binding_receipt` CHECK ((((`status` = _ascii'dispatching') and (`native_cid` is null)) or ((`status` = _ascii'bound') and (`native_cid` is not null)))),
  CONSTRAINT `maa_model_binding_revision` CHECK ((`model_revision` <= 9007199254740991))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interaction_actions 原卡片回答/取消动作
CREATE TABLE `maa_interaction_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `interaction_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `response_action` varchar(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `values_payload` json NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_action_business_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_interaction_action_one_card` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`interaction_id`),
  CONSTRAINT `maa_interaction_action_owned_card_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `interaction_id`) REFERENCES `maa_interactions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_interaction_action_kind` CHECK ((`response_action` in (_latin1'submit',_latin1'cancel'))),
  CONSTRAINT `maa_interaction_action_status` CHECK ((`status` in (_latin1'dispatching',_latin1'accepted')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interactions 澄清卡片及Run归属
CREATE TABLE `maa_interactions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_payload` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_native_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`native_iid`),
  UNIQUE KEY `maa_interaction_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`id`),
  CONSTRAINT `maa_interaction_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_message_actions 原消息动作及幂等回执
CREATE TABLE `maa_message_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `proposed_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_action_business_identity` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  KEY `maa_action_owned_run_fk` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_action_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_receipt` CHECK ((((`status` = _latin1'dispatching') and (`public_run_id` is null)) or ((`status` = _latin1'accepted') and (`public_run_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_model_change_actions 带预期模型版本的切换动作
CREATE TABLE `maa_model_change_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `expected_revision` bigint unsigned NOT NULL,
  `from_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `from_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_attempt` bigint unsigned NOT NULL,
  `receipt_payload` json DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_model_change_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_model_change_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  CONSTRAINT `maa_model_change_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_attempt` CHECK ((`dispatch_attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_model_change_expected` CHECK ((`expected_revision` <= 9007199254740990)),
  CONSTRAINT `maa_model_change_status` CHECK ((((cast(`status` as char charset binary) = _latin1'dispatching') and (`receipt_payload` is null)) or ((cast(`status` as char charset binary) in (_latin1'accepted',_latin1'rejected')) and (`receipt_payload` is not null) and (json_type(`receipt_payload`) = _latin1'OBJECT')))),
  CONSTRAINT `maa_model_change_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_run_stop_actions 停止原Run的幂等动作与观测终态
CREATE TABLE `maa_run_stop_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stop_action` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt` bigint unsigned NOT NULL,
  `terminal_run_status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_stop_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_stop_one_run` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_stop_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_stop_action` CHECK ((`stop_action` = _latin1'interrupt')),
  CONSTRAINT `maa_stop_attempt` CHECK ((`attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_stop_state` CHECK ((((`status` = _latin1'unknown') and (`terminal_run_status` is null)) or ((`status` = _latin1'terminal_observed') and (`terminal_run_status` is not null) and (`terminal_run_status` in (_latin1'completed',_latin1'failed',_latin1'cancelled'))))),
  CONSTRAINT `maa_stop_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_runs 公开Run映射、状态与原生/公开助手证据摘要
CREATE TABLE `maa_runs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `native_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `public_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `native_assistant_bytes` bigint unsigned NOT NULL DEFAULT '0',
  `assistant_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  `last_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_run_native_identity` (`native_rid`),
  UNIQUE KEY `maa_run_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  KEY `maa_run_owned_list` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  CONSTRAINT `maa_run_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_run_source_evidence` CHECK ((((`native_assistant_sha256` is null) and (`public_assistant_sha256` is null) and (`native_assistant_bytes` = 0) and (`assistant_event_seq` = 0)) or ((`native_assistant_sha256` is not null) and (`public_assistant_sha256` is not null) and (`native_assistant_bytes` > 0) and (`assistant_event_seq` > 0) and (`assistant_event_seq` <= `last_event_seq`)))),
  CONSTRAINT `maa_run_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'waiting_interaction',_latin1'completed',_latin1'failed',_latin1'cancelled')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvas_revisions 历史画布修订
CREATE TABLE `studio_canvas_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_canvas_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_canvas_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_canvases` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_canvas_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_canvas_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_canvas_revision_kind` CHECK ((`kind` = _latin1'canvas')),
  CONSTRAINT `studio_canvas_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_canvas_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_canvas_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvases 项目当前画布文档
CREATE TABLE `studio_canvases` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_canvas_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_canvas_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_conversations 项目内的对话会话
CREATE TABLE `studio_conversations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_conversation_owned_scope` (`owner_id`,`project_id`,`id`),
  KEY `studio_conversation_list` (`owner_id`,`project_id`,`created_at`,`id`),
  CONSTRAINT `studio_conversation_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_recovery_intents 积分结算恢复意图;不是用户钱包全账本
CREATE TABLE `studio_credit_recovery_intents` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `intent_bytes` mediumblob NOT NULL,
  `intent_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_intent_job_kind` (`job_id`,`kind`),
  KEY `credit_intent_recovery` (`state`,`updated_at`,`id`),
  KEY `credit_intent_owned_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `credit_intent_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `credit_intent_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_intent_kind` CHECK (((`kind` in (_latin1'RESERVE_BIND',_latin1'CAPTURE',_latin1'RELEASE')) and (((`kind` = _latin1'RESERVE_BIND') and (`authorization_id` is null)) or ((`kind` in (_latin1'CAPTURE',_latin1'RELEASE')) and (`authorization_id` is not null))))),
  CONSTRAINT `credit_intent_lease` CHECK ((((`state` = _latin1'leased') and (`lease_token_sha256` is not null) and regexp_like(`lease_token_sha256`,_latin1'^[a-f0-9]{64}$') and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`state` <> _latin1'leased') and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `credit_intent_payload_sha` CHECK ((`intent_sha256` = lower(sha2(`intent_bytes`,256)))),
  CONSTRAINT `credit_intent_payload_size` CHECK ((length(`intent_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_intent_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_intent_state` CHECK ((`state` in (_latin1'pending',_latin1'leased',_latin1'confirmed',_latin1'unknown'))),
  CONSTRAINT `credit_intent_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_export_jobs 时间轴导出队列和发布输出
CREATE TABLE `studio_export_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `document_revision` bigint unsigned NOT NULL,
  `input_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_json` json NOT NULL,
  `output_json` json DEFAULT NULL,
  `error_code` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
  `lease_token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_export_input` (`project_id`,`document_revision`,`input_hash`),
  KEY `studio_export_claim` (`status`,`created_at`,`id`),
  KEY `studio_export_owner_list` (`owner_id`,`project_id`,`created_at`,`id`),
  KEY `studio_export_expiry` (`status`,`lease_until`),
  CONSTRAINT `studio_export_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_export_lease` CHECK ((((`status` = _latin1'running') and (`lease_token_hash` is not null) and (`lease_until` is not null)) or ((`status` <> _latin1'running') and (`lease_token_hash` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_export_output` CHECK ((((`status` = _latin1'succeeded') and (`output_json` is not null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'queued',_latin1'running')) and (`output_json` is null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'failed',_latin1'unknown')) and (`output_json` is null) and (`error_code` <> _latin1'')))),
  CONSTRAINT `studio_export_revision` CHECK ((`document_revision` between 1 and 2147483647)),
  CONSTRAINT `studio_export_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'succeeded',_latin1'failed',_latin1'unknown')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_jobs 已批准媒体任务及状态机
CREATE TABLE `studio_media_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_approval_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `approval_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credit_authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_mode` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_expires_at` datetime(6) DEFAULT NULL,
  `status` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `client_cancel_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_requested_at` datetime(6) DEFAULT NULL,
  `staged_asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `staged_asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_job_request` (`request_id`),
  UNIQUE KEY `studio_media_job_client` (`owner_id`,`client_approval_id`),
  UNIQUE KEY `studio_media_job_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_job_owned_request` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`),
  KEY `studio_media_job_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_job_queue` (`status`,`updated_at`,`id`),
  KEY `studio_media_job_request_fk` (`owner_id`,`project_id`,`conversation_id`,`request_id`,`request_sha256`,`quote_sha256`),
  CONSTRAINT `studio_media_job_request_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `request_id`, `request_sha256`, `quote_sha256`) REFERENCES `studio_media_requests` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`, `quote_sha256`),
  CONSTRAINT `studio_media_job_authorization` CHECK ((((`credit_authorization_id` is null) and (`authorization_mode` is null) and (`authorization_expires_at` is null) and (`status` in (_latin1'approval_pending',_latin1'cancelled'))) or ((`credit_authorization_id` is not null) and (`authorization_mode` is not null) and (`authorization_mode` in (_latin1'commercial',_latin1'test_allowance')) and (`authorization_expires_at` is not null) and (`authorization_expires_at` > `created_at`) and (`status` <> _latin1'approval_pending')))),
  CONSTRAINT `studio_media_job_cancel` CHECK ((((`client_cancel_id` is null) and (`cancel_request_sha256` is null) and (`cancel_requested_at` is null)) or ((`client_cancel_id` is not null) and (`cancel_request_sha256` is not null) and (`cancel_requested_at` is not null) and (`cancel_requested_at` between `created_at` and `updated_at`)))),
  CONSTRAINT `studio_media_job_lease` CHECK ((((`status` in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is not null) and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`status` not in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_media_job_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_job_staged` CHECK ((((`staged_asset_id` is null) and (`staged_asset_sha256` is null)) or ((`staged_asset_id` is not null) and (`staged_asset_sha256` is not null)))),
  CONSTRAINT `studio_media_job_status` CHECK ((`status` in (_latin1'approval_pending',_latin1'queued',_latin1'leased',_latin1'submitting',_latin1'running',_latin1'submission_unknown',_latin1'succeeded',_latin1'failed',_latin1'cancelled'))),
  CONSTRAINT `studio_media_job_task` CHECK (((`status` not in (_latin1'running',_latin1'succeeded')) or (`provider_task_id` is not null))),
  CONSTRAINT `studio_media_job_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_outputs 受验证的输出与素材映射
CREATE TABLE `studio_media_outputs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `ordinal` int unsigned NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_result_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_output_job` (`job_id`,`ordinal`),
  KEY `studio_media_output_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`),
  CONSTRAINT `studio_media_output_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`),
  CONSTRAINT `studio_media_output_ordinal` CHECK ((`ordinal` = 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_requests 用户输入/Agent来源、报价、批准摘要与准入确认
CREATE TABLE `studio_media_requests` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_bytes` mediumblob NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quoted_credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_snapshot_bytes` mediumblob,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_request_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_request_quoted` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`,`quote_sha256`),
  UNIQUE KEY `studio_media_request_client` (`owner_id`,`project_id`,`conversation_id`,`client_request_id`),
  KEY `studio_media_request_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_request_source_fk` (`owner_id`,`project_id`,`conversation_id`,`source_binding_id`,`source_run_id`),
  CONSTRAINT `studio_media_request_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`),
  CONSTRAINT `studio_media_request_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `source_binding_id`, `source_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`),
  CONSTRAINT `studio_media_quote_credits` CHECK ((`quoted_credits` between 1 and 1000000000)),
  CONSTRAINT `studio_media_quote_sha` CHECK ((`quote_sha256` = lower(sha2(`quote_bytes`,256)))),
  CONSTRAINT `studio_media_quote_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)))),
  CONSTRAINT `studio_media_request_origin` CHECK ((((`origin` in (_latin1'ui',_latin1'user_edit')) and (`source_binding_id` is null) and (`source_run_id` is null) and (`source_assistant_sha256` is null) and (`source_snapshot_bytes` is null)) or ((`origin` = _latin1'agent') and (`source_binding_id` is not null) and (`source_run_id` is not null) and (`source_assistant_sha256` is not null) and (`source_snapshot_bytes` is not null) and (length(`source_snapshot_bytes`) between 1 and 65536) and (`source_assistant_sha256` = lower(sha2(`source_snapshot_bytes`,256)))))),
  CONSTRAINT `studio_media_request_sha` CHECK ((`request_sha256` = lower(sha2(`request_bytes`,256)))),
  CONSTRAINT `studio_media_request_size` CHECK (((length(`request_bytes`) between 1 and 65536) and (length(`quote_bytes`) between 1 and 65536)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_submission_attempts 一次提交的任务证据与不确定结果
CREATE TABLE `studio_media_submission_attempts` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt_no` int unsigned NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stage` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_started_at` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `terminal_receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `reconciliation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_attempt_job` (`job_id`,`attempt_no`),
  UNIQUE KEY `studio_media_attempt_idempotency` (`provider_idempotency_key`),
  KEY `studio_media_attempt_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `studio_media_attempt_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `studio_media_attempt_number` CHECK ((`attempt_no` = 1)),
  CONSTRAINT `studio_media_attempt_state` CHECK ((((`stage` = _latin1'prepared') and (`dispatch_started_at` is null) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'dispatching',_latin1'unknown')) and (`dispatch_started_at` is not null) and (`dispatch_started_at` >= `created_at`) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` = _latin1'acknowledged') and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'completed',_latin1'failed')) and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is not null)) or ((`stage` = _latin1'reconciled_not_sent') and (`dispatch_started_at` is not null) and (`provider_task_id` is null) and (`reconciliation_sha256` is not null)))),
  CONSTRAINT `studio_media_attempt_times` CHECK (((`updated_at` >= `created_at`) and ((`dispatch_started_at` is null) or (`dispatch_started_at` between `created_at` and `updated_at`))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_project_asset_refs 项目对用户级素材的引用;不复制素材所有权
CREATE TABLE `studio_project_asset_refs` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `detached_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`project_id`,`asset_id`),
  KEY `studio_project_asset_owned_page` (`owner_id`,`project_id`,`asset_id`),
  KEY `studio_project_asset_reverse` (`owner_id`,`asset_id`,`project_id`),
  CONSTRAINT `studio_project_asset_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_project_asset_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_project_asset_state` CHECK ((((`state` = _latin1'attached') and (`detached_at` is null)) or ((`state` = _latin1'detached') and (`detached_at` is not null) and (`detached_at` = `updated_at`)))),
  CONSTRAINT `studio_project_asset_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_projects 用户拥有的创作项目
CREATE TABLE `studio_projects` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_project_owner_identity` (`owner_id`,`id`),
  KEY `studio_project_owner_list` (`owner_id`,`created_at`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_timeline_revisions 历史时间轴修订
CREATE TABLE `studio_timeline_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_timeline_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_timeline_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_video_documents` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_timeline_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_timeline_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_timeline_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_timeline_revision_kind` CHECK ((`kind` = _latin1'timeline')),
  CONSTRAINT `studio_timeline_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_timeline_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_timeline_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_video_documents 项目当前时间轴文档
CREATE TABLE `studio_video_documents` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_timeline_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_document_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_document_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
共享身份库(15 张)与积分账本 七个品牌相同,展开查看
studio_assets
CREATE TABLE `studio_assets` (
  `id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `storage_key` varchar(512) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `publication_state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT 'visible',
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_asset_owner_digest` (`owner_id`,`sha256`),
  KEY `studio_asset_owner_list` (`owner_id`,`created_at`,`id`),
  KEY `studio_asset_visible_list` (`owner_id`,`publication_state`,`created_at`,`id`),
  CONSTRAINT `studio_asset_publication_state` CHECK (CAST(publication_state AS BINARY) IN ('pending','visible')),
  CONSTRAINT `studio_asset_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_billing_cycles
CREATE TABLE `studio_billing_cycles` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `payment_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_invoice_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `billing_cycle_owned` (`owner_id`,`id`),
  UNIQUE KEY `billing_cycle_period` (`subscription_id`,`period_start`),
  UNIQUE KEY `billing_cycle_invoice` (`provider_key`,`provider_account`,`provider_invoice_id`),
  UNIQUE KEY `billing_cycle_order` (`payment_order_id`),
  KEY `billing_cycle_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  KEY `billing_cycle_order_fk` (`owner_id`,`payment_order_id`,`plan_version_id`),
  KEY `billing_cycle_order_provider_fk` (`owner_id`,`payment_order_id`,`provider_key`,`provider_account`),
  CONSTRAINT `billing_cycle_order_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `plan_version_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `plan_version_id`),
  CONSTRAINT `billing_cycle_order_provider_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_amount` CHECK ((`grant_credits` between 1 and 1000000000)),
  CONSTRAINT `billing_cycle_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `billing_cycle_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `billing_cycle_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `billing_cycle_state` CHECK (((`state` in (_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `billing_cycle_times` CHECK (((`period_end` > `period_start`) and (`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_accounts
CREATE TABLE `studio_credit_accounts` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `unit` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`),
  CONSTRAINT `credit_account_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `credit_account_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_account_status` CHECK ((`status` in (_latin1'open',_latin1'suspended'))),
  CONSTRAINT `credit_account_unit` CHECK ((`unit` = _latin1'credits'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorization_allocations
CREATE TABLE `studio_credit_authorization_allocations` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `allocated_credits` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`authorization_id`,`lot_id`),
  UNIQUE KEY `credit_allocation_owned` (`owner_id`,`authorization_id`,`lot_id`),
  KEY `credit_allocation_lot` (`owner_id`,`lot_id`,`authorization_id`),
  CONSTRAINT `credit_allocation_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_amount` CHECK ((`allocated_credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorizations
CREATE TABLE `studio_credit_authorizations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `profile` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `reservation_bytes` mediumblob NOT NULL,
  `reservation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_bytes` mediumblob NOT NULL,
  `policy_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_authorization_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_authorization_job` (`owner_id`,`profile`,`job_id`),
  CONSTRAINT `credit_authorization_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_authorization_amount` CHECK ((`credits` between 1 and 1000000000)),
  CONSTRAINT `credit_authorization_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_authorization_policy_sha` CHECK ((`policy_sha256` = lower(sha2(`policy_bytes`,256)))),
  CONSTRAINT `credit_authorization_policy_size` CHECK ((length(`policy_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_profile` CHECK ((`profile` in (_latin1'topview',_latin1'pollo',_latin1'invideo',_latin1'libtv',_latin1'flova',_latin1'creatok',_latin1'creatify'))),
  CONSTRAINT `credit_authorization_reservation_sha` CHECK ((`reservation_sha256` = lower(sha2(`reservation_bytes`,256)))),
  CONSTRAINT `credit_authorization_reservation_size` CHECK ((length(`reservation_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_authorization_state` CHECK ((`state` in (_latin1'HELD',_latin1'CAPTURED',_latin1'RELEASED'))),
  CONSTRAINT `credit_authorization_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_ledger_entries
CREATE TABLE `studio_credit_ledger_entries` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `operation_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_delta` bigint NOT NULL,
  `held_delta` bigint NOT NULL,
  `captured_delta` bigint NOT NULL,
  `facts_bytes` mediumblob NOT NULL,
  `facts_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_entry_operation` (`owner_id`,`operation_key`,`lot_id`),
  KEY `credit_entry_page` (`owner_id`,`created_at`,`id`),
  KEY `credit_entry_lot_fk` (`owner_id`,`lot_id`),
  KEY `credit_entry_allocation_fk` (`owner_id`,`authorization_id`,`lot_id`),
  CONSTRAINT `credit_entry_allocation_fk` FOREIGN KEY (`owner_id`, `authorization_id`, `lot_id`) REFERENCES `studio_credit_authorization_allocations` (`owner_id`, `authorization_id`, `lot_id`),
  CONSTRAINT `credit_entry_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_delta` CHECK ((((`kind` = _latin1'grant') and (`authorization_id` is null) and (`grant_delta` between 1 and 1000000000) and (`held_delta` = 0) and (`captured_delta` = 0)) or ((`kind` = _latin1'hold') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between 1 and 1000000000) and (`captured_delta` = 0)) or ((`kind` = _latin1'capture') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`captured_delta` between 1 and 1000000000) and (`held_delta` = -(`captured_delta`))) or ((`kind` = _latin1'release') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between -(1000000000) and -(1)) and (`captured_delta` = 0)))),
  CONSTRAINT `credit_entry_facts_sha` CHECK ((`facts_sha256` = lower(sha2(`facts_bytes`,256)))),
  CONSTRAINT `credit_entry_facts_size` CHECK ((length(`facts_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_entry_operation_key` CHECK ((((`kind` = _latin1'grant') and (`operation_key` = concat(`lot_id`,_latin1':grant'))) or ((`kind` <> _latin1'grant') and (`operation_key` = concat(`authorization_id`,_latin1':',`kind`)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_lots
CREATE TABLE `studio_credit_lots` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `held_credits` bigint unsigned NOT NULL,
  `captured_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_cycle_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_lot_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_lot_order` (`source_order_id`),
  UNIQUE KEY `credit_lot_cycle` (`source_cycle_id`),
  KEY `credit_lot_eligible` (`owner_id`,`expires_at`,`id`),
  KEY `credit_lot_order_fk` (`owner_id`,`source_order_id`),
  KEY `credit_lot_cycle_fk` (`owner_id`,`source_cycle_id`),
  CONSTRAINT `credit_lot_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_lot_cycle_fk` FOREIGN KEY (`owner_id`, `source_cycle_id`) REFERENCES `studio_billing_cycles` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_order_fk` FOREIGN KEY (`owner_id`, `source_order_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_counters` CHECK (((`grant_credits` between 1 and 1000000000) and (`held_credits` <= 1000000000) and (`captured_credits` <= 1000000000) and ((`held_credits` + `captured_credits`) <= `grant_credits`))),
  CONSTRAINT `credit_lot_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_lot_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_lot_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_lot_source` CHECK ((((`source_order_id` is not null) and (`source_cycle_id` is null)) or ((`source_order_id` is null) and (`source_cycle_id` is not null)))),
  CONSTRAINT `credit_lot_times` CHECK (((`expires_at` > `valid_from`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_plan_versions
CREATE TABLE `studio_credit_plan_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `credit_validity_seconds` int unsigned NOT NULL,
  `billing_interval_months` int unsigned NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  `retired_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_plan_version_key` (`plan_key`,`version_key`),
  CONSTRAINT `credit_plan_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `credit_plan_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `credit_plan_duration` CHECK (((`credit_validity_seconds` between 1 and 31622400) and ((`retired_at` is null) or (`retired_at` >= `published_at`)))),
  CONSTRAINT `credit_plan_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_plan_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_plan_kind` CHECK (((`kind` in (_latin1'subscription',_latin1'topup')) and (((`kind` = _latin1'subscription') and (`billing_interval_months` in (1,3,12))) or ((`kind` = _latin1'topup') and (`billing_interval_months` = 0)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_model_versions
CREATE TABLE `studio_media_model_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `config_bytes` mediumblob NOT NULL,
  `config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_version_key` (`model_id`,`version_key`),
  UNIQUE KEY `studio_media_version_owned_model` (`model_id`,`id`),
  CONSTRAINT `studio_media_version_model_fk` FOREIGN KEY (`model_id`) REFERENCES `studio_media_models` (`id`),
  CONSTRAINT `studio_media_version_sha` CHECK ((`config_sha256` = lower(sha2(`config_bytes`,256)))),
  CONSTRAINT `studio_media_version_size` CHECK ((length(`config_bytes`) between 1 and 65536))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_models
CREATE TABLE `studio_media_models` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `display_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_model_key` (`model_key`),
  KEY `studio_media_model_published_fk` (`id`,`published_version_id`),
  CONSTRAINT `studio_media_model_published_fk` FOREIGN KEY (`id`, `published_version_id`) REFERENCES `studio_media_model_versions` (`model_id`, `id`),
  CONSTRAINT `studio_media_model_kind` CHECK ((`kind` = _ascii'video')),
  CONSTRAINT `studio_media_model_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_model_status` CHECK (((`status` in (_ascii'draft',_ascii'active',_ascii'disabled')) and ((`status` <> _ascii'active') or (`published_version_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_events
CREATE TABLE `studio_payment_events` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_event_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `body_bytes` mediumblob NOT NULL,
  `body_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_event_provider` (`provider_key`,`provider_account`,`provider_event_id`),
  KEY `payment_event_order_fk` (`owner_id`,`order_id`,`provider_key`,`provider_account`),
  KEY `payment_event_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  CONSTRAINT `payment_event_order_fk` FOREIGN KEY (`owner_id`, `order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_event_body_sha` CHECK ((`body_sha256` = lower(sha2(`body_bytes`,256)))),
  CONSTRAINT `payment_event_body_size` CHECK ((length(`body_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_event_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_event_state` CHECK ((`state` in (_latin1'VERIFIED',_latin1'PROCESSED'))),
  CONSTRAINT `payment_event_subject` CHECK (((`order_id` is not null) or (`subscription_id` is not null))),
  CONSTRAINT `payment_event_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_orders
CREATE TABLE `studio_payment_orders` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_order_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_order_owned` (`owner_id`,`id`),
  UNIQUE KEY `payment_order_owned_plan` (`owner_id`,`id`,`plan_version_id`),
  UNIQUE KEY `payment_order_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `payment_order_client` (`owner_id`,`client_action_id`),
  UNIQUE KEY `payment_order_provider_key` (`provider_key`,`provider_account`,`provider_idempotency_key`),
  UNIQUE KEY `payment_order_provider_id` (`provider_key`,`provider_account`,`provider_order_id`),
  KEY `payment_order_plan_fk` (`plan_version_id`),
  CONSTRAINT `payment_order_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `payment_order_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_order_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `payment_order_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `payment_order_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `payment_order_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_order_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_order_state` CHECK (((`state` in (_latin1'CREATING',_latin1'CREATE_UNKNOWN',_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`provider_order_id` is not null) and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `payment_order_times` CHECK (((`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_sessions
CREATE TABLE `studio_sessions` (
  `token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `user_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `revoked_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`token_hash`),
  KEY `studio_session_expiry` (`expires_at`),
  KEY `studio_session_user` (`user_id`,`expires_at`),
  CONSTRAINT `studio_session_user_fk` FOREIGN KEY (`user_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_session_times` CHECK ((`expires_at` > `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_subscriptions
CREATE TABLE `studio_subscriptions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_subscription_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `cancel_at_period_end` tinyint(1) NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `subscription_owned` (`owner_id`,`id`),
  UNIQUE KEY `subscription_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `subscription_provider` (`provider_key`,`provider_account`,`provider_subscription_id`),
  KEY `subscription_plan_fk` (`plan_version_id`),
  CONSTRAINT `subscription_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `subscription_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `subscription_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `subscription_state` CHECK (((`state` in (_latin1'PENDING',_latin1'ACTIVE',_latin1'PAST_DUE',_latin1'CANCELLED',_latin1'ENDED')) and (`cancel_at_period_end` in (0,1)))),
  CONSTRAINT `subscription_times` CHECK (((`period_end` > `period_start`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_users
CREATE TABLE `studio_users` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `email` varchar(254) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `password_hash` varbinary(72) NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT '1',
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_user_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

积分账本链路

plan_versions → payment_orders(CREATING→PAID)→ payment_events(VERIFIED)→ credit_lots(带适用模型与品牌)→ ledger_entries(grant / hold / capture / release,只追加)→ authorizations / allocations。批准生成时按批次分配冻结额度,扣费与释放都有幂等键。
为什么这样设计账本只追加、用触发器禁止修改,才有可信的对账;批准时按批次分配冻结额度,才能在失败时准确释放。约束写在数据库里而不是应用里,是因为应用会有 bug,而约束不会(决策 D3、D17)。

TopView · 接口清单

共 55 个公开接口,取自源码路由盘点。路径都以 /v1 开头;写操作需要会话 Cookie、可信 Host/Origin 与 CSRF;带品牌的路径用 ?profile=topview;共享的登录、素材、模型目录路径不接受 profile 参数。

Agent/统一动作 7 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactionsrunInteractions200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactions/{interaction_id}/responsesrespondInteractionclient_action_id, card_sha256, action, values202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/interaction-actions/{client_action_id}interactionAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changeschangeConversationModelclient_action_id, expected_revision, agent_model, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changes/{client_action_id}modelChangeAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stopstopRunclient_action_id, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/stop-actions/{client_action_id}stopAction200
Asset 4 个
方法路径operationId请求字段成功
GET/v1/assetslistAssets200
POST/v1/assetsuploadAssetfile201
GET/v1/assets/{asset_id}getAsset200
GET/v1/assets/{asset_id}/contentassetContent200/206
LoginSession 1 个
方法路径operationId请求字段成功
POST/v1/auth/logoutlogout200
MaaBinding 5 个
方法路径operationId请求字段成功
GET/v1/maa/model-optionsmaaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/messagessendMaaMessageclient_action_id, content202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/actions/{client_action_id}getMaaAction200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stategetMaaState200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/eventsmaaEvents200
Media 11 个
方法路径operationId请求字段成功
GET/v1/media/modelspublicMediaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requestscreateMediaRequestclient_request_id, model_id, model_version_id, prompt, duration_seconds, ratio, resolution, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requestsmediaRequestByClient200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}getMediaRequest200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}getMediaJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}/outputgetMediaOutput200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/jobmediaJobByRequest200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/approveapproveMediaRequestclient_approval_id, request_sha256, quote_sha256200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/from-maacreateMediaRequestFromMaainput, source_run_id, source_assistant_sha256, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobslistMediaJobs200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/intentslistMediaIntents200
Other 9 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/canvas/revisionslistCanvasRevisions200
GET/v1/projects/{project_id}/canvas/revisions/{revision}getCanvasRevision200
POST/v1/projects/{project_id}/canvas/revisions/{revision}/restorerestoreCanvasRevisionexpected_revision200
GET/v1/projects/{project_id}/document/revisionslistTimelineRevisions200
GET/v1/projects/{project_id}/document/revisions/{revision}getTimelineRevision200
POST/v1/projects/{project_id}/document/revisions/{revision}/restorerestoreTimelineRevisionexpected_revision200
GET/v1/projects/{project_id}/assetslistProjectAssets200
POST/v1/projects/{project_id}/assetsattachProjectAssetasset_id, expected_revision200
POST/v1/projects/{project_id}/assets/{asset_id}/detachdetachProjectAssetexpected_revision200
Project 15 个
方法路径operationId请求字段成功
GET/v1/projectslistProjects200
POST/v1/projectscreateProjecttitle201
GET/v1/projects/{project_id}getProject200
GET/v1/projects/{project_id}/conversationslistConversations200
POST/v1/projects/{project_id}/conversationscreateConversationtitle201
GET/v1/projects/{project_id}/conversations/{conversation_id}getConversation200
GET/v1/projects/{project_id}/canvasgetCanvas200
POST/v1/projects/{project_id}/canvassaveCanvasexpected_revision, state200
GET/v1/projects/{project_id}/documentgetDocument200
POST/v1/projects/{project_id}/documentsaveDocumentexpected_revision, state200
GET/v1/projects/{project_id}/export-jobslistExportJobs200
POST/v1/projects/{project_id}/export-jobsqueueExportdocument_revision, output?202
GET/v1/projects/{project_id}/export-jobs/{job_id}getExportJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindinggetMaaBinding200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindingbindMaaagent_model XOR use_default:true200
User 3 个
方法路径operationId请求字段成功
POST/v1/auth/registerregisteremail, password201
POST/v1/auth/loginloginemail, password200
GET/v1/auth/sessionsession200

TopView 自己公开的接口调用流程(来自其官方资料)

步骤调用关系操作证据
取得上传身份API调用方 → 文件接口GET https://api.topview.ai/v1/upload/credential?format=...&needAccelerateUrl=...;请求头 Topview-Uid、Authorization;返回 fileId/uploadUrl。A
直传文件字节API调用方 → 对象存储PUT <返回的 uploadUrl>,Body 为本地文件字节;公开文档示例为 S3 签名 URL。A
确认文件可用API调用方 → 文件接口GET https://api.topview.ai/v1/upload/check?fileId=...;合同示例检查 result=true。A
提交预览任务API调用方 → 广告接口POST https://api.topview.ai/v1/m2v/task/submit;productLink 或 fileIds、preview=true;返回 taskId。A
轮询预览结果API调用方 → 广告接口GET https://api.topview.ai/v1/m2v/task/query?taskId=...&needCloudFrontUrl=...。A
审阅修改脚本API调用方 → 脚本接口GET https://api.topview.ai/v1/m2v/script/list?taskId=...;选择候选后 POST /v1/m2v/script/update,Body:{taskId,scriptId,scriptContents}。A
选定版本导出API调用方 → 导出接口POST https://api.topview.ai/v1/m2v/export;Body:{taskId,scriptId}。A
查询最终交付API调用方 → 广告接口按 taskId 继续使用 GET https://api.topview.ai/v1/m2v/task/query 查询任务状态与结果;最终资源地址以实际合同响应为准。A
为什么这样设计所有写动作都带客户端幂等键(client_action_id / client_request_id / client_approval_id),重复提交返回原回执;报价和批准分成两个接口;停止、切换模型、澄清卡回答都是「动作」而不是「状态修改」,所以每一步都能查回执。这样前端可以放心地重试,后端也不会因为重试产生第二次副作用(决策 D2、D8、D14)。

TopView · 代码与下载

topview-replica.zip
0.4 MB · 54 个文件 · SHA-256 2a843ce186fa1db905c5b8a71b20462ffad0b89e30a874f8148d9d2f089b0c6b
下载:topview-replica.zip · 后端与合同 · 全部代码
在线试用:http://119.23.106.60:8101/。测试环境,整套独立部署(前端、BFF、Maa 智能体、数据库),可以注册、对话、生成视频(Seedance,测试积分)。页面右下角有“内部复刻研究”标识。
目录内容
frontend/TopView 前端:<code>candidate-8875-unified-v22.html</code> 及它引用的全部脚本与样式
contracts/本品牌的表结构 SQL、ER(svg / mmd)、外键清单;接口目录(json / csv);共享身份库表结构
README.md怎么运行、需要自备什么

运行前端

python3 -m http.server -d frontend 8791
# 打开 http://127.0.0.1:8791/agent-integration/apps-production-brand-v14/topview/candidate-8875-unified-v22.html
后端和全部品牌:Go 后端、Python 媒体桥、全部测试和 7 个品牌的合同在 backend-and-contracts.zip;all-brands-code.zip 是全部。包里没有任何凭据、数据库备份或媒体文件,已做敏感串扫描。
需自备:MySQL 与账号(按 contracts 建库)、Maa 智能体服务(MAA_AGENT_ROOT)、模型网关与对象存储配置。静态前端单独打开不需要后端,但登录、对话、生成、导出需要整套服务。

复刻完成度(审计 2026-10-19)

结论:不是完美复刻。官方资料里的 63 项能力:实测跑通 1、只有通用对话 0、后端有但没跑过 9、没有后端 53。

真正跑通的:创作 Agent。

界面对照(画布编辑器(新建项目后,已登录)):真实页面采集到 38 个控件 / 标题文字,复刻页找到 15 个;位置偏差中位数 0 px(13/15 个在 24 px 内)。缺的有:Navigation menu、Codex参考体验 · 咖啡广告(仅规划)、System Settings、Tutorial、Publish、Credits、Material manager、Collapse、Search names or prompts、Filter、List、Today…。

逐个点击(已登录首页):51 个可点控件,点了有效 7 个,只提示“此功能正在接入” 38 个,没反应或只变样式 3 个,禁用 3 个,未点(发送 / 删除 / 退出等)0 个。

只提示“正在接入”的控件(38 次点击,33 种)

工作区、AI Marketer、Community、Settings、Vidu Q4 Preview ×2、Nano Banana 2.1 ×2、3D Builder ×2、Kling 4 · Coming Soon ×2、Film Festival ×2、Video Agent、Drama Studio、Skills、Motion Video Design、Short Film Scriptwriter、Actor Expression Enhancer、Explainer Video Maker、Video Recast、UGC Ads、More Skills、Short film、Music video、Product film、UGC ads、Explainer video、Video recast、Brand story、Social video、All、Short Film、Marketing、Animation、Social、Explainer

点了没反应或只变样式的控件(3 次点击,3 种)

Home、Film Studio、Seedance 2.5

复刻页自报的绑定状态(页面内嵌的绑定清单):原生工作区控件(新建项目、会话、登录等)7 / 10 个没有绑定到原页面的控件,由统一的工作区条代替;路由都标为本地已确认;输入框 2 个标为“需要修结构”。

Pollo · 总览

重点Pollo 看这三行就够
  • 它是什么:营销批量生成 + 大模板库(63 个技能);Agent 交互:流式事件,可停止,刷新后恢复。
  • 我们借鉴:事件带序号可续读;模板与技能做成数据(决策 D8 D9)。
  • 复刻现状:9 项已验证,在线试用 http://119.23.106.60:8102/;后端与其余六个品牌共用一套(24 张表、55 个接口),所以 ER / 表结构 / 接口页的通用部分默认折叠。

以 Agent、场景 Skill、多模型生成和营销工具为中心,另提供 Director 多轨编辑、API、MCP、CLI 和原生应用。历史登录仅验证一轮文字 Agent;其余媒体能力按官方说明盘点。

证据:W 官方说明,A 官方接口合同,C 客户端代码,O 实测。“未实测”= 只核对了官方页面,没有上传素材或生成。

能力目录
80 项,分 12 类
入口
Home / Create / Agent / Templates / Apps
复刻后端
24 张表 · 34 个外键 · 85 个索引

能力目录

Agent 与场景 4 项
能力说明证据限制
自然语言 Agent 创作Web Agent 支持文字交互;官网说明可把脚本、图片和链接作为创作输入。O2026-10-12 历史登录只完成一轮文字回复;多模态仅观察输入菜单与官网描述,未提交图片、链接、脚本或生成媒体。
场景 Skill 选择按故事、广告、UGC、音乐、讲解、电影、电商和地产选择创作场景。O2026-10-12 历史登录 UI 已观察 Skill 入口;未逐个执行,不能据入口推断完整工具链。
质量与执行策略Fast/Quality 控制生成策略,Autopilot/Guided 控制自主执行与确认节奏。O2026-10-12 历史 UI 和 high/guided 请求已观察;UI 档位与服务端实际 mode 映射、成本差异未验证。
澄清与局部修改Agent 可追问缺失条件,并修改指定场景、素材或方向后继续任务。W官网说明;未亲测中断、批准、修改后的依赖失效与恢复行为。
项目与会话 2 项
能力说明证据限制
项目、会话和历史项目下按 Thread 加载历史,单次 Run 有状态、消息和下一步建议。O2026-10-12 已观察 Project、Thread、Run 与分页请求;跨项目隔离、权限及数据库外键未验证。
流式回复与恢复入口Web 客户端采用 POST SSE 增量回复,并准备取消、重连和会话恢复逻辑。C2026-10-12 当前公开客户端静态分析;正常 SSE 另有实测,取消、断线续传、恢复保留期未触发。
视频生成 16 项
能力说明证据限制
文生视频与提示词辅助文字生成视频,可选画幅、时长、数量、分辨率和种子,并有提示词辅助。W参数随模型变化;页面中的生成音频选项不代表所有模型均支持。
图生视频上传静态图片作为起点,描述动作并选择输出设置。W未实测
参考图引导视频用角色、物体或场景参考引导视频内容与外观。W角色一致性是官网能力声明;跨镜头稳定性未做样本测试。
视频风格转换已有视频作为输入,保留动作结构并改变视觉风格。W未实测
动作迁移用参考动作视频驱动角色图片,复用表演与运动方向。W未实测
视频文字编辑上传视频,用文字修改画面物体、背景、风格或光线。W官方文字编辑说明;未生成或核实具体模型的支持范围。
视频放大提升已有视频的分辨率和清晰度。W未实测
视频换脸以人物参考替换目标视频中的面部。W未实测
视频去背景从视频中分离主体并移除背景。W未实测
视频延长根据已有片段继续生成后续画面。W未实测
唇形同步让人物视频的口型与输入声音匹配。W未实测
视频去字幕移除画面中已有的烧录字幕。W未实测
视频降噪降低视频中的颗粒和噪点。W未实测
视频物体移除删除视频画面中的指定物体。W未实测
视频重构画幅为不同画幅重新安排视频的取景。W未实测
视频主体抠像分离指定视频主体,作为重新合成的素材。W未实测
图片生成与编辑 16 项
能力说明证据限制
多模型图片生成文字生成图片,选择模型、画幅及参考方向,用于设计、角色和商品视觉。W未实测
图生图与参考变体输入已有图片生成变化版本,调整内容、风格和细节。W未实测
图片文字编辑用编辑指令调整图片内容、布局或风格。W未实测
图片水印移除清理图片上的水印或覆盖标记。W未实测
图片局部擦除移除图片中的指定物体或区域内容。W未实测
图片放大放大图片并增强细节。W未实测
图片去背景从图片分离主体并移除背景。W未实测
图片换脸用人物参考替换目标照片中的面部。W未实测
对话式图片创作通过聊天描述生成或继续修改图片。W未实测
图片锐化增强模糊图片的边缘和视觉清晰度。W未实测
图片背景生成为主体生成新的场景背景。W未实测
图片背景模糊模糊背景以突出主体。W未实测
图片背景替换将已有背景替换为指定场景。W未实测
图片白底处理将图片背景调整为白色。W未实测
图片人物移除从图片中删除选定人物。W未实测
面部抠图从人物图片提取面部或头部区域。W未实测
角色与音频 5 项
能力说明证据限制
照片数字人与说话角色单张照片或角色参考生成说话视频,配合语音呈现口播。W官方页面说明;未创建角色,口型、身份和动作质量未实测。
多语言文字配音选择语言、口音和音色,将文字转为配音音频。W未生成配音;音色许可、语言效果和商业使用范围须确认。
音效生成按声音描述生成音效,为视频补充环境和动作声音。W未实测
商品数字人用商品照片生成由数字人展示或讲解商品的视频。W未实测
声音克隆用参考音频建立可复用声音。W未上传声音;克隆授权、样本要求和语言质量须实测。
画布与时间轴 4 项
能力说明证据限制
Director 多场景上下文从想法或已有镜头组织作品,复用人物、商品和地点并比较不同 take。W官网 Director Beta 能力;未登录创建工程,不能据此推断其与 Agent Project 的实体关系。
多轨时间轴编辑视频、字幕、音频放在多轨时间轴,可裁剪、拆分和重排。WW:当前官网明确展示;未验证工程保存接口、版本冲突、导出结果或与 Canvas 同步。
字幕编辑修改字幕文字、字体、大小和颜色,可逐条或统一应用。W官网描述;字幕格式、自动识别准确率和导出兼容性未验证。
镜头再生成与首末帧镜头保留提示词、参考、模型和参数,按位置再生成并用首末帧衔接。W官网描述;首末帧连续性与历史版本持久化未验证。
素材与品牌 2 项
能力说明证据限制
分类素材与输入来源素材按商品、角色、场景、风格、道具分类;附件支持上传、元素、品牌和作品来源。O2026-10-12 历史登录 UI 已观察;素材详情、用户级归属、跨项目权限和版本结构未验证。
Brand Kit保存品牌标志、颜色、字体和语气,供品牌内容复用。W官网说明;字段 Schema、自动应用范围与团队共享权限未实测。
模板与灵感 3 项
能力说明证据限制
场景应用和模板Apps 与 Marketing Studio 提供按创作目的选择的 Skill、应用与模板。W目录与模板入口已核实;不把同一生成能力的应用别名重复统计。
灵感与作品复用浏览示例灵感,并将已认可的参考或生成素材带回创作。W官网描述;社区投稿、点赞、复制权限及公开作品治理未验证。
广告灵感库Marketing Studio 提供 Ad Library,用于发现和搜索广告参考。W公开页面正文有限;广告覆盖量、筛选条件、下载与使用权限未知。
营销批量创作 18 项
能力说明证据限制
商品链接转视频广告从商品链接提取背景与卖点,形成视频广告任务。W官网入口;未抓取商品链接,素材采集、授权和广告成片质量未实测。
参考广告改编以参考广告为起点,为自己的商品复用叙事或展示方式。W未提交参考广告;相似度、可编辑范围和第三方素材权利未知。
批量短视频创作按批量任务生成短视频变体。WWeb 批量并发、任务数量上限与失败计费未验证;不套用 CLI/MCP 批量上限。
商品照片生成生成用于展示和宣传商品的照片。W官方说明;商品细节保持与平台审核结果未实测。
虚拟试穿视频用服装和人物参考生成穿搭展示视频。W未实测
商品照片转视频广告以商品图片作为视频广告的起点。W未实测
脚本转视频广告输入已写好的广告脚本生成对应视频。W未实测
UGC 风格视频广告将商品信息组织为用户分享或口播风格的广告。W未实测
商品 Listing 图片套组为商品生成配套展示图片。W未实测
商品海报生成把商品信息组合为推广海报。W未实测
商品场景布置把商品放进生活或营销展示场景。W未实测
服装平铺图将服装参考整理为平铺展示图。W未实测
商品图片美化改善已有商品图的展示效果。W未实测
服装隐形模特生成保留服装形状的无模特展示图。W未实测
商品参考图改编根据参考商品视觉制作自己的商品展示。W未实测
图片服装替换Marketing Studio 有替换人物服装的工具入口。W当前公开目录可见工具名;具体输入、数量上限、输出结构和收费未实测。
商品 A+ 内容Marketing Studio 有制作电商 A+ 内容的入口。W当前公开目录可见工具名;具体输入、数量上限、输出结构和收费未实测。
批量参考视频改编Marketing Studio 有批量改编参考短视频的入口。W当前公开目录可见工具名;具体输入、数量上限、输出结构和收费未实测。
分享与交付 1 项
能力说明证据限制
公开可见性、复制控制与下载生成页有 Public Visibility 与 Copy Protection 选项;成品可下载用于交付。W只核实入口与官网说明;分享 URL、ACL、复制保护效果及实时协作均未验证。
订阅与权益 3 项
能力说明证据限制
订阅与积分余额订阅发放积分,工具、模型、时长、数量和分辨率影响消耗,可查看余额。W官网 FAQ 说明订阅积分不结转;不固化价格、当前余额或所有套餐额度。
升级和加购积分可通过升级或符合条件的积分加购包补充使用额度。W资格、结算方式与地区价格以当前账号确认;未购买或改变订阅。
商业使用与无水印权益官方说明付费商业使用及无水印导出权益。W无水印依据官方地区页,具体工具、模型和套餐应查看当前条款;不作第三方素材版权保证。
开放集成与客户端 6 项
能力说明证据限制
统一多模型入口视频、图片和音频能力按模型入口选择,共用产品工具导航。W模型品牌合并为目录能力;可用版本、参数和输出规格随时间变化。
异步生成 APIAPI Key 鉴权提交模型生成任务,返回 taskId 与 pending 状态。A官方合同未调用;示例和正文存在旧字段表述,集成应以选定模型 Schema 为准。
任务查询与回调按 taskId 查询生成结果、失败原因和成本,或用 webhook 接收完成通知。A仅核实合同;回调签名、重试保证、幂等与可用性未验证。
MCP 创作和任务卡片通过 MCP 生成、编辑和增强素材,支持任务状态、费用估算及客户端卡片。W官方能力说明;未连接 MCP,不假定所有客户端支持同一种交互卡片。
CLI 与本地文件接入CLI 通过账号授权调用创作能力,可提交参考文件、批量任务并查询余额和进度。W官方称单批 1–12 任务;未安装执行,不能套用于 Web 或公开 API。
iOS 与 Android 创作应用原生应用提供生成、编辑、Agent、模板及社交尺寸创作入口。W只核实官方商店说明,未安装;Director、素材同步和 Web 功能是否完整对应未知。

尚不清楚的部分

竞品技术栈观察

Pollo 可高置信识别此前发布的 Next.js/Turbopack 前端构件;精确框架版本、原始源码语言、Nginx 与后端实现仍未知。Cloudflare/Vercel 只限边缘或子站线索。

项目观察到的内容状态置信度
Nginx 产品及准确版本未确认使用 Nginx;准确版本未知。unknownlow
浏览器运行语言已交付浏览器 JavaScript chunks;不是原始 TypeScript 源文件。observedhigh
前端原始源码语言未知:可能有 JS、TS 或 TSX,但未取得源码证据。unknownlow
前端框架及版本Next.js 指纹很强;精确 Next.js、React 版本未知。inferredhigh
构建器、SSR 与渲染模式观察到 TURBOPACK 和 _next 构件;SSR/SSG/ISR 策略与构建器版本未知。observedhigh
后端语言未知:业务服务、Agent、计费及生成 Worker 的语言均未证实。unknownlow
后端框架及版本实际框架未知;仅观察到 tRPC 客户端与 LangGraph 风格 Agent 协议。unknownlow
CDN、边缘与源站界限先前公网 DNS 落在 Cloudflare 地址段;docs 有 Vercel CNAME。业务源站与地域未知。inferredhigh
前后端仓库与目录关系未知:同域 API、Next 构件及公开 CLI 仓库不能证明前后端同仓或同目录。unknownlow
竞品服务调用图

真实文字调用链为项目上下文 → Thread → Run POST SSE → 工具消息 → 终态读回。媒体、素材、权益和部署边界另外标注,不能从兼容协议还原服务端实现。

节点层职责证据
Web客户端client已观察项目列表、场景入口和文字 Agent 交互的浏览器 UI。O
公开边缘入口entry公网 DNS 地址段与 Cloudflare 相符,可能提供产品域入口与缓存。I
业务JSON入口entry实际 tRPC 列表过程的公开调用边界。O
Agent HTTP入口entry实际 /api/agent-gateway/agent/v1 Thread/Run 资源与 SSE 入口。O
项目会话职责domain处理 Project/Thread 的身份与项目上下文,组织会话查询。I
订阅积分职责domain提供订阅、使用桶与允许能力的业务视图,可能参与任务额度控制。I
素材产物职责domain用户元素列表及 Thread artifacts 入口反映素材查询、引用或结果整理职责。I
Agent运行职责runtimeRun 状态及工具消息表明存在消息组织与执行编排职责。I
文字呈现工具runtime协议中确实出现名为 message_notify_user 的调用和成功 ToolResult。O
媒体任务职责runtime官网描述由 Agent 生成视频/图片/声音,可能需要组织生成任务与输出。I
会话状态保存dataGET state 和 conversation 可返回终态消息,可能有缓存、文件或数据库保存。I
模型执行边界external产品和公开生成合同涉及模型选择,可能调用内部模型或外部供方。I
文件存储待核实data媒体或用户文件如何持有、落库和交付尚未确认。U
公开生成APIentry官方 API 合同定义模型任务提交、查询及 webhook。A

增长与运营机制

多语言场景与模型矩阵、模板激活、机器可读文档和 MCP / CLI 渠道。(robots + docs llms + /agent DOM 元数据)

复刻状态

能力状态依据
登录、注册、会话过期提示、登录入口已验证过期后 8 秒内出现横幅,点「登录」弹登录框,登录后会话自动恢复
Agent 多轮对话、刷新后恢复会话已验证真实 Maa;18 条消息恢复
根据方案生成视频(报价→批准→生成→素材)已验证两次真实 Seedance 2.5 生成,15 积分/次;报价过期后批准被拒,不建任务不扣费
停止回复已验证修复了 v17 起就存在的缺陷;数据库有停止记录,run=cancelled;对话里显示「已停止回复」
切换模型已验证DeepSeek-V4-Flash,模型版本 0→1
素材页、Apps/Tools 目录、Templates 页、Create已验证Apps 为 Agent 模板入口,Tools 标注未接入;Templates 为自写起始提示词
澄清卡片(HITL)已验证(隔离环境)没说清商品时 Agent 发出「商品名称」卡,页面渲染卡片,用户回答后同一个 Run 恢复并继续执行,最终给出完整分镜方案(真实 Maa,新对话完整走通)。过程中遇到并修了三个问题:Agent 的 plan-only 策略不给交互工具;业务后端只认哈希形式的卡指纹;前端模型切换层在卡打开时拒绝回答。限制:业务后端只认这一种单字段卡,Agent 侧已限制只能发它;一次恢复后的 Run 因模型偶发「工具调用无法解析」失败,重试后完成。生产实例未启用
多模型生成(Seedance 2.5 / 2.0 / 2.0 Fast / 2.0 Mini)已验证四个模型都真实生成成功并入账(Mini 5 秒 3 积分、Fast 15 秒 10 积分、2.0 5 秒 8 积分、2.5 15 秒 15 积分);每个模型一个独立 worker 进程;对话框的模型下拉按数据显示各自价格。新增模型不改代码:只加一份 SQL,并给该 worker 设环境变量 STUDIO_MEDIA_GATEWAY_EXTRA_MODELS。另试了 Seedance 1.5 Pro(两条供应商路由),网关接收后被供应商判失败且无原因,疑似当前密钥无权限,冻结积分保持“费用待核对”,没有出片
聊天内附件已验证(隔离环境)在隔离实例上点输入框「+」选图→上传→发送,助手回答出图片颜色;上传 201、消息 202。生产实例没有启用:附件与统一品牌实例互斥,且需要带输入文件能力的 Maa 与对应迁移
前端入口:agent-integration/apps-production-brand-v14/pollo/candidate-8891-attachments-v22.html
复刻的架构 七个品牌相同,展开查看
flowchart LR
  U[浏览器] -->|静态页| FE[品牌前端<br/>令牌 + 通用层 + 运行时脚本]
  FE -->|/v1/* Cookie + CSRF| BFF[BFF 业务服务 Go]
  BFF --> MY[(MySQL<br/>共享身份库 + 品牌库)]
  BFF -->|会话 / 消息 / 事件| MAA[Maa Agent]
  BFF -->|任务| WK[媒体 worker]
  WK -->|submit_video| GW[模型网关]
  GW --> MOD[视频模型]
  MAA --> GW
  BFF --> OBJ[(素材 / 导出存储)]
所有七个复刻共用同一套后端:24 张表的品牌库 + 15 张表的共享身份库,55 个公开接口(路径里用 ?profile=pollo 区分品牌)。品牌差异在前端:令牌(颜色、圆角、字体)、入口与页面结构。
为什么这样设计七个竞品的后端结构在我们的实测里几乎一致,差别只在入口、模板和视觉。所以复刻把差异收敛到前端令牌,后端只维护一份;这也是最终设计里「共享内核 + 品牌配置」的来源(决策 D17)。

Pollo · Agent 交互过程

竞品里观察到的

NOT_RUN_NO_FREE_CONFIRMATION 本轮登录后查看Agent和Director只读工程、素材与时间轴,余额422;新文字回合未发现明确免费承诺,因此未发送。此前17帧SSE文字回合、0媒体/0积分,仅复用历史证据,不冒充本轮完整方案交互。

Pollo 实测的接口

方法路径请求用途
POST/threads`metadata:{idempotency_key:string,project_id:string}``{code:number,msg:string,data:{thread_id:string}}`,先建立长会话
POST/threads/{thread_id}/runs/stream下节完整结构200 SSE,创建并订阅这一轮 Run
POST/threads/search`metadata:{project_id:string},limit:number,offset:number``data:{threads:ThreadSummary[],total:number}`,按当前 Project 加载历史
POST/threads/status`thread_ids:string[]``data.threads[]:{thread_id,status,metadata,updated_at}`,批量状态轮询
GET/threads/{thread_id}/state无 Body`data:{checkpoint,checkpoint_id,parent_checkpoint,parent_checkpoint_id,created_at,interrup
GET/threads/{thread_id}/runsQuery keys `limit,offset,status``data.runs:[]`,此处 UI 查询特定状态,不能据空列表推断无历史 Run
POST/threads/{thread_id}/conversation`{limit:10}``data:{messages:Message[],has_more:boolean,next_before_id:number}`
POST/threads/{thread_id}/artifacts`{}``data:{artifact_groups:{}}`,本次无素材
GET/threads/{thread_id}/suggestionsQuery key `turn_id``data.items:string[]`,本次返回 3 条下一步建议
Pollo 的消息走「HTTP JSON 控制面 + POST 响应体的 SSE」,不是 WebSocket;一次短回复实测 17 帧、0 媒体、可见余额无变化(只证明这一次)。

竞品公开的调用流程

步骤调用关系操作证据
载入项目上下文浏览器 → 业务入口tRPC UI batch:userProject.getListV2、generationModel.list、userElement.list。O
读取项目会话浏览器 → Agent入口POST /api/agent-gateway/agent/v1/threads/search;metadata.project_id、limit、offset。O
创建长会话浏览器 → Agent入口POST /api/agent-gateway/agent/v1/threads;metadata:{project_id,idempotency_key} → data.thread_id。O
启动当前回合浏览器 → Agent入口POST /api/agent-gateway/agent/v1/threads/{thread_id}/runs/stream;input.messages、config.configurable、metadata、stream_mode;返回 text/event-stream。O
合并流式消息Agent入口 → 浏览器SSE metadata(run_id)、values、messages([MessageChunk,ExecutionMetadata])、end;每帧含 event/data/id。O
显示工具结果Agent入口 → 浏览器messages 中出现 message_notify_user:ToolCall.id == ToolResult.tool_call_id;status=success、artifact=[]。O
读回持久视图浏览器 → Agent入口end success 后:GET /api/agent-gateway/agent/v1/threads/{thread_id}/state;POST .../conversation;POST .../artifacts;GET .../suggestions?turn_id=...。O
核对商业视图浏览器 → 业务入口刷新首页可见积分,并读取既有 subLog.getSubLog / subUsage 订阅与用量投影;未取得完整私有 HTTP 路径。O

在本品牌复刻上的测试结果

能力状态依据
Agent 多轮对话、刷新后恢复会话已验证真实 Maa;18 条消息恢复
根据方案生成视频(报价→批准→生成→素材)已验证两次真实 Seedance 2.5 生成,15 积分/次;报价过期后批准被拒,不建任务不扣费
停止回复已验证修复了 v17 起就存在的缺陷;数据库有停止记录,run=cancelled;对话里显示「已停止回复」
切换模型已验证DeepSeek-V4-Flash,模型版本 0→1
澄清卡片(HITL)已验证(隔离环境)没说清商品时 Agent 发出「商品名称」卡,页面渲染卡片,用户回答后同一个 Run 恢复并继续执行,最终给出完整分镜方案(真实 Maa,新对话完整走通)。过程中遇到并修了三个问题:Agent 的 plan-only 策略不给交互工具;业务后端只认哈希形式的卡指纹;前端模型切换层在卡打开时拒绝回答。限制:业务后端只认这一种单字段卡,Agent 侧已限制只能发它;一次恢复后的 Run 因模型偶发「工具调用无法解析」失败,重试后完成。生产实例未启用
多模型生成(Seedance 2.5 / 2.0 / 2.0 Fast / 2.0 Mini)已验证四个模型都真实生成成功并入账(Mini 5 秒 3 积分、Fast 15 秒 10 积分、2.0 5 秒 8 积分、2.5 15 秒 15 积分);每个模型一个独立 worker 进程;对话框的模型下拉按数据显示各自价格。新增模型不改代码:只加一份 SQL,并给该 worker 设环境变量 STUDIO_MEDIA_GATEWAY_EXTRA_MODELS。另试了 Seedance 1.5 Pro(两条供应商路由),网关接收后被供应商判失败且无原因,疑似当前密钥无权限,冻结积分保持“费用待核对”,没有出片
复刻里的 Agent 交互设计(时序图、状态机、规则) 七个品牌相同,展开查看
sequenceDiagram
  autonumber
  participant B as 浏览器
  participant S as BFF
  participant DB as MySQL
  participant M as Maa Agent
  participant W as 媒体 worker
  participant G as 模型网关
  B->>S: POST /projects, /conversations
  B->>S: POST maa/binding
  S->>DB: maa_bindings dispatching
  S->>M: 创建原生会话
  M-->>S: native_cid
  S->>DB: maa_bindings bound
  B->>S: POST maa/messages {client_action_id, content}
  S->>DB: maa_message_actions 幂等回执
  S-->>B: 202 + run_id
  S->>M: 提交消息
  S->>DB: maa_runs queued 到 running
  loop 事件游标
    B->>S: GET runs/{id}/events
    S-->>B: assistant.delta / assistant.message
  end
  M-->>S: run.completed
  S->>DB: 记录助手内容 SHA-256
  B->>S: POST media/requests/from-maa {source_run_id, sha256}
  S-->>B: 报价 + 有效期 + quote_sha256
  B->>S: POST approve {client_approval_id, request_sha256, quote_sha256}
  S->>DB: 冻结积分 + 建任务
  W->>G: submit_video
  G-->>W: 视频
  W->>DB: 产出入库 + 扣费
  B->>S: GET media/jobs/{id}

Run 状态机

stateDiagram-v2
  [*] --> queued: POST maa/messages 202
  queued --> running
  running --> waiting_interaction: 需要澄清卡
  waiting_interaction --> running: respondInteraction
  running --> completed
  running --> failed
  running --> cancelled: stopRun
  waiting_interaction --> cancelled
  completed --> [*]
  failed --> [*]
  cancelled --> [*]

媒体任务状态机

stateDiagram-v2
  [*] --> quoted
  quoted --> expired: 约 5 分钟
  quoted --> approved: approve
  approved --> held: 冻结积分
  held --> queued
  queued --> running: worker 领取
  running --> succeeded: 产出入库
  succeeded --> captured: 扣费确认
  running --> failed
  failed --> 待核对: 供应商已受理后失败,冻结保留,界面写「费用待核对」

交互规则

环节规则落库
绑定会话bindMaa 传 agent_model 或 use_default;先 dispatching,拿到原生会话号后才 bound(约束保证二者一致)maa_bindings
发消息client_action_id 幂等;返回 202 + run_id;同一个动作 ID 内容不同返回 409maa_message_actions, maa_runs
事件按 seq 游标读取;服务端先清洗路径、内部地址和令牌再公开maa_runs.last_event_seq
完成证据完成时记录助手内容的两份 SHA-256 与字节数maa_runs
澄清卡(HITL)Run 进入 waiting_interaction;respondInteraction 带 card_sha256;不能恢复两次maa_interactions, maa_interaction_actions
停止stopRun 幂等,回执只说「已观测到终态」;对话里保留「已停止回复」maa_run_stop_actions
切换模型必须带 expected_revision;有未决切换时拒绝发新消息maa_model_change_actions
生成视频from-maa → 报价 → 批准 → 冻结 → worker → 入库 → 扣费;批准带请求哈希与报价哈希studio_media_*, 积分账本
报价过期批准被拒,不建任务不扣费;界面提示重新报价studio_media_requests.expires_at
失败后的积分供应商已受理后失败:冻结保留,界面写「费用待核对」,不自动释放;本部署的账本里还没出现过释放分录(12 笔冻结、8 笔扣费、其余 4 笔仍冻结)studio_credit_ledger_entries
会话过期每 8 秒检查登录状态;失效时横幅提示,登录后恢复—
为什么这样设计把事件做成带序号的流、把动作做成带幂等键的回执,是因为长回复一定会遇到断线、刷新和重复点击。序号让续读不丢不重;幂等让重复点击安全;诚实的回执(只说「已观测到终态」)避免把「已停止」说成「不收费」。我们还在复刻里修掉了一个停止按钮从不发请求的缺陷,这说明每条交互路径都需要端到端测试(决策 D8)。

Pollo · ER 图

品牌库 studio_profile_pollo:24 张表、34 个真实外键、66 个检查约束、22 个触发器。图由实际表结构生成,箭头从父表指向子表。品牌库与共享身份库之间的 owner_id、asset_id 靠应用层归属检查,不画成外键。

共 61 条关系:一对多 37 条,一对一 10 条,一对多(父可选) 10 条,一对一(父可选) 4 条;多对多 2 组(经关联表)。

用户与登录 素材 积分账本 支付与订阅 模型目录 项目与会话 画布 · 时间轴 · 导出 Agent 交互 生成 · 媒体 · 交付

基数标注(线上的文字,箭头从父表指向子表):1:N 一对多,子表外键必填;0..1:N 一对多,但外键可空,所以父是可选的;1:1 一对一,子表外键上有唯一键;0..1:1 一对一且父可选。红色粗双向线 = N:M 多对多,经带 ⇄ 的关联表(它的主键就是两个外键)。实线 = 数据库真实外键;灰色虚线 = 跨库关系(两个库之间数据库无法建外键,由应用层检查);橙色粗框与粗线 = Agent 交互相关。每张表都带 owner_id(指向用户),为了图清楚没有逐条画线。

全景图:全部实体与关系

默认缩放到页面宽度,先看整体结构;点一下图切换到原始大小(看清表名,在框内上下左右滚动),再点一下缩回。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":26,"rankSpacing":80}}}%%
flowchart LR
  subgraph sg_i_user["用户与登录"]
    direction TB
    t_studio_users["users"]
    t_studio_sessions["sessions"]
  end
  subgraph sg_i_asset["素材"]
    direction TB
    t_studio_assets["assets"]
  end
  subgraph sg_i_credit["积分账本"]
    direction TB
    t_studio_credit_accounts["credit_accounts"]
    t_studio_credit_lots["credit_lots"]
    t_studio_credit_authorizations["credit_authorizations"]
    t_studio_credit_authorization_allocations["⇄ credit_authorization_allocations"]
    t_studio_credit_ledger_entries["credit_ledger_entries"]
    t_studio_credit_plan_versions["credit_plan_versions"]
    t_studio_billing_cycles["billing_cycles"]
  end
  subgraph sg_i_pay["支付与订阅"]
    direction TB
    t_studio_payment_orders["payment_orders"]
    t_studio_payment_events["payment_events"]
    t_studio_subscriptions["subscriptions"]
  end
  subgraph sg_i_model["模型目录"]
    direction TB
    t_studio_media_models["media_models"]
    t_studio_media_model_versions["media_model_versions"]
  end
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  subgraph sg_media["生成 · 媒体 · 交付"]
    direction TB
    t_studio_media_requests["media_requests"]
    t_studio_media_jobs["media_jobs"]
    t_studio_media_submission_attempts["media_submission_attempts"]
    t_studio_media_outputs["media_outputs"]
    t_generation_jobs["gen_jobs"]
    t_generation_reservations["gen_reservations"]
    t_generation_deliveries["gen_deliveries"]
    t_generation_development_budgets["gen_development_budgets"]
    t_studio_credit_recovery_intents["credit_recovery_intents"]
  end
  t_studio_users -->|"1:N"| t_studio_assets
  t_studio_payment_orders -->|"1:1"| t_studio_billing_cycles
  t_studio_subscriptions -->|"1:N"| t_studio_billing_cycles
  t_studio_users -->|"1:1"| t_studio_credit_accounts
  t_studio_credit_authorizations -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_lots -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_authorizations
  t_studio_credit_authorization_allocations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_authorizations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_lots -->|"1:N"| t_studio_credit_ledger_entries
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_lots
  t_studio_billing_cycles -->|"0..1:1"| t_studio_credit_lots
  t_studio_payment_orders -->|"0..1:1"| t_studio_credit_lots
  t_studio_media_models -->|"1:N"| t_studio_media_model_versions
  t_studio_media_model_versions -->|"0..1:1"| t_studio_media_models
  t_studio_payment_orders -->|"0..1:N"| t_studio_payment_events
  t_studio_subscriptions -->|"0..1:N"| t_studio_payment_events
  t_studio_users -->|"1:N"| t_studio_payment_events
  t_studio_credit_plan_versions -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_sessions
  t_studio_credit_plan_versions -->|"1:N"| t_studio_subscriptions
  t_studio_users -->|"1:N"| t_studio_subscriptions
  t_generation_jobs -->|"1:1"| t_generation_deliveries
  t_studio_projects -->|"1:N"| t_generation_deliveries
  t_studio_conversations -->|"1:N"| t_generation_deliveries
  t_generation_development_budgets -->|"1:N"| t_generation_jobs
  t_studio_projects -->|"1:N"| t_generation_jobs
  t_studio_conversations -->|"1:N"| t_generation_jobs
  t_generation_jobs -->|"1:1"| t_generation_reservations
  t_studio_conversations -->|"1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"0..1:1"| t_maa_bindings
  t_maa_interactions -->|"1:1"| t_maa_interaction_actions
  t_maa_runs -->|"1:N"| t_maa_interactions
  t_maa_bindings -->|"1:N"| t_maa_message_actions
  t_maa_runs -->|"0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"1:N"| t_maa_model_change_actions
  t_maa_runs -->|"1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"1:N"| t_maa_runs
  t_studio_canvases -->|"1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"1:1"| t_studio_canvases
  t_studio_projects -->|"1:N"| t_studio_conversations
  t_studio_media_jobs -->|"1:N"| t_studio_credit_recovery_intents
  t_studio_projects -->|"1:N"| t_studio_export_jobs
  t_studio_media_requests -->|"1:1"| t_studio_media_jobs
  t_studio_media_jobs -->|"1:N"| t_studio_media_outputs
  t_studio_conversations -->|"1:N"| t_studio_media_requests
  t_maa_runs -->|"0..1:N"| t_studio_media_requests
  t_studio_media_jobs -->|"1:N"| t_studio_media_submission_attempts
  t_studio_projects -->|"1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"1:1"| t_studio_video_documents
  t_studio_assets -.->|"1:N"| t_studio_project_asset_refs
  t_studio_assets -.->|"1:N"| t_studio_media_outputs
  t_studio_assets -.->|"1:N"| t_generation_deliveries
  t_studio_media_model_versions -.->|"1:N"| t_studio_media_requests
  t_studio_media_models -.->|"1:N"| t_studio_media_requests
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_media_jobs
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_credit_recovery_intents
  t_studio_credit_authorizations <-.->|"N:M · 经 credit_authorization_allocations"| t_studio_credit_lots
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_users,t_studio_sessions user
class t_studio_assets asset
class t_studio_credit_accounts,t_studio_credit_lots,t_studio_credit_authorizations,t_studio_credit_authorization_allocations,t_studio_credit_ledger_entries,t_studio_credit_plan_versions,t_studio_billing_cycles credit
class t_studio_payment_orders,t_studio_payment_events,t_studio_subscriptions pay
class t_studio_media_models,t_studio_media_model_versions model
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
class t_studio_media_requests,t_studio_media_jobs,t_studio_media_submission_attempts,t_studio_media_outputs,t_generation_jobs,t_generation_reservations,t_generation_deliveries,t_generation_development_budgets,t_studio_credit_recovery_intents media
class t_studio_credit_authorization_allocations,t_studio_project_asset_refs junction
  style sg_i_user fill:none,stroke:#6b7280,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_asset fill:none,stroke:#06b6d4,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_credit fill:none,stroke:#eab308,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_pay fill:none,stroke:#ec4899,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_model fill:none,stroke:#6366f1,stroke-width:1px,stroke-dasharray:5 4
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  style sg_media fill:none,stroke:#22c55e,stroke-width:1px
  linkStyle 30,31,32,33,34,35,36,37,38,48 stroke:#f97316,stroke-width:2.5px
  linkStyle 54,55,56,57,58,59,60 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 61,62 stroke:#dc2626,stroke-width:3px

多对多(N:M)

数据库里没有“多对多”这种列,它总是靠一张关联表表达。下面这几张表的主键由两个外键组成,所以它们两端是多对多:

多对多的两端关联表关联表的主键 / 唯一键
studio_credit_authorizations ⇄ studio_credit_lotsstudio_credit_authorization_allocationsowner_id, authorization_id, lot_id
studio_projects ⇄ studio_assetsstudio_project_asset_refsproject_id, asset_id

Agent 交互相关(圈出来)

绑定 → Run → 消息 / 澄清卡 / 切换模型 / 停止,每个动作一张回执表;线上是“外键列 · 基数”。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  t_studio_conversations -->|"conversation_id · 1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"pending_model_change_id · 0..1:1"| t_maa_bindings
  t_maa_interactions -->|"interaction_id · 1:1"| t_maa_interaction_actions
  t_maa_runs -->|"public_run_id · 1:N"| t_maa_interactions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_message_actions
  t_maa_runs -->|"public_run_id · 0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_model_change_actions
  t_maa_runs -->|"public_run_id · 1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_runs
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  linkStyle 0,1,2,3,4,5,6,7,8 stroke:#f97316,stroke-width:2.5px

画布与时间轴

项目下是一份视频文档,画布、画布版本、时间线版本和导出都挂在这份文档上;素材放在共享身份库,项目只记引用,项目与素材是多对多,经 project_asset_refs。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_asset["素材(共享身份库)"]
    direction TB
    t_studio_assets["assets"]
  end
  t_studio_canvases -->|"project_id · 1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"restored_from_revision · 0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_canvases
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
  t_studio_projects -->|"project_id · 1:N"| t_studio_export_jobs
  t_studio_projects -->|"project_id · 1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"project_id · 1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"restored_from_revision · 0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_video_documents
  t_studio_assets -.->|"asset_id · 1:N"| t_studio_project_asset_refs
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_studio_assets asset
class t_studio_project_asset_refs junction
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px
  linkStyle 9 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 10 stroke:#dc2626,stroke-width:3px
关系清单 逐条 61 条,含基数
父表子表外键列(子表里)基数约束域
generation_jobsgeneration_deliveriesjob_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_assetsgeneration_deliveriesasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_conversationsgeneration_deliveriesowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_deliveriesowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_development_budgetsgeneration_jobsowner_id, budget_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_conversationsgeneration_jobsowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_jobsgeneration_reservationsjob_id, budget_id, owner_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_model_change_actionsmaa_bindingsowner_id, project_id, conversation_id, id, pending_model_change_id0..1:1 一对一(父可选)DB 外键Agent 交互 → Agent 交互
studio_conversationsmaa_bindingsowner_id, project_id, conversation_id1:1 一对一DB 外键项目与会话 → Agent 交互
maa_interactionsmaa_interaction_actionsowner_id, project_id, conversation_id, binding_id, public_run_id, interaction_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_runsmaa_interactionsowner_id, project_id, conversation_id, binding_id, public_run_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_message_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_message_actionsowner_id, project_id, conversation_id, binding_id, public_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_model_change_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_run_stop_actionsowner_id, project_id, conversation_id, binding_id, public_run_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_runsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
studio_usersstudio_assetsowner_id1:N 一对多DB 外键用户与登录 → 素材
studio_payment_ordersstudio_billing_cyclesowner_id, payment_order_id, plan_version_id1:1 一对一DB 外键支付与订阅 → 积分账本
studio_subscriptionsstudio_billing_cyclesowner_id, subscription_id, provider_key, provider_account1:N 一对多DB 外键支付与订阅 → 积分账本
studio_canvas_revisionsstudio_canvas_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_canvasesstudio_canvas_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_canvasesowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_projectsstudio_conversationsowner_id, project_id1:N 一对多DB 外键项目与会话 → 项目与会话
studio_usersstudio_credit_accountsowner_id1:1 一对一DB 外键用户与登录 → 积分账本
studio_credit_authorizationsstudio_credit_authorization_allocationsowner_id, authorization_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_authorization_allocationsowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_authorizationsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_authorization_allocationsstudio_credit_ledger_entriesowner_id, authorization_id, lot_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_authorizationsstudio_credit_ledger_entriesowner_id, authorization_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_ledger_entriesowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_billing_cyclesstudio_credit_lotsowner_id, source_cycle_id0..1:1 一对一(父可选)DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_lotsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_payment_ordersstudio_credit_lotsowner_id, source_order_id0..1:1 一对一(父可选)DB 外键支付与订阅 → 积分账本
studio_credit_authorizationsstudio_credit_recovery_intentsauthorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_jobsstudio_credit_recovery_intentsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_projectsstudio_export_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_credit_authorizationsstudio_media_jobscredit_authorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_requestsstudio_media_jobsowner_id, project_id, conversation_id, request_id, request_sha256, quote_sha2561:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_model_versionsmodel_id1:N 一对多DB 外键模型目录 → 模型目录
studio_media_model_versionsstudio_media_modelsid, published_version_id0..1:1 一对一(父可选)DB 外键模型目录 → 模型目录
studio_assetsstudio_media_outputsasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_outputsowner_id, project_id, conversation_id, job_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_runsstudio_media_requestsowner_id, project_id, conversation_id, source_binding_id, source_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → 生成 · 媒体 · 交付
studio_conversationsstudio_media_requestsowner_id, project_id, conversation_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_media_model_versionsstudio_media_requestsmodel_version_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_requestsmodel_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_submission_attemptsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_payment_ordersstudio_payment_eventsowner_id, order_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_subscriptionsstudio_payment_eventsowner_id, subscription_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_usersstudio_payment_eventsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_credit_plan_versionsstudio_payment_ordersplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_payment_ordersowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_assetsstudio_project_asset_refsasset_id1:N 一对多跨库(应用层检查)素材 → 画布 · 时间轴 · 导出
studio_projectsstudio_project_asset_refsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_usersstudio_sessionsuser_id1:N 一对多DB 外键用户与登录 → 用户与登录
studio_credit_plan_versionsstudio_subscriptionsplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_subscriptionsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_timeline_revisionsstudio_timeline_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_video_documentsstudio_timeline_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_video_documentsowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
完整 ER:39 张表的全部列,带主键 / 外键与基数符号(鸦脚)
erDiagram
  studio_assets {
    CHAR id PK
    CHAR owner_id FK
    CHAR sha256
    JSON metadata_json
    VARCHAR storage_key
    DATETIME created_at
    VARCHAR publication_state
  }
  studio_billing_cycles {
    CHAR id PK
    CHAR owner_id FK
    CHAR subscription_id FK
    CHAR plan_version_id FK
    CHAR payment_order_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_invoice_id
    DATETIME period_start
    DATETIME period_end
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_accounts {
    CHAR owner_id PK
    VARCHAR unit
    VARCHAR status
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_credit_authorization_allocations {
    CHAR owner_id FK
    CHAR authorization_id PK
    CHAR lot_id PK
    BIGINT_UNSIGNED allocated_credits
    DATETIME created_at
  }
  studio_credit_authorizations {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR profile
    CHAR project_id
    CHAR conversation_id
    CHAR job_id
    CHAR request_id
    CHAR request_sha256
    CHAR quote_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    MEDIUMBLOB reservation_bytes
    CHAR reservation_sha256
    MEDIUMBLOB policy_bytes
    CHAR policy_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_ledger_entries {
    CHAR id PK
    CHAR owner_id FK
    CHAR lot_id FK
    CHAR authorization_id FK
    VARCHAR operation_key
    VARCHAR kind
    BIGINT grant_delta
    BIGINT held_delta
    BIGINT captured_delta
    MEDIUMBLOB facts_bytes
    CHAR facts_sha256
    DATETIME created_at
  }
  studio_credit_lots {
    CHAR id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED grant_credits
    BIGINT_UNSIGNED held_credits
    BIGINT_UNSIGNED captured_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    CHAR source_order_id FK
    CHAR source_cycle_id FK
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_plan_versions {
    CHAR id PK
    VARCHAR plan_key
    VARCHAR version_key
    VARCHAR kind
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    INT_UNSIGNED credit_validity_seconds
    INT_UNSIGNED billing_interval_months
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    DATETIME published_at
    DATETIME retired_at
  }
  studio_media_model_versions {
    CHAR id PK
    CHAR model_id FK
    VARCHAR version_key
    MEDIUMBLOB config_bytes
    CHAR config_sha256
    DATETIME published_at
  }
  studio_media_models {
    CHAR id PK
    VARCHAR model_key
    VARCHAR kind
    VARCHAR display_name
    VARCHAR status
    CHAR published_version_id FK
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_payment_events {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_event_id
    CHAR order_id FK
    CHAR subscription_id FK
    MEDIUMBLOB body_bytes
    CHAR body_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_payment_orders {
    CHAR id PK
    CHAR owner_id FK
    CHAR client_action_id
    CHAR plan_version_id FK
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_idempotency_key
    VARCHAR provider_order_id
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_sessions {
    CHAR token_hash PK
    CHAR user_id FK
    DATETIME created_at
    DATETIME expires_at
    DATETIME revoked_at
  }
  studio_subscriptions {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_subscription_id
    CHAR plan_version_id FK
    VARCHAR state
    DATETIME period_start
    DATETIME period_end
    TINYINT cancel_at_period_end
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_users {
    CHAR id PK
    VARCHAR email
    VARBINARY password_hash
    TINYINT enabled
    DATETIME created_at
  }
  generation_deliveries {
    CHAR job_id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR asset_id FK
    CHAR request_sha256
    CHAR maa_source_sha256
    CHAR provider_source_sha256
    VARCHAR model
    VARCHAR tool_name
    CHAR input_sha256
    JSON metadata_json
    DATETIME created_at
  }
  generation_development_budgets {
    CHAR id PK
    CHAR owner_id
    VARCHAR policy_version
    BIGINT_UNSIGNED credits_limit
    BIGINT_UNSIGNED credits_reserved
    INT_UNSIGNED calls_limit
    INT_UNSIGNED calls_reserved
    DATETIME expires_at
  }
  generation_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR native_conversation_id
    CHAR native_run_id
    CHAR source_sha256
    CHAR budget_id FK
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    VARCHAR status
    INT_UNSIGNED attempts
    MEDIUMBLOB result_bytes
    DATETIME updated_at
  }
  generation_reservations {
    CHAR job_id PK
    CHAR budget_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED credits
    INT_UNSIGNED calls
    VARCHAR status
  }
  maa_bindings {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR provider
    VARCHAR model_name
    VARCHAR status
    VARCHAR native_cid
    DATETIME created_at
    BIGINT_UNSIGNED model_revision
    CHAR pending_model_change_id FK
  }
  maa_interaction_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR interaction_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR client_action_id
    CHAR card_sha256
    CHAR request_sha256
    CHAR content_sha256
    VARCHAR response_action
    JSON values_payload
    VARCHAR status
    DATETIME created_at
  }
  maa_interactions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR card_sha256
    JSON card_payload
    DATETIME created_at
  }
  maa_message_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_action_id
    CHAR request_sha256
    MEDIUMTEXT content
    VARCHAR status
    CHAR public_run_id FK
    CHAR proposed_run_id
    DATETIME created_at
  }
  maa_model_change_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    CHAR client_action_id
    CHAR request_sha256
    CHAR native_request_sha256
    BIGINT_UNSIGNED expected_revision
    VARCHAR from_provider
    VARCHAR from_model_name
    VARCHAR target_provider
    VARCHAR target_model_name
    VARCHAR status
    BIGINT_UNSIGNED dispatch_attempt
    JSON receipt_payload
    DATETIME created_at
    DATETIME updated_at
  }
  maa_run_stop_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    VARCHAR native_rid
    CHAR client_action_id
    CHAR request_sha256
    VARCHAR stop_action
    VARCHAR status
    BIGINT_UNSIGNED attempt
    VARCHAR terminal_run_status
    DATETIME created_at
    DATETIME updated_at
  }
  maa_runs {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_rid
    VARCHAR status
    DATETIME created_at
    CHAR native_assistant_sha256
    CHAR public_assistant_sha256
    BIGINT_UNSIGNED native_assistant_bytes
    BIGINT_UNSIGNED assistant_event_seq
    BIGINT_UNSIGNED last_event_seq
  }
  studio_canvas_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_canvases {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_conversations {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    VARCHAR title
    DATETIME created_at
  }
  studio_credit_recovery_intents {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256
    VARCHAR kind
    CHAR authorization_id FK
    MEDIUMBLOB intent_bytes
    CHAR intent_sha256
    VARCHAR state
    CHAR lease_token_sha256
    DATETIME lease_until
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_export_jobs {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED document_revision
    CHAR input_hash
    VARCHAR status
    JSON input_json
    JSON output_json
    VARCHAR error_code
    CHAR lease_token_hash
    DATETIME lease_until
    INT_UNSIGNED attempts
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR request_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256 FK
    CHAR client_approval_id
    CHAR approval_sha256
    CHAR credit_authorization_id FK
    VARCHAR authorization_mode
    DATETIME authorization_expires_at
    VARCHAR status
    BIGINT_UNSIGNED revision
    CHAR lease_token_sha256
    DATETIME lease_until
    VARCHAR provider_task_id
    CHAR client_cancel_id
    CHAR cancel_request_sha256
    DATETIME cancel_requested_at
    CHAR staged_asset_id
    CHAR staged_asset_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_outputs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED ordinal
    CHAR asset_id FK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    CHAR provider_result_sha256
    DATETIME created_at
  }
  studio_media_requests {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_request_id
    CHAR model_id FK
    CHAR model_version_id FK
    CHAR model_config_sha256
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    MEDIUMBLOB quote_bytes
    CHAR quote_sha256
    BIGINT_UNSIGNED quoted_credits
    DATETIME expires_at
    VARCHAR origin
    CHAR source_binding_id FK
    CHAR source_run_id FK
    CHAR source_assistant_sha256
    MEDIUMBLOB source_snapshot_bytes
    DATETIME created_at
  }
  studio_media_submission_attempts {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED attempt_no
    CHAR request_sha256 FK
    VARCHAR provider_idempotency_key
    VARCHAR stage
    DATETIME dispatch_started_at
    VARCHAR provider_task_id
    CHAR receipt_sha256
    CHAR terminal_receipt_sha256
    CHAR reconciliation_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_project_asset_refs {
    CHAR project_id PK
    CHAR owner_id FK
    CHAR asset_id PK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
    DATETIME detached_at
  }
  studio_projects {
    CHAR id PK
    CHAR owner_id
    VARCHAR title
    DATETIME created_at
  }
  studio_timeline_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_video_documents {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_users ||--o{ studio_assets : "owner_id"
  studio_payment_orders ||--o| studio_billing_cycles : "plan_version_id"
  studio_subscriptions ||--o{ studio_billing_cycles : "provider_account"
  studio_users ||--o| studio_credit_accounts : "owner_id"
  studio_credit_authorizations ||--o{ studio_credit_authorization_allocations : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_authorization_allocations : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_authorizations : "owner_id"
  studio_credit_authorization_allocations |o--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_authorizations |o--o{ studio_credit_ledger_entries : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_lots : "owner_id"
  studio_billing_cycles |o--o| studio_credit_lots : "source_cycle_id"
  studio_payment_orders |o--o| studio_credit_lots : "source_order_id"
  studio_media_models ||--o{ studio_media_model_versions : "model_id"
  studio_media_model_versions |o--o| studio_media_models : "published_version_id"
  studio_payment_orders |o--o{ studio_payment_events : "provider_account"
  studio_subscriptions |o--o{ studio_payment_events : "provider_account"
  studio_users ||--o{ studio_payment_events : "owner_id"
  studio_credit_plan_versions ||--o{ studio_payment_orders : "plan_version_id"
  studio_users ||--o{ studio_payment_orders : "owner_id"
  studio_users ||--o{ studio_sessions : "user_id"
  studio_credit_plan_versions ||--o{ studio_subscriptions : "plan_version_id"
  studio_users ||--o{ studio_subscriptions : "owner_id"
  generation_jobs ||--o| generation_deliveries : "job_id"
  studio_projects ||--o{ generation_deliveries : "project_id"
  studio_conversations ||--o{ generation_deliveries : "session_id"
  generation_development_budgets ||--o{ generation_jobs : "budget_id"
  studio_projects ||--o{ generation_jobs : "project_id"
  studio_conversations ||--o{ generation_jobs : "session_id"
  generation_jobs ||--o| generation_reservations : "budget_id"
  studio_conversations ||--o| maa_bindings : "conversation_id"
  maa_model_change_actions |o--o| maa_bindings : "pending_model_change_id"
  maa_interactions ||--o| maa_interaction_actions : "interaction_id"
  maa_runs ||--o{ maa_interactions : "public_run_id"
  maa_bindings ||--o{ maa_message_actions : "binding_id"
  maa_runs |o--o{ maa_message_actions : "public_run_id"
  maa_bindings ||--o{ maa_model_change_actions : "binding_id"
  maa_runs ||--o| maa_run_stop_actions : "public_run_id"
  maa_bindings ||--o{ maa_runs : "binding_id"
  studio_canvases ||--o{ studio_canvas_revisions : "project_id"
  studio_canvas_revisions |o--o{ studio_canvas_revisions : "restored_from_revision"
  studio_projects ||--o| studio_canvases : "project_id"
  studio_projects ||--o{ studio_conversations : "project_id"
  studio_media_jobs ||--o{ studio_credit_recovery_intents : "job_id"
  studio_projects ||--o{ studio_export_jobs : "project_id"
  studio_media_requests ||--o| studio_media_jobs : "request_id"
  studio_media_jobs ||--o{ studio_media_outputs : "job_id"
  studio_conversations ||--o{ studio_media_requests : "conversation_id"
  maa_runs |o--o{ studio_media_requests : "source_run_id"
  studio_media_jobs ||--o{ studio_media_submission_attempts : "job_id"
  studio_projects ||--o{ studio_project_asset_refs : "project_id"
  studio_video_documents ||--o{ studio_timeline_revisions : "project_id"
  studio_timeline_revisions |o--o{ studio_timeline_revisions : "restored_from_revision"
  studio_projects ||--o| studio_video_documents : "project_id"
  studio_assets ||--o{ studio_project_asset_refs : "asset_id"
  studio_assets ||--o{ studio_media_outputs : "asset_id"
  studio_assets ||--o{ generation_deliveries : "asset_id"
  studio_media_model_versions ||--o{ studio_media_requests : "model_version_id"
  studio_media_models ||--o{ studio_media_requests : "model_id"
  studio_credit_authorizations |o--o{ studio_media_jobs : "credit_authorization_id"
  studio_credit_authorizations |o--o{ studio_credit_recovery_intents : "authorization_id"
自动生成的整体图(按真实表结构,备查)
Pollo 品牌库 ER 图

共享身份库 ER 图

共享身份库 ER 图
实测验证与设计原因 七个品牌相同,展开查看

实测验证(本机 MySQL 实库)

对照本机真实运行的数据库 studio_profile_pollo 和 studio_platform_identity,逐项核对本页的表、列、关系与说明。14 / 14 项通过。

查看全部核对项
核对项结果
表数 = 发布值 24通过
列数 = 289通过
外键数 = 34通过
检查约束数 = 66通过
触发器数 = 22通过
索引数(含主键) = 85通过
外键 CSV 行数 = 实库外键数通过
mmd 实体 = 实库表通过
svg 标题 24 tables / 34 actual foreign keys = 实库通过
参考 SQL 表集合与列顺序 = 实库通过
15 张表通过
外键 CSV 行数 = 实库外键数通过
svg 标题与实库一致通过
参考 SQL 表集合与列顺序 = 实库通过

实库里的关系是这样的

  • 34 个外键里 31 个是复合外键,且都带着 owner_id,其余 3 个是单列外键;22 张子表都带 owner 外键。所以一行数据不可能指向别的用户的项目或会话。
  • 删除与更新规则全部是 RESTRICT / NO ACTION,没有任何级联:被引用的行不能被删除。
  • 品牌库与共享身份库之间的关系(owner_id、asset_id、model_id + model_version_id)没有数据库外键,靠应用层检查;我们逐表核对了现有数据,没有孤儿。

主动破坏测试(在事务里尝试,全部回滚)

尝试数据库的反应
外键:导出任务.project_id 改成不存在的项目被拒绝拒绝
复合外键:项目素材引用.project_id 改成别的用户的项目被拒绝(owner 与 project 必须成对)拒绝
触发器:已提交的消息动作不可改写拒绝
触发器:媒体产出不可改写拒绝
外键 RESTRICT:被会话引用的项目不能删除拒绝
复合外键:被引用的会话不能把 owner 改成别人拒绝
检查约束:Run 状态只能是约定的六个值拒绝
触发器:账本分录不可修改拒绝
触发器:账本分录不可删除拒绝
验证范围的说明:这几个库里的数据量很小(每个品牌库约 70–270 行),孤儿检查能证明现有数据没有违反关系,不能证明大数据量下的表现;主动破坏测试在 Pollo 库上执行,另外六个品牌库的列、外键、索引、触发器已逐项核对与 Pollo 完全一致。
为什么这样设计外键用真实数据库约束表达,复合外键把 owner/project/conversation 一起带上,所以任何一行数据都不可能指向别人的项目。跨库关系不画外键而用应用层检查,是因为不同库之间数据库本身保证不了,画出来反而误导(决策 D12、D17)。

Pollo · 表结构

MySQL 8,字符集 utf8mb4,ID 为 char(36) ascii_bin。点击展开 DDL。

按职责分组

项目与会话 2 张
  • studio_projects:用户拥有的创作项目
  • studio_conversations:项目内的对话会话
Agent执行与并发控制 7 张
  • maa_bindings:业务会话与Native Maa会话映射、模型版本与未决操作
  • maa_runs:公开Run映射、状态与原生/公开助手证据摘要
  • maa_message_actions:原消息动作及幂等回执
  • maa_interactions:澄清卡片及Run归属
  • maa_interaction_actions:原卡片回答/取消动作
  • maa_model_change_actions:带预期模型版本的切换动作
  • maa_run_stop_actions:停止原Run的幂等动作与观测终态
画布、时间轴与素材引用 5 张
  • studio_canvases:项目当前画布文档
  • studio_canvas_revisions:历史画布修订
  • studio_video_documents:项目当前时间轴文档
  • studio_timeline_revisions:历史时间轴修订
  • studio_project_asset_refs:项目对用户级素材的引用;不复制素材所有权
媒体生成、结算恢复与导出 6 张
  • studio_media_requests:用户输入/Agent来源、报价、批准摘要与准入确认
  • studio_media_jobs:已批准媒体任务及状态机
  • studio_media_submission_attempts:一次提交的任务证据与不确定结果
  • studio_media_outputs:受验证的输出与素材映射
  • studio_credit_recovery_intents:积分结算恢复意图;不是用户钱包全账本
  • studio_export_jobs:时间轴导出队列和发布输出
既有generation兼容路径 22 张
  • generation_jobs:既有generation任务,不等同新PublicMedia任务
  • generation_reservations:既有任务费用预留
  • generation_development_budgets:既有开发测试预算
  • generation_deliveries:既有生成交付回执
  • owner_id:归属用户;不是允许跨用户查询的参数。
  • model_revision:并发准入版本;数据库默认0不能代替Native权威读回。
  • pending_model_change_id:未决模型切换引用,阻止其他writer绕过。
  • studio_users:用户账号记录;不导出密码哈希或任何业务行
  • studio_sessions:登录会话;这里只公开字段结构,不公开会话值
  • studio_assets:用户级素材元数据与存储键结构,项目仅引用素材ID
  • studio_media_models:公开媒体模型目录结构
  • studio_media_model_versions:媒体模型不可变版本/计价能力结构
  • studio_subscriptions:订阅状态持久化
  • studio_billing_cycles:订阅账期
  • studio_payment_orders:订单记录
  • studio_payment_events:支付事件幂等记录;不代表已有公开支付路由
  • studio_credit_accounts:用户积分账户
  • studio_credit_lots:积分批次
  • studio_credit_authorizations:费用预留与结算授权
  • studio_credit_authorization_allocations:授权对批次的分配
  • studio_credit_ledger_entries:积分流水
  • studio_credit_plan_versions:积分方案不可变版本

品牌库 studio_profile_pollo(24 张)

generation_deliveries 既有生成交付回执
CREATE TABLE `generation_deliveries` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `maa_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `tool_name` varchar(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_delivery_owner_project` (`owner_id`,`project_id`,`session_id`,`job_id`),
  KEY `generation_delivery_owned_asset` (`owner_id`,`asset_id`),
  KEY `generation_delivery_session_fk` (`session_id`),
  CONSTRAINT `generation_delivery_job_fk` FOREIGN KEY (`job_id`) REFERENCES `generation_jobs` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`, `job_id`) REFERENCES `generation_jobs` (`owner_id`, `project_id`, `session_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_session_fk` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_model_tool` CHECK ((((`model` = _ascii'seedance-2.0') and (`tool_name` = _ascii'seedance2_video_generator')) or ((`model` in (_ascii'minimax-h3',_ascii'seedance-2.5')) and (`tool_name` = _ascii'insai_video_generate'))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_development_budgets 既有开发测试预算
CREATE TABLE `generation_development_budgets` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_version` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits_limit` bigint unsigned NOT NULL,
  `credits_reserved` bigint unsigned NOT NULL DEFAULT '0',
  `calls_limit` int unsigned NOT NULL,
  `calls_reserved` int unsigned NOT NULL DEFAULT '0',
  `expires_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_budget_owner` (`owner_id`,`id`),
  CONSTRAINT `generation_budget_limits` CHECK (((`credits_limit` between 1 and 1000000000) and (`credits_reserved` <= `credits_limit`) and (`calls_limit` between 1 and 100) and (`calls_reserved` <= `calls_limit`))),
  CONSTRAINT `generation_budget_policy` CHECK ((`policy_version` = _latin1'operator-test/v1'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_jobs 既有generation任务,不等同新PublicMedia任务
CREATE TABLE `generation_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_conversation_id` char(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_run_id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `result_bytes` mediumblob,
  `updated_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_job_budget_owner` (`id`,`budget_id`,`owner_id`),
  UNIQUE KEY `generation_job_owned_scope` (`owner_id`,`project_id`,`session_id`,`id`),
  KEY `generation_job_owner` (`owner_id`,`project_id`,`session_id`),
  KEY `generation_job_session` (`session_id`),
  KEY `generation_job_budget` (`owner_id`,`budget_id`),
  CONSTRAINT `generation_job_budget` FOREIGN KEY (`owner_id`, `budget_id`) REFERENCES `generation_development_budgets` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_project` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_session` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_attempt` CHECK ((((`status` = _ascii'APPROVED') and (`attempts` = 0) and (`result_bytes` is null)) or ((`status` = _ascii'SUBMITTING') and (`attempts` = 1) and (`result_bytes` is null)) or ((`status` in (_ascii'SUCCEEDED',_ascii'FAILED',_ascii'UNKNOWN')) and (`attempts` = 1) and (`result_bytes` is not null)))),
  CONSTRAINT `generation_job_credit` CHECK ((`credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_reservations 既有任务费用预留
CREATE TABLE `generation_reservations` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `calls` int unsigned NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_reservation_job` (`job_id`,`budget_id`,`owner_id`),
  CONSTRAINT `generation_reservation_job` FOREIGN KEY (`job_id`, `budget_id`, `owner_id`) REFERENCES `generation_jobs` (`id`, `budget_id`, `owner_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_reservation_fixed` CHECK (((`credits` between 1 and 1000000000) and (`calls` = 1) and (`status` = _latin1'HELD')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_bindings 业务会话与Native Maa会话映射、模型版本与未决操作
CREATE TABLE `maa_bindings` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `model_revision` bigint unsigned NOT NULL DEFAULT '0',
  `pending_model_change_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_binding_business_scope` (`owner_id`,`project_id`,`conversation_id`),
  UNIQUE KEY `maa_binding_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `maa_binding_native_identity` (`native_cid`),
  KEY `maa_model_change_pending_fk` (`owner_id`,`project_id`,`conversation_id`,`id`,`pending_model_change_id`),
  CONSTRAINT `maa_binding_owned_conversation_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_pending_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `id`, `pending_model_change_id`) REFERENCES `maa_model_change_actions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_binding_receipt` CHECK ((((`status` = _ascii'dispatching') and (`native_cid` is null)) or ((`status` = _ascii'bound') and (`native_cid` is not null)))),
  CONSTRAINT `maa_model_binding_revision` CHECK ((`model_revision` <= 9007199254740991))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interaction_actions 原卡片回答/取消动作
CREATE TABLE `maa_interaction_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `interaction_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `response_action` varchar(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `values_payload` json NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_action_business_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_interaction_action_one_card` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`interaction_id`),
  CONSTRAINT `maa_interaction_action_owned_card_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `interaction_id`) REFERENCES `maa_interactions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_interaction_action_kind` CHECK ((`response_action` in (_latin1'submit',_latin1'cancel'))),
  CONSTRAINT `maa_interaction_action_status` CHECK ((`status` in (_latin1'dispatching',_latin1'accepted')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interactions 澄清卡片及Run归属
CREATE TABLE `maa_interactions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_payload` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_native_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`native_iid`),
  UNIQUE KEY `maa_interaction_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`id`),
  CONSTRAINT `maa_interaction_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_message_actions 原消息动作及幂等回执
CREATE TABLE `maa_message_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `proposed_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_action_business_identity` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  KEY `maa_action_owned_run_fk` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_action_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_receipt` CHECK ((((`status` = _latin1'dispatching') and (`public_run_id` is null)) or ((`status` = _latin1'accepted') and (`public_run_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_model_change_actions 带预期模型版本的切换动作
CREATE TABLE `maa_model_change_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `expected_revision` bigint unsigned NOT NULL,
  `from_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `from_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_attempt` bigint unsigned NOT NULL,
  `receipt_payload` json DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_model_change_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_model_change_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  CONSTRAINT `maa_model_change_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_attempt` CHECK ((`dispatch_attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_model_change_expected` CHECK ((`expected_revision` <= 9007199254740990)),
  CONSTRAINT `maa_model_change_status` CHECK ((((cast(`status` as char charset binary) = _utf8mb4'dispatching') and (`receipt_payload` is null)) or ((cast(`status` as char charset binary) in (_utf8mb4'accepted',_utf8mb4'rejected')) and (`receipt_payload` is not null) and (json_type(`receipt_payload`) = _utf8mb4'OBJECT')))),
  CONSTRAINT `maa_model_change_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_run_stop_actions 停止原Run的幂等动作与观测终态
CREATE TABLE `maa_run_stop_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stop_action` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt` bigint unsigned NOT NULL,
  `terminal_run_status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_stop_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_stop_one_run` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_stop_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_stop_action` CHECK ((`stop_action` = _utf8mb4'interrupt')),
  CONSTRAINT `maa_stop_attempt` CHECK ((`attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_stop_state` CHECK ((((`status` = _utf8mb4'unknown') and (`terminal_run_status` is null)) or ((`status` = _utf8mb4'terminal_observed') and (`terminal_run_status` is not null) and (`terminal_run_status` in (_utf8mb4'completed',_utf8mb4'failed',_utf8mb4'cancelled'))))),
  CONSTRAINT `maa_stop_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_runs 公开Run映射、状态与原生/公开助手证据摘要
CREATE TABLE `maa_runs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `native_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `public_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `native_assistant_bytes` bigint unsigned NOT NULL DEFAULT '0',
  `assistant_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  `last_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_run_native_identity` (`native_rid`),
  UNIQUE KEY `maa_run_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  KEY `maa_run_owned_list` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  CONSTRAINT `maa_run_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_run_source_evidence` CHECK ((((`native_assistant_sha256` is null) and (`public_assistant_sha256` is null) and (`native_assistant_bytes` = 0) and (`assistant_event_seq` = 0)) or ((`native_assistant_sha256` is not null) and (`public_assistant_sha256` is not null) and (`native_assistant_bytes` > 0) and (`assistant_event_seq` > 0) and (`assistant_event_seq` <= `last_event_seq`)))),
  CONSTRAINT `maa_run_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'waiting_interaction',_latin1'completed',_latin1'failed',_latin1'cancelled')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvas_revisions 历史画布修订
CREATE TABLE `studio_canvas_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_canvas_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_canvas_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_canvases` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_canvas_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_canvas_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_canvas_revision_kind` CHECK ((`kind` = _latin1'canvas')),
  CONSTRAINT `studio_canvas_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_canvas_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_canvas_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvases 项目当前画布文档
CREATE TABLE `studio_canvases` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_canvas_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_canvas_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_conversations 项目内的对话会话
CREATE TABLE `studio_conversations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_conversation_owned_scope` (`owner_id`,`project_id`,`id`),
  KEY `studio_conversation_list` (`owner_id`,`project_id`,`created_at`,`id`),
  CONSTRAINT `studio_conversation_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_recovery_intents 积分结算恢复意图;不是用户钱包全账本
CREATE TABLE `studio_credit_recovery_intents` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `intent_bytes` mediumblob NOT NULL,
  `intent_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_intent_job_kind` (`job_id`,`kind`),
  KEY `credit_intent_recovery` (`state`,`updated_at`,`id`),
  KEY `credit_intent_owned_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `credit_intent_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `credit_intent_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_intent_kind` CHECK (((`kind` in (_latin1'RESERVE_BIND',_latin1'CAPTURE',_latin1'RELEASE')) and (((`kind` = _latin1'RESERVE_BIND') and (`authorization_id` is null)) or ((`kind` in (_latin1'CAPTURE',_latin1'RELEASE')) and (`authorization_id` is not null))))),
  CONSTRAINT `credit_intent_lease` CHECK ((((`state` = _latin1'leased') and (`lease_token_sha256` is not null) and regexp_like(`lease_token_sha256`,_latin1'^[a-f0-9]{64}$') and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`state` <> _latin1'leased') and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `credit_intent_payload_sha` CHECK ((`intent_sha256` = lower(sha2(`intent_bytes`,256)))),
  CONSTRAINT `credit_intent_payload_size` CHECK ((length(`intent_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_intent_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_intent_state` CHECK ((`state` in (_latin1'pending',_latin1'leased',_latin1'confirmed',_latin1'unknown'))),
  CONSTRAINT `credit_intent_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_export_jobs 时间轴导出队列和发布输出
CREATE TABLE `studio_export_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `document_revision` bigint unsigned NOT NULL,
  `input_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_json` json NOT NULL,
  `output_json` json DEFAULT NULL,
  `error_code` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
  `lease_token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_export_input` (`project_id`,`document_revision`,`input_hash`),
  KEY `studio_export_claim` (`status`,`created_at`,`id`),
  KEY `studio_export_owner_list` (`owner_id`,`project_id`,`created_at`,`id`),
  KEY `studio_export_expiry` (`status`,`lease_until`),
  CONSTRAINT `studio_export_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_export_lease` CHECK ((((`status` = _latin1'running') and (`lease_token_hash` is not null) and (`lease_until` is not null)) or ((`status` <> _latin1'running') and (`lease_token_hash` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_export_output` CHECK ((((`status` = _latin1'succeeded') and (`output_json` is not null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'queued',_latin1'running')) and (`output_json` is null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'failed',_latin1'unknown')) and (`output_json` is null) and (`error_code` <> _latin1'')))),
  CONSTRAINT `studio_export_revision` CHECK ((`document_revision` between 1 and 2147483647)),
  CONSTRAINT `studio_export_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'succeeded',_latin1'failed',_latin1'unknown')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_jobs 已批准媒体任务及状态机
CREATE TABLE `studio_media_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_approval_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `approval_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credit_authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_mode` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_expires_at` datetime(6) DEFAULT NULL,
  `status` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `client_cancel_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_requested_at` datetime(6) DEFAULT NULL,
  `staged_asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `staged_asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_job_request` (`request_id`),
  UNIQUE KEY `studio_media_job_client` (`owner_id`,`client_approval_id`),
  UNIQUE KEY `studio_media_job_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_job_owned_request` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`),
  KEY `studio_media_job_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_job_queue` (`status`,`updated_at`,`id`),
  KEY `studio_media_job_request_fk` (`owner_id`,`project_id`,`conversation_id`,`request_id`,`request_sha256`,`quote_sha256`),
  CONSTRAINT `studio_media_job_request_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `request_id`, `request_sha256`, `quote_sha256`) REFERENCES `studio_media_requests` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`, `quote_sha256`),
  CONSTRAINT `studio_media_job_authorization` CHECK ((((`credit_authorization_id` is null) and (`authorization_mode` is null) and (`authorization_expires_at` is null) and (`status` in (_latin1'approval_pending',_latin1'cancelled'))) or ((`credit_authorization_id` is not null) and (`authorization_mode` is not null) and (`authorization_mode` in (_latin1'commercial',_latin1'test_allowance')) and (`authorization_expires_at` is not null) and (`authorization_expires_at` > `created_at`) and (`status` <> _latin1'approval_pending')))),
  CONSTRAINT `studio_media_job_cancel` CHECK ((((`client_cancel_id` is null) and (`cancel_request_sha256` is null) and (`cancel_requested_at` is null)) or ((`client_cancel_id` is not null) and (`cancel_request_sha256` is not null) and (`cancel_requested_at` is not null) and (`cancel_requested_at` between `created_at` and `updated_at`)))),
  CONSTRAINT `studio_media_job_lease` CHECK ((((`status` in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is not null) and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`status` not in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_media_job_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_job_staged` CHECK ((((`staged_asset_id` is null) and (`staged_asset_sha256` is null)) or ((`staged_asset_id` is not null) and (`staged_asset_sha256` is not null)))),
  CONSTRAINT `studio_media_job_status` CHECK ((`status` in (_latin1'approval_pending',_latin1'queued',_latin1'leased',_latin1'submitting',_latin1'running',_latin1'submission_unknown',_latin1'succeeded',_latin1'failed',_latin1'cancelled'))),
  CONSTRAINT `studio_media_job_task` CHECK (((`status` not in (_latin1'running',_latin1'succeeded')) or (`provider_task_id` is not null))),
  CONSTRAINT `studio_media_job_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_outputs 受验证的输出与素材映射
CREATE TABLE `studio_media_outputs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `ordinal` int unsigned NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_result_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_output_job` (`job_id`,`ordinal`),
  KEY `studio_media_output_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`),
  CONSTRAINT `studio_media_output_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`),
  CONSTRAINT `studio_media_output_ordinal` CHECK ((`ordinal` = 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_requests 用户输入/Agent来源、报价、批准摘要与准入确认
CREATE TABLE `studio_media_requests` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_bytes` mediumblob NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quoted_credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_snapshot_bytes` mediumblob,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_request_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_request_quoted` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`,`quote_sha256`),
  UNIQUE KEY `studio_media_request_client` (`owner_id`,`project_id`,`conversation_id`,`client_request_id`),
  KEY `studio_media_request_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_request_source_fk` (`owner_id`,`project_id`,`conversation_id`,`source_binding_id`,`source_run_id`),
  CONSTRAINT `studio_media_request_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`),
  CONSTRAINT `studio_media_request_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `source_binding_id`, `source_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`),
  CONSTRAINT `studio_media_quote_credits` CHECK ((`quoted_credits` between 1 and 1000000000)),
  CONSTRAINT `studio_media_quote_sha` CHECK ((`quote_sha256` = lower(sha2(`quote_bytes`,256)))),
  CONSTRAINT `studio_media_quote_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)))),
  CONSTRAINT `studio_media_request_origin` CHECK ((((`origin` in (_latin1'ui',_latin1'user_edit')) and (`source_binding_id` is null) and (`source_run_id` is null) and (`source_assistant_sha256` is null) and (`source_snapshot_bytes` is null)) or ((`origin` = _latin1'agent') and (`source_binding_id` is not null) and (`source_run_id` is not null) and (`source_assistant_sha256` is not null) and (`source_snapshot_bytes` is not null) and (length(`source_snapshot_bytes`) between 1 and 65536) and (`source_assistant_sha256` = lower(sha2(`source_snapshot_bytes`,256)))))),
  CONSTRAINT `studio_media_request_sha` CHECK ((`request_sha256` = lower(sha2(`request_bytes`,256)))),
  CONSTRAINT `studio_media_request_size` CHECK (((length(`request_bytes`) between 1 and 65536) and (length(`quote_bytes`) between 1 and 65536)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_submission_attempts 一次提交的任务证据与不确定结果
CREATE TABLE `studio_media_submission_attempts` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt_no` int unsigned NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stage` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_started_at` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `terminal_receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `reconciliation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_attempt_job` (`job_id`,`attempt_no`),
  UNIQUE KEY `studio_media_attempt_idempotency` (`provider_idempotency_key`),
  KEY `studio_media_attempt_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `studio_media_attempt_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `studio_media_attempt_number` CHECK ((`attempt_no` = 1)),
  CONSTRAINT `studio_media_attempt_state` CHECK ((((`stage` = _latin1'prepared') and (`dispatch_started_at` is null) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'dispatching',_latin1'unknown')) and (`dispatch_started_at` is not null) and (`dispatch_started_at` >= `created_at`) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` = _latin1'acknowledged') and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'completed',_latin1'failed')) and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is not null)) or ((`stage` = _latin1'reconciled_not_sent') and (`dispatch_started_at` is not null) and (`provider_task_id` is null) and (`reconciliation_sha256` is not null)))),
  CONSTRAINT `studio_media_attempt_times` CHECK (((`updated_at` >= `created_at`) and ((`dispatch_started_at` is null) or (`dispatch_started_at` between `created_at` and `updated_at`))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_project_asset_refs 项目对用户级素材的引用;不复制素材所有权
CREATE TABLE `studio_project_asset_refs` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `detached_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`project_id`,`asset_id`),
  KEY `studio_project_asset_owned_page` (`owner_id`,`project_id`,`asset_id`),
  KEY `studio_project_asset_reverse` (`owner_id`,`asset_id`,`project_id`),
  CONSTRAINT `studio_project_asset_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_project_asset_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_project_asset_state` CHECK ((((`state` = _latin1'attached') and (`detached_at` is null)) or ((`state` = _latin1'detached') and (`detached_at` is not null) and (`detached_at` = `updated_at`)))),
  CONSTRAINT `studio_project_asset_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_projects 用户拥有的创作项目
CREATE TABLE `studio_projects` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_project_owner_identity` (`owner_id`,`id`),
  KEY `studio_project_owner_list` (`owner_id`,`created_at`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_timeline_revisions 历史时间轴修订
CREATE TABLE `studio_timeline_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_timeline_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_timeline_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_video_documents` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_timeline_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_timeline_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_timeline_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_timeline_revision_kind` CHECK ((`kind` = _latin1'timeline')),
  CONSTRAINT `studio_timeline_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_timeline_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_timeline_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_video_documents 项目当前时间轴文档
CREATE TABLE `studio_video_documents` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_timeline_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_document_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_document_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
共享身份库(15 张)与积分账本 七个品牌相同,展开查看
studio_assets
CREATE TABLE `studio_assets` (
  `id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `storage_key` varchar(512) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `publication_state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT 'visible',
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_asset_owner_digest` (`owner_id`,`sha256`),
  KEY `studio_asset_owner_list` (`owner_id`,`created_at`,`id`),
  KEY `studio_asset_visible_list` (`owner_id`,`publication_state`,`created_at`,`id`),
  CONSTRAINT `studio_asset_publication_state` CHECK (CAST(publication_state AS BINARY) IN ('pending','visible')),
  CONSTRAINT `studio_asset_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_billing_cycles
CREATE TABLE `studio_billing_cycles` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `payment_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_invoice_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `billing_cycle_owned` (`owner_id`,`id`),
  UNIQUE KEY `billing_cycle_period` (`subscription_id`,`period_start`),
  UNIQUE KEY `billing_cycle_invoice` (`provider_key`,`provider_account`,`provider_invoice_id`),
  UNIQUE KEY `billing_cycle_order` (`payment_order_id`),
  KEY `billing_cycle_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  KEY `billing_cycle_order_fk` (`owner_id`,`payment_order_id`,`plan_version_id`),
  KEY `billing_cycle_order_provider_fk` (`owner_id`,`payment_order_id`,`provider_key`,`provider_account`),
  CONSTRAINT `billing_cycle_order_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `plan_version_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `plan_version_id`),
  CONSTRAINT `billing_cycle_order_provider_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_amount` CHECK ((`grant_credits` between 1 and 1000000000)),
  CONSTRAINT `billing_cycle_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `billing_cycle_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `billing_cycle_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `billing_cycle_state` CHECK (((`state` in (_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `billing_cycle_times` CHECK (((`period_end` > `period_start`) and (`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_accounts
CREATE TABLE `studio_credit_accounts` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `unit` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`),
  CONSTRAINT `credit_account_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `credit_account_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_account_status` CHECK ((`status` in (_latin1'open',_latin1'suspended'))),
  CONSTRAINT `credit_account_unit` CHECK ((`unit` = _latin1'credits'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorization_allocations
CREATE TABLE `studio_credit_authorization_allocations` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `allocated_credits` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`authorization_id`,`lot_id`),
  UNIQUE KEY `credit_allocation_owned` (`owner_id`,`authorization_id`,`lot_id`),
  KEY `credit_allocation_lot` (`owner_id`,`lot_id`,`authorization_id`),
  CONSTRAINT `credit_allocation_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_amount` CHECK ((`allocated_credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorizations
CREATE TABLE `studio_credit_authorizations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `profile` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `reservation_bytes` mediumblob NOT NULL,
  `reservation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_bytes` mediumblob NOT NULL,
  `policy_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_authorization_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_authorization_job` (`owner_id`,`profile`,`job_id`),
  CONSTRAINT `credit_authorization_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_authorization_amount` CHECK ((`credits` between 1 and 1000000000)),
  CONSTRAINT `credit_authorization_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_authorization_policy_sha` CHECK ((`policy_sha256` = lower(sha2(`policy_bytes`,256)))),
  CONSTRAINT `credit_authorization_policy_size` CHECK ((length(`policy_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_profile` CHECK ((`profile` in (_latin1'topview',_latin1'pollo',_latin1'invideo',_latin1'libtv',_latin1'flova',_latin1'creatok',_latin1'creatify'))),
  CONSTRAINT `credit_authorization_reservation_sha` CHECK ((`reservation_sha256` = lower(sha2(`reservation_bytes`,256)))),
  CONSTRAINT `credit_authorization_reservation_size` CHECK ((length(`reservation_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_authorization_state` CHECK ((`state` in (_latin1'HELD',_latin1'CAPTURED',_latin1'RELEASED'))),
  CONSTRAINT `credit_authorization_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_ledger_entries
CREATE TABLE `studio_credit_ledger_entries` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `operation_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_delta` bigint NOT NULL,
  `held_delta` bigint NOT NULL,
  `captured_delta` bigint NOT NULL,
  `facts_bytes` mediumblob NOT NULL,
  `facts_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_entry_operation` (`owner_id`,`operation_key`,`lot_id`),
  KEY `credit_entry_page` (`owner_id`,`created_at`,`id`),
  KEY `credit_entry_lot_fk` (`owner_id`,`lot_id`),
  KEY `credit_entry_allocation_fk` (`owner_id`,`authorization_id`,`lot_id`),
  CONSTRAINT `credit_entry_allocation_fk` FOREIGN KEY (`owner_id`, `authorization_id`, `lot_id`) REFERENCES `studio_credit_authorization_allocations` (`owner_id`, `authorization_id`, `lot_id`),
  CONSTRAINT `credit_entry_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_delta` CHECK ((((`kind` = _latin1'grant') and (`authorization_id` is null) and (`grant_delta` between 1 and 1000000000) and (`held_delta` = 0) and (`captured_delta` = 0)) or ((`kind` = _latin1'hold') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between 1 and 1000000000) and (`captured_delta` = 0)) or ((`kind` = _latin1'capture') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`captured_delta` between 1 and 1000000000) and (`held_delta` = -(`captured_delta`))) or ((`kind` = _latin1'release') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between -(1000000000) and -(1)) and (`captured_delta` = 0)))),
  CONSTRAINT `credit_entry_facts_sha` CHECK ((`facts_sha256` = lower(sha2(`facts_bytes`,256)))),
  CONSTRAINT `credit_entry_facts_size` CHECK ((length(`facts_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_entry_operation_key` CHECK ((((`kind` = _latin1'grant') and (`operation_key` = concat(`lot_id`,_latin1':grant'))) or ((`kind` <> _latin1'grant') and (`operation_key` = concat(`authorization_id`,_latin1':',`kind`)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_lots
CREATE TABLE `studio_credit_lots` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `held_credits` bigint unsigned NOT NULL,
  `captured_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_cycle_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_lot_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_lot_order` (`source_order_id`),
  UNIQUE KEY `credit_lot_cycle` (`source_cycle_id`),
  KEY `credit_lot_eligible` (`owner_id`,`expires_at`,`id`),
  KEY `credit_lot_order_fk` (`owner_id`,`source_order_id`),
  KEY `credit_lot_cycle_fk` (`owner_id`,`source_cycle_id`),
  CONSTRAINT `credit_lot_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_lot_cycle_fk` FOREIGN KEY (`owner_id`, `source_cycle_id`) REFERENCES `studio_billing_cycles` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_order_fk` FOREIGN KEY (`owner_id`, `source_order_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_counters` CHECK (((`grant_credits` between 1 and 1000000000) and (`held_credits` <= 1000000000) and (`captured_credits` <= 1000000000) and ((`held_credits` + `captured_credits`) <= `grant_credits`))),
  CONSTRAINT `credit_lot_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_lot_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_lot_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_lot_source` CHECK ((((`source_order_id` is not null) and (`source_cycle_id` is null)) or ((`source_order_id` is null) and (`source_cycle_id` is not null)))),
  CONSTRAINT `credit_lot_times` CHECK (((`expires_at` > `valid_from`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_plan_versions
CREATE TABLE `studio_credit_plan_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `credit_validity_seconds` int unsigned NOT NULL,
  `billing_interval_months` int unsigned NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  `retired_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_plan_version_key` (`plan_key`,`version_key`),
  CONSTRAINT `credit_plan_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `credit_plan_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `credit_plan_duration` CHECK (((`credit_validity_seconds` between 1 and 31622400) and ((`retired_at` is null) or (`retired_at` >= `published_at`)))),
  CONSTRAINT `credit_plan_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_plan_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_plan_kind` CHECK (((`kind` in (_latin1'subscription',_latin1'topup')) and (((`kind` = _latin1'subscription') and (`billing_interval_months` in (1,3,12))) or ((`kind` = _latin1'topup') and (`billing_interval_months` = 0)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_model_versions
CREATE TABLE `studio_media_model_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `config_bytes` mediumblob NOT NULL,
  `config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_version_key` (`model_id`,`version_key`),
  UNIQUE KEY `studio_media_version_owned_model` (`model_id`,`id`),
  CONSTRAINT `studio_media_version_model_fk` FOREIGN KEY (`model_id`) REFERENCES `studio_media_models` (`id`),
  CONSTRAINT `studio_media_version_sha` CHECK ((`config_sha256` = lower(sha2(`config_bytes`,256)))),
  CONSTRAINT `studio_media_version_size` CHECK ((length(`config_bytes`) between 1 and 65536))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_models
CREATE TABLE `studio_media_models` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `display_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_model_key` (`model_key`),
  KEY `studio_media_model_published_fk` (`id`,`published_version_id`),
  CONSTRAINT `studio_media_model_published_fk` FOREIGN KEY (`id`, `published_version_id`) REFERENCES `studio_media_model_versions` (`model_id`, `id`),
  CONSTRAINT `studio_media_model_kind` CHECK ((`kind` = _ascii'video')),
  CONSTRAINT `studio_media_model_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_model_status` CHECK (((`status` in (_ascii'draft',_ascii'active',_ascii'disabled')) and ((`status` <> _ascii'active') or (`published_version_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_events
CREATE TABLE `studio_payment_events` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_event_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `body_bytes` mediumblob NOT NULL,
  `body_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_event_provider` (`provider_key`,`provider_account`,`provider_event_id`),
  KEY `payment_event_order_fk` (`owner_id`,`order_id`,`provider_key`,`provider_account`),
  KEY `payment_event_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  CONSTRAINT `payment_event_order_fk` FOREIGN KEY (`owner_id`, `order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_event_body_sha` CHECK ((`body_sha256` = lower(sha2(`body_bytes`,256)))),
  CONSTRAINT `payment_event_body_size` CHECK ((length(`body_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_event_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_event_state` CHECK ((`state` in (_latin1'VERIFIED',_latin1'PROCESSED'))),
  CONSTRAINT `payment_event_subject` CHECK (((`order_id` is not null) or (`subscription_id` is not null))),
  CONSTRAINT `payment_event_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_orders
CREATE TABLE `studio_payment_orders` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_order_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_order_owned` (`owner_id`,`id`),
  UNIQUE KEY `payment_order_owned_plan` (`owner_id`,`id`,`plan_version_id`),
  UNIQUE KEY `payment_order_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `payment_order_client` (`owner_id`,`client_action_id`),
  UNIQUE KEY `payment_order_provider_key` (`provider_key`,`provider_account`,`provider_idempotency_key`),
  UNIQUE KEY `payment_order_provider_id` (`provider_key`,`provider_account`,`provider_order_id`),
  KEY `payment_order_plan_fk` (`plan_version_id`),
  CONSTRAINT `payment_order_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `payment_order_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_order_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `payment_order_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `payment_order_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `payment_order_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_order_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_order_state` CHECK (((`state` in (_latin1'CREATING',_latin1'CREATE_UNKNOWN',_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`provider_order_id` is not null) and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `payment_order_times` CHECK (((`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_sessions
CREATE TABLE `studio_sessions` (
  `token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `user_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `revoked_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`token_hash`),
  KEY `studio_session_expiry` (`expires_at`),
  KEY `studio_session_user` (`user_id`,`expires_at`),
  CONSTRAINT `studio_session_user_fk` FOREIGN KEY (`user_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_session_times` CHECK ((`expires_at` > `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_subscriptions
CREATE TABLE `studio_subscriptions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_subscription_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `cancel_at_period_end` tinyint(1) NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `subscription_owned` (`owner_id`,`id`),
  UNIQUE KEY `subscription_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `subscription_provider` (`provider_key`,`provider_account`,`provider_subscription_id`),
  KEY `subscription_plan_fk` (`plan_version_id`),
  CONSTRAINT `subscription_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `subscription_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `subscription_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `subscription_state` CHECK (((`state` in (_latin1'PENDING',_latin1'ACTIVE',_latin1'PAST_DUE',_latin1'CANCELLED',_latin1'ENDED')) and (`cancel_at_period_end` in (0,1)))),
  CONSTRAINT `subscription_times` CHECK (((`period_end` > `period_start`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_users
CREATE TABLE `studio_users` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `email` varchar(254) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `password_hash` varbinary(72) NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT '1',
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_user_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

积分账本链路

plan_versions → payment_orders(CREATING→PAID)→ payment_events(VERIFIED)→ credit_lots(带适用模型与品牌)→ ledger_entries(grant / hold / capture / release,只追加)→ authorizations / allocations。批准生成时按批次分配冻结额度,扣费与释放都有幂等键。
为什么这样设计账本只追加、用触发器禁止修改,才有可信的对账;批准时按批次分配冻结额度,才能在失败时准确释放。约束写在数据库里而不是应用里,是因为应用会有 bug,而约束不会(决策 D3、D17)。

Pollo · 接口清单

共 55 个公开接口,取自源码路由盘点。路径都以 /v1 开头;写操作需要会话 Cookie、可信 Host/Origin 与 CSRF;带品牌的路径用 ?profile=pollo;共享的登录、素材、模型目录路径不接受 profile 参数。

Agent/统一动作 7 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactionsrunInteractions200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactions/{interaction_id}/responsesrespondInteractionclient_action_id, card_sha256, action, values202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/interaction-actions/{client_action_id}interactionAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changeschangeConversationModelclient_action_id, expected_revision, agent_model, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changes/{client_action_id}modelChangeAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stopstopRunclient_action_id, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/stop-actions/{client_action_id}stopAction200
Asset 4 个
方法路径operationId请求字段成功
GET/v1/assetslistAssets200
POST/v1/assetsuploadAssetfile201
GET/v1/assets/{asset_id}getAsset200
GET/v1/assets/{asset_id}/contentassetContent200/206
LoginSession 1 个
方法路径operationId请求字段成功
POST/v1/auth/logoutlogout200
MaaBinding 5 个
方法路径operationId请求字段成功
GET/v1/maa/model-optionsmaaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/messagessendMaaMessageclient_action_id, content202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/actions/{client_action_id}getMaaAction200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stategetMaaState200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/eventsmaaEvents200
Media 11 个
方法路径operationId请求字段成功
GET/v1/media/modelspublicMediaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requestscreateMediaRequestclient_request_id, model_id, model_version_id, prompt, duration_seconds, ratio, resolution, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requestsmediaRequestByClient200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}getMediaRequest200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}getMediaJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}/outputgetMediaOutput200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/jobmediaJobByRequest200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/approveapproveMediaRequestclient_approval_id, request_sha256, quote_sha256200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/from-maacreateMediaRequestFromMaainput, source_run_id, source_assistant_sha256, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobslistMediaJobs200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/intentslistMediaIntents200
Other 9 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/canvas/revisionslistCanvasRevisions200
GET/v1/projects/{project_id}/canvas/revisions/{revision}getCanvasRevision200
POST/v1/projects/{project_id}/canvas/revisions/{revision}/restorerestoreCanvasRevisionexpected_revision200
GET/v1/projects/{project_id}/document/revisionslistTimelineRevisions200
GET/v1/projects/{project_id}/document/revisions/{revision}getTimelineRevision200
POST/v1/projects/{project_id}/document/revisions/{revision}/restorerestoreTimelineRevisionexpected_revision200
GET/v1/projects/{project_id}/assetslistProjectAssets200
POST/v1/projects/{project_id}/assetsattachProjectAssetasset_id, expected_revision200
POST/v1/projects/{project_id}/assets/{asset_id}/detachdetachProjectAssetexpected_revision200
Project 15 个
方法路径operationId请求字段成功
GET/v1/projectslistProjects200
POST/v1/projectscreateProjecttitle201
GET/v1/projects/{project_id}getProject200
GET/v1/projects/{project_id}/conversationslistConversations200
POST/v1/projects/{project_id}/conversationscreateConversationtitle201
GET/v1/projects/{project_id}/conversations/{conversation_id}getConversation200
GET/v1/projects/{project_id}/canvasgetCanvas200
POST/v1/projects/{project_id}/canvassaveCanvasexpected_revision, state200
GET/v1/projects/{project_id}/documentgetDocument200
POST/v1/projects/{project_id}/documentsaveDocumentexpected_revision, state200
GET/v1/projects/{project_id}/export-jobslistExportJobs200
POST/v1/projects/{project_id}/export-jobsqueueExportdocument_revision, output?202
GET/v1/projects/{project_id}/export-jobs/{job_id}getExportJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindinggetMaaBinding200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindingbindMaaagent_model XOR use_default:true200
User 3 个
方法路径operationId请求字段成功
POST/v1/auth/registerregisteremail, password201
POST/v1/auth/loginloginemail, password200
GET/v1/auth/sessionsession200

Pollo 自己公开的接口调用流程(来自其官方资料)

步骤调用关系操作证据
载入项目上下文浏览器 → 业务入口tRPC UI batch:userProject.getListV2、generationModel.list、userElement.list。O
读取项目会话浏览器 → Agent入口POST /api/agent-gateway/agent/v1/threads/search;metadata.project_id、limit、offset。O
创建长会话浏览器 → Agent入口POST /api/agent-gateway/agent/v1/threads;metadata:{project_id,idempotency_key} → data.thread_id。O
启动当前回合浏览器 → Agent入口POST /api/agent-gateway/agent/v1/threads/{thread_id}/runs/stream;input.messages、config.configurable、metadata、stream_mode;返回 text/event-stream。O
合并流式消息Agent入口 → 浏览器SSE metadata(run_id)、values、messages([MessageChunk,ExecutionMetadata])、end;每帧含 event/data/id。O
显示工具结果Agent入口 → 浏览器messages 中出现 message_notify_user:ToolCall.id == ToolResult.tool_call_id;status=success、artifact=[]。O
读回持久视图浏览器 → Agent入口end success 后:GET /api/agent-gateway/agent/v1/threads/{thread_id}/state;POST .../conversation;POST .../artifacts;GET .../suggestions?turn_id=...。O
核对商业视图浏览器 → 业务入口刷新首页可见积分,并读取既有 subLog.getSubLog / subUsage 订阅与用量投影;未取得完整私有 HTTP 路径。O
为什么这样设计所有写动作都带客户端幂等键(client_action_id / client_request_id / client_approval_id),重复提交返回原回执;报价和批准分成两个接口;停止、切换模型、澄清卡回答都是「动作」而不是「状态修改」,所以每一步都能查回执。这样前端可以放心地重试,后端也不会因为重试产生第二次副作用(决策 D2、D8、D14)。

Pollo · 代码与下载

pollo-replica.zip
2.7 MB · 78 个文件 · SHA-256 76d911e406f4578a40b0c334faddcd5fc814ead15420990ff1fd7bcd5d0f9d71
下载:pollo-replica.zip · 后端与合同 · 全部代码
在线试用:http://119.23.106.60:8102/。测试环境,整套独立部署(前端、BFF、Maa 智能体、数据库),可以注册、对话、生成视频(Seedance,测试积分)。页面右下角有“内部复刻研究”标识。
目录内容
frontend/Pollo 前端:<code>candidate-8891-attachments-v22.html</code> 及它引用的全部脚本与样式
contracts/本品牌的表结构 SQL、ER(svg / mmd)、外键清单;接口目录(json / csv);共享身份库表结构
README.md怎么运行、需要自备什么

运行前端

python3 -m http.server -d frontend 8791
# 打开 http://127.0.0.1:8791/agent-integration/apps-production-brand-v14/pollo/candidate-8891-attachments-v22.html
后端和全部品牌:Go 后端、Python 媒体桥、全部测试和 7 个品牌的合同在 backend-and-contracts.zip;all-brands-code.zip 是全部。包里没有任何凭据、数据库备份或媒体文件,已做敏感串扫描。
需自备:MySQL 与账号(按 contracts 建库)、Maa 智能体服务(MAA_AGENT_ROOT)、模型网关与对象存储配置。静态前端单独打开不需要后端,但登录、对话、生成、导出需要整套服务。

复刻完成度(审计 2026-10-19)

结论:不是完美复刻。官方资料里的 80 项能力:实测跑通 5、仅隔离环境跑通 1、只有通用对话 0、后端有但没跑过 5、没有后端 69。

真正跑通的:自然语言 Agent 创作;澄清与局部修改(隔离环境);项目、会话和历史;流式回复与恢复入口;文生视频与提示词辅助;分类素材与输入来源。

界面对照(首页(已登录)):真实页面采集到 18 个控件 / 标题文字,复刻页找到 15 个;位置偏差中位数 34 px(3/15 个在 24 px 内)。缺的有:Refer & Earn、Text/Image to Video、Pollo 2.0。

界面对照(Agent 页):真实页面采集到 24 个控件 / 标题文字,复刻页找到 12 个;位置偏差中位数 135 px(0/12 个在 24 px 内)。缺的有:Quality · High、More Skills、Got it、Why Create Videos With Pollo Agent、Turn Rough Inputs Into One Complete Video、Change Direction Without Starting Over、Video Workflows Built Around Your Goal、Project Assets Ready to Reuse、Create More With Pollo AI Video Tools、Loved by 10M+ Creators and Marketers Globally、Questions and Answers、YOUR NEXT VIDEO, FULLY PRODUCED。

逐个点击(已登录首页):47 个可点控件,点了有效 14 个,只提示“此功能正在接入” 28 个,没反应或只变样式 3 个,禁用 0 个,未点(发送 / 删除 / 退出等)2 个。

只提示“正在接入”的控件(28 次点击,28 种)

Toggle sidebar、Video、Image、Director、Marketing Studio、Brand Kit、Interactive Video、App、API、MCP、♧ Refer & Earn、Text/Image to Video⌄、WIN IPHONE DUO、POLLO MCP、GET UNLIMITED GPT IMAGE 2.5、AI Video Editor、Video Upscaler、Video Enhancer、Lip Sync AI、Face Swap Video、AI Video Extender、Viral、TVC、Product Shot、Product Image Set、UGC Video Ad、Food & Beverage、Social Media

点了没反应或只变样式的控件(3 次点击,3 种)

Home、Create、Video

复刻页自报的绑定状态(页面内嵌的绑定清单):原生工作区控件(新建项目、会话、登录等)9 / 10 个没有绑定到原页面的控件,由统一的工作区条代替;路由 projects、canvas、assets 缺失或只做了一部分;输入框 2 个标为“需要修结构”。

首屏截图对照(左:真实产品 pollo/source-home-desktop-safe.png;右:复刻页)· 粗粒度差异 27%

真实截图是测试账号登录后的画面,内容(作品、项目、横幅)本来就不同,差异百分比只作参考,重点看布局。

Pollo:左为真实产品截图,右为复刻页

invideo AI · 总览

重点invideo AI 看这三行就够
  • 它是什么:一句话让 Agent 出片,再进编辑器;Agent 交互:Agent 产物可在编辑器继续改。
  • 我们借鉴:Agent 产出落成可编辑对象(决策 D1)。
  • 复刻现状:4 项已验证,在线试用 http://119.23.106.60:8103/;后端与其余六个品牌共用一套(24 张表、55 个接口),所以 ER / 表结构 / 接口页的通用部分默认折叠。

按 12 类盘点当前 Editor、invideo AI、legacy Studio、官方手机 App 与外部 MCP。每条是官方公开能力或合同,使用平台分别标注。

证据:W 官方说明,A 官方接口合同,C 客户端代码,O 实测。“未实测”= 只核对了官方页面,没有上传素材或生成。

能力目录
58 项,分 12 类
入口
Agent 工作台 + 编辑器
复刻后端
24 张表 · 34 个外键 · 85 个索引

能力目录

Agent 与规划 5 项
能力说明证据限制
自然语言剪辑与追问说明目标片段、时长和保留内容,由 Agent 调整时间轴,再人工修改或继续追问。W帮助描述工作流,未抓到 Web 会话传输或修改指令的数据包。
多个专业 Agent 共用项目背景可把选角、场景和音乐分给不同 Agent,共享角色、地点和创作规则。W公开流程不证明 Agent 数量上限或后台任务调度方式。
脚本协写与逐镜头规划先写脚本和镜头计划,再审核和调整后进入生成。W未核实私有接口、质量表现及当前账号的具体额度。
按画面含义查找素材用自然语言描述要找的片段,由 Agent 从项目素材中定位。W属于官网能力说明;检索效果和索引实现未实测。
生成前审批与参数修改可选择直接生成、视频生成前确认或每次确认;审批时可改提示词、模型、时长和比例。W设置作用范围及所有入口是否一致待实测。
项目与会话 2 项
能力说明证据限制
项目中找回时间轴从 Projects 进入项目,在 Notebook 和 Agent Chat 中重新打开保存的时间轴。WUI 层级已说明;项目、Agent、聊天和时间轴的内部 ID 与基数未知。
已提交工作在云端继续完成上传和提交后可关闭浏览器,再打开项目查看 Agent 的结果。W不证明失败重试、断流续传或任务恰好执行一次。
素材管理 2 项
能力说明证据限制
上传图片视频音频并分文件夹本地上传或拖入素材,用 Media Folders 按场景、机位或类型整理。W格式、大小和存储额度以登录后的限制为准。
多机位同步与主音轨选择把不同机位放入文件夹,指定主音频,让 Agent 对齐为时间轴图层。W支持流程已有帮助;同步精度和异常素材处理待验证。
画布与分镜 2 项
能力说明证据限制
Storyboard 与 Canvas 切换旧 Studio 支持在分镜总览和单场景画布之间切换。W这里的 Canvas 是场景编辑视图,不能据此认定存在节点连线工作流。
场景与元素独立编辑可增删、复制或重排场景,调整元素图层、显示时间、动画和转场。W未核实私有接口、质量表现及当前账号的具体额度。
时间轴编辑 3 项
能力说明证据限制
多轨时间轴与可调整面板当前 Editor 有视频和音频轨、播放头、Monitor 和 Inspector,支持调整工作区布局。W未核实私有接口、质量表现及当前账号的具体额度。
裁切拆分移动与轨道锁定修剪片段、刀片拆分、调整轨道和位置;关联音频可保持同步。W快捷键和支持格式需用实际素材验证。
音量混合与颜色调整在时间轴中平衡对白和背景音乐,调整画面颜色;也可给 Agent 对应编辑指令。W未核实私有接口、质量表现及当前账号的具体额度。
生成与模型 15 项
能力说明证据限制
提示词到完整视频给出内容、平台和风格,让 AI 组织脚本、画面、配音、字幕和音乐,再继续修改。W该页面包含 AI v3.0 的 Magic Box 流程,不能直接当成当前 Editor 的同一界面。
文字生成图片用文字描述构图和风格,选择图片模型生成独立素材。W不采用宣传页中不一致的模型数量;质量和可用模型待验证。
参考图片生成动态视频上传图片,描述镜头运动并选择模型,生成可继续编辑的片段。W跨镜头人物和产品一致性是官方能力主张,效果未实测。
延长或补齐已有场景当前 Editor 可生成镜头前后或中间缺失部分,画面和声音保留为可调整图层。W官方 2026-10-06 公布最长 30 秒扩展;实际模型准入、扣费和连续性待验。
图片清晰度增强与放大上传图片选择增强模型,改善清晰度、噪声和曝光,并生成新的结果。W宣传中的效果、批量吞吐和最大输出规格未实测。
多模型目录与生成质量档位可选择生成模型;Agent 帮助还区分 Lite、Pro、Ultra 等质量与积分档位。W模型品牌计为一个目录能力;不同版本的档位和权益不可混用。
参考图片编辑与风格转换上传原图,用提示词修改局部元素、场景或风格。W官方工具页说明;模型准入、输出效果和具体额度未实测。
AI 缩略图制作提供标题、视觉要求和参考图,生成用于目标平台的封面。W不采用宣传中的点击率提升或生成速度保证。
动画影片工作流选用动画创作流程,组织脚本、生成画面和配音,再用文字修改。W页面仍描述 AI v3.0 与 Magic Box;不是已验证的逐帧动画编辑器。
去除背景与透明 PNG上传图片分离主体,可下载透明背景 PNG,或继续替换场景。W官方工具页说明;模型准入、输出效果和具体额度未实测。
生成可编辑 SVG 矢量图用文字描述生成图标、Logo 或插画,并以 SVG 交付。W真正矢量路径、编辑性和软件兼容性只有官方声明,未检测输出文件。
图片尺寸与比例重构上传图片选择目标比例,通过扩图和重新构图适配平台。W不重复按每种社交平台统计;画面完整性与分辨率待验。
多图片合并与拼贴用文字指定多图组合,可并排、网格或融合到同一画面。W官方工具页说明;模型准入、输出效果和具体额度未实测。
信息图制作与动画化提供数据、步骤或对比要求,生成信息图,再继续制作动态视频。W数据准确性与文字渲染需核对,不采用宣传准确率作为验收证据。
图片局部清理用文字移除多余物件、人物或图中文字,生成修补后的结果。W官方工具页说明;模型准入、输出效果和具体额度未实测。
声音人物字幕 10 项
能力说明证据限制
文字配音与情绪声音把脚本转成声音,选择语言、口音和表现方式,生成独立音频或用于视频。W未核实私有接口、质量表现及当前账号的具体额度。
多角色对白与场景音频按脚本分配不同说话者,或把对白和环境声组织成场景音频。W具体声音类型依模型而异,音频分层和质量未实测。
授权声音克隆上传本人或取得许可的声音样本,建立可用于多语言配音的声音。W官方要求许可并说明有验证流程;验证环节未实际操作。
自动字幕与字幕样式上传视频识别语音并对齐字幕,或为生成视频添加、调整字幕样式。W文字校正、字幕文件格式和导出清晰度的套餐限制待验。
视频配音与字幕翻译通过目标语言指令调整配音和字幕,制作同一视频的多语言版本。W该页保留 Magic Box 路线;未证实所有当前 Editor 和 App 入口一致。
AI Twin 与人物口播用自己的影像建立 Twin,或选用人物资源,结合配音制作口播视频。WExpress 与 Pro 等路线及人物授权要求不同,不能承诺全套餐可用。
参考人脸替换视频人物上传原视频和参考人像,生成换脸视频。W授权审核、真实感和宣传中的批量效果未实测。
文字生成音效描述需要的声响,生成独立音效。W官方工具页说明;模型准入、输出效果和具体额度未实测。
参考视频或音频生成声音把视频或音频与文字指令一起输入,生成相应声音。W官方工具页说明;模型准入、输出效果和具体额度未实测。
语音转换为另一声音以已有语音为输入,保留表演节奏和情绪,转换说话声音。W区别于建立可复用身份的声音克隆;具体模型和授权限制待验。
模板品牌广告 5 项
能力说明证据限制
模板与库存媒体组合旧 Studio 按用途查找模板,替换素材、文字和音乐,完成品牌或社交视频。W未核实私有接口、质量表现及当前账号的具体额度。
品牌字体颜色与 Logo 预设旧 Studio 可保存品牌素材和样式,供后续编辑使用。W未核实私有接口、质量表现及当前账号的具体额度。
商品链接到 UGC 广告读取商品或 App 信息,选定人物,先审核静态镜头,再制作配音和口型动画。W商品提取、人物一致性和口型同步均为官网流程主张,未运行生成。
广告开头变体与跨市场复用在同一产品与人物背景下生成不同开头、形式或语言版本。W官方展示并行广告路线;并发额度、审批和质量比较待验。
音乐视频场景工作流从音乐视频主题出发组织视觉、配音和音乐,再用文字修改并导出。W音乐视频制作不等于已经验证能生成独立原创音乐文件。
历史审阅协作 5 项
能力说明证据限制
分支里程碑与历史回退Branch 用于替代剪辑,Version Tag 标记阶段;History 和撤销用于返回之前的时间轴状态。W仅有产品语义;版本图、合并、保留期和并发控制合同未知。
旧 Studio 复制项目与版本回收可复制视频并改变尺寸;旧版本可移入回收站再恢复。W不证明深拷贝素材或无限保留版本。
Playlist 审阅链接与时间码反馈把时间轴加入 Playlist,分享审阅链接,定位到时间点评论、回复和处理反馈。W帮助要求评论者登录,产品页称可匿名评论,口径冲突待验;评论不会自动变成 Agent 执行指令。
当前 Editor 多人实时编辑当前官网公布团队邀请和实时光标,允许成员共同参与编辑与反馈。W这是当前 Editor 宣传功能;多人冲突、权限和套餐限制未实测,不套用到旧 Studio。
旧 Studio 共享反馈旧 Studio 提供分享视频和收集意见的协作路线。W2024 帮助同时明确当时不能多人编辑一个项目,当前是否更新待验证。
导出与交付 3 项
能力说明证据限制
视频音频和静帧分别导出可选择时间范围与输出参数,导出 MP4、独立音频或播放头所在的 PNG 静帧。W编码、分辨率和响度支持需以实际导出验证。
交接可继续剪辑的工程支持向 Premiere、Final Cut 或通过 OpenTimelineIO 向其他剪辑软件交接工程。W跨软件特效、字幕、音色和素材路径的兼容性未实测。
视频压缩工具上传视频选择压缩质量,下载体积较小的文件。W官网宣称免注册;本轮未上传文件,也未验证实际格式、大小上限和压缩率。
订阅与团队 3 项
能力说明证据限制
按席位与资源权益售卖当前套餐区分积分、模型准入、库存媒体、存储及团队席位等资源。W不固定价格、席位数量或额度;旧 Studio、AI 和新 Editor 权益互通未证实。
按消息和项目查看用量可查看单次 Agent 交换消耗和项目用量,并设置项目积分预算。W计费账本、失败退款和预算的原子性没有公开合同。
企业角色项目权限与模型策略企业版公布工作区角色、项目可见范围、模型策略、用量洞察和审计控制。W企业定制能力以商务合同和账号准入为准,未验证可由普通套餐开启。
外部接入与 App 3 项
能力说明证据限制
远程 MCP 的 OAuth 接入官方入口为 https://mcp.invideo.io/mcp,使用 Streamable HTTP,连接时授权 invideo 账号。AA 仅证明公开接入合同;未授权调用 tools/list,也不证明 Web 聊天采用相同协议。
外部 Agent 操作同一编辑工程官方说明外部 Agent 可导入和生成媒体,编辑时间轴、标题、转场、颜色和混音。W能力说明不等于已取得工具参数 schema、实际请求或运行结果。
官方 iOS 与 Android 创作入口官方商店列有 invideo App,公布 Agent 创作、时间轴编辑、生成和团队反馈能力。W发布者 Invideo Inc;Android 包名 io.invideo.ai,iOS 商店 ID 6471394316。未安装、取得 IPA/APK 或验证与 Web 的功能和权益互通。

尚不清楚的部分

竞品技术栈观察

能支持旧 Studio 的 JavaScript 壳页、公开练习的 React/TypeScript/Vite/Rust-Wasm 技术,以及官方岗位的 Phoenix/Elixir/Rust 工程线索。AI/Editor/Studio 的生产框架、Nginx 版本、仓库布局及源站部署仍未确认。

项目观察到的内容状态置信度
Nginx 与版本是否使用 Nginx、所在层及版本均未确认。unknownlow
浏览器运行语言旧 Studio 壳页明确依赖 JavaScript;AI 与当前 Editor 的运行细节未确认。observedhigh
前端源码语言生产源码语言未确认;官方公开练习有 TSX,TypeScript 声明 ~5.9.3。unknownlow
前端框架与版本生产框架未确认;旧 Studio 路径提示 React;公开练习声明 React ^19.2.0。unknownlow
构建与 SSR生产构建/SSR 未确认;公开练习使用 Vite(声明 ^7.2.4)。unknownlow
后端语言官方岗位披露 Elixir 服务与 Rust 组件;各产品实际部署未确认。inferredmedium
后端框架与版本官方岗位涉及 Phoenix、Ecto、OTP;对应线上服务与精确版本未知。inferredmedium
边缘入口与源站AI/静态资源指向 CloudFront;MCP 入口指向 us-west-2 ELB;真实源站与存储未知。observedhigh
代码仓库与目录生产仓库数量、前后端同目录及发布关系均未知;公开练习的目录不能代表产品。unknownlow
竞品服务调用图

当前 Editor 的项目→Agent→可编辑 Timeline→历史/审阅/导出有官方过程证据;外部 MCP 协议公开。服务图只列逻辑职责,独立候选层、生成适配及真实部署均有明确推断边界。

节点层职责证据
Editor界面client用户操作 Project、Notebook、Timeline 的客户端。W
外部AgentclientMCP 客户端,例如 Codex/Claude;只作为外部接入者。W
外部MCP入口entry官方公开 Streamable HTTP/OAuth endpoint;同日 DNS 有 ELB 入口线索。A
项目访问职责domain选择账号可访问的 Project、Notebook 与目标 Timeline。W
项目素材职责domain上传、整理媒体并供编辑使用。W
Agent任务职责runtime读取项目背景并承接编辑/生成任务。W
结果检查职责domain将 Agent 编辑结果呈现给人检查;是否有独立候选暂存层未证实。I
时间轴编辑职责domain承载人工与 Agent 面向同一 Timeline 的修改。W
分支历史职责domain提供 Branch、Tag 与 History 的产品行为。W
审阅分享职责domainPlaylist 链接、下载控制和时间码反馈。W
导出交付职责runtime按范围与输出设置交付视频、声音、静帧或工程。W
云端执行职责runtime承接已经提交的 Agent 工作,页面关闭后可继续。W
生成适配职责runtime可能协调媒体生成及积分审批,向编辑任务提供结果。I

增长与运营机制

旧 Studio 与新 Editor 分开获客,场景/模型内容接入产品,外部 Agent 与联盟补充分发。(官方正文;源元数据未知)

复刻状态

能力状态依据
工作区条、登录框、新建项目框已验证令牌生效;弹窗背景 #212121、主按钮白色
Agent 对话:新建项目与会话、自动选默认模型、发送收回复已验证真实 Maa,收到回复
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
Agent 工作台与编辑器布局已加载沿用早期静态页
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8103 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
前端入口:agent-integration/apps-production-brand-v14/invideo/candidate-8875-unified-v22.html
复刻的架构 七个品牌相同,展开查看
flowchart LR
  U[浏览器] -->|静态页| FE[品牌前端<br/>令牌 + 通用层 + 运行时脚本]
  FE -->|/v1/* Cookie + CSRF| BFF[BFF 业务服务 Go]
  BFF --> MY[(MySQL<br/>共享身份库 + 品牌库)]
  BFF -->|会话 / 消息 / 事件| MAA[Maa Agent]
  BFF -->|任务| WK[媒体 worker]
  WK -->|submit_video| GW[模型网关]
  GW --> MOD[视频模型]
  MAA --> GW
  BFF --> OBJ[(素材 / 导出存储)]
所有七个复刻共用同一套后端:24 张表的品牌库 + 15 张表的共享身份库,55 个公开接口(路径里用 ?profile=invideo 区分品牌)。品牌差异在前端:令牌(颜色、圆角、字体)、入口与页面结构。
为什么这样设计七个竞品的后端结构在我们的实测里几乎一致,差别只在入口、模板和视觉。所以复刻把差异收敛到前端令牌,后端只维护一份;这也是最终设计里「共享内核 + 品牌配置」的来源(决策 D17)。

invideo AI · Agent 交互过程

竞品里观察到的

NOT_RUN_COST_GATE 从当前官网仅导航一次Start Creating,实际到注册/登录入口,未到Agent工作台。现行官方说明包含回复对话费用,不满足零积分条件,未发送。

费用与准入

项值
selected_modelnot_reached
observed_price当前工作台未到达;官方说明每次回复可包含对话与媒体积分费用。
available_creditsnot_checked_current_agent
exact_per_turn_costunknown
approval用户仅授权明确消耗0积分的Agent文字对话,不得花现有积分。
free_only_requirementtrue
zero_credit_cost_confirmedfalse
action_takenno_send
reason当前产品先遇到登录入口;官方免费套餐为有限credits,不能证明文字回复免费。Always Ask只控制生成批准,不豁免对话计费。
evidence_refs["IV-COST-W1", "IV-FREE-W2"]

竞品公开的调用流程

步骤调用关系操作证据
打开项目编辑用户 → 项目入口进入 Project,再从 Notebook 的 Agent Chat 找到目标 Timeline。W
准备素材编辑用户 → 项目入口上传媒体并用文件夹整理;等待上传完成后再把它用于 Agent 工作。W
提交剪辑目标编辑用户 → Agent编排描述片段、长度和保留内容;项目中的 Agent 可共享创作背景并接收各自任务。W
检查Agent结果Agent编排 → 编辑状态Agent 更新剪辑后,用户在 Monitor/Timeline 检查可继续手改的结果;此处候选指待检查的结果。W
人工细调编辑用户 → 编辑状态手改剪辑,或把局部修订要求发回 Agent;人工与 Agent 面向同一 Timeline。W
保留方向与历史编辑用户 → 编辑状态用 Branch 尝试替代剪辑,Tag 标记阶段,History 返回之前状态。W
发送审阅版本编辑用户 → 交付模块选择 Timeline 建 Playlist 审阅链接,设置观看/下载权限并收集时间码反馈。W
导出作品编辑用户 → 交付模块选择范围和参数导出视频、音频、静帧或工程,再检查最终文件。W
可选外部接入外部Agent → 项目入口经 https://mcp.invideo.io/mcp 授权后使用 Editor 能力;没有调用任何工具或取得参数 schema。A

在本品牌复刻上的测试结果

能力状态依据
Agent 对话:新建项目与会话、自动选默认模型、发送收回复已验证真实 Maa,收到回复
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
Agent 工作台与编辑器布局已加载沿用早期静态页
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8103 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
复刻里的 Agent 交互设计(时序图、状态机、规则) 七个品牌相同,展开查看
sequenceDiagram
  autonumber
  participant B as 浏览器
  participant S as BFF
  participant DB as MySQL
  participant M as Maa Agent
  participant W as 媒体 worker
  participant G as 模型网关
  B->>S: POST /projects, /conversations
  B->>S: POST maa/binding
  S->>DB: maa_bindings dispatching
  S->>M: 创建原生会话
  M-->>S: native_cid
  S->>DB: maa_bindings bound
  B->>S: POST maa/messages {client_action_id, content}
  S->>DB: maa_message_actions 幂等回执
  S-->>B: 202 + run_id
  S->>M: 提交消息
  S->>DB: maa_runs queued 到 running
  loop 事件游标
    B->>S: GET runs/{id}/events
    S-->>B: assistant.delta / assistant.message
  end
  M-->>S: run.completed
  S->>DB: 记录助手内容 SHA-256
  B->>S: POST media/requests/from-maa {source_run_id, sha256}
  S-->>B: 报价 + 有效期 + quote_sha256
  B->>S: POST approve {client_approval_id, request_sha256, quote_sha256}
  S->>DB: 冻结积分 + 建任务
  W->>G: submit_video
  G-->>W: 视频
  W->>DB: 产出入库 + 扣费
  B->>S: GET media/jobs/{id}

Run 状态机

stateDiagram-v2
  [*] --> queued: POST maa/messages 202
  queued --> running
  running --> waiting_interaction: 需要澄清卡
  waiting_interaction --> running: respondInteraction
  running --> completed
  running --> failed
  running --> cancelled: stopRun
  waiting_interaction --> cancelled
  completed --> [*]
  failed --> [*]
  cancelled --> [*]

媒体任务状态机

stateDiagram-v2
  [*] --> quoted
  quoted --> expired: 约 5 分钟
  quoted --> approved: approve
  approved --> held: 冻结积分
  held --> queued
  queued --> running: worker 领取
  running --> succeeded: 产出入库
  succeeded --> captured: 扣费确认
  running --> failed
  failed --> 待核对: 供应商已受理后失败,冻结保留,界面写「费用待核对」

交互规则

环节规则落库
绑定会话bindMaa 传 agent_model 或 use_default;先 dispatching,拿到原生会话号后才 bound(约束保证二者一致)maa_bindings
发消息client_action_id 幂等;返回 202 + run_id;同一个动作 ID 内容不同返回 409maa_message_actions, maa_runs
事件按 seq 游标读取;服务端先清洗路径、内部地址和令牌再公开maa_runs.last_event_seq
完成证据完成时记录助手内容的两份 SHA-256 与字节数maa_runs
澄清卡(HITL)Run 进入 waiting_interaction;respondInteraction 带 card_sha256;不能恢复两次maa_interactions, maa_interaction_actions
停止stopRun 幂等,回执只说「已观测到终态」;对话里保留「已停止回复」maa_run_stop_actions
切换模型必须带 expected_revision;有未决切换时拒绝发新消息maa_model_change_actions
生成视频from-maa → 报价 → 批准 → 冻结 → worker → 入库 → 扣费;批准带请求哈希与报价哈希studio_media_*, 积分账本
报价过期批准被拒,不建任务不扣费;界面提示重新报价studio_media_requests.expires_at
失败后的积分供应商已受理后失败:冻结保留,界面写「费用待核对」,不自动释放;本部署的账本里还没出现过释放分录(12 笔冻结、8 笔扣费、其余 4 笔仍冻结)studio_credit_ledger_entries
会话过期每 8 秒检查登录状态;失效时横幅提示,登录后恢复—
为什么这样设计把事件做成带序号的流、把动作做成带幂等键的回执,是因为长回复一定会遇到断线、刷新和重复点击。序号让续读不丢不重;幂等让重复点击安全;诚实的回执(只说「已观测到终态」)避免把「已停止」说成「不收费」。我们还在复刻里修掉了一个停止按钮从不发请求的缺陷,这说明每条交互路径都需要端到端测试(决策 D8)。

invideo AI · ER 图

品牌库 studio_profile_invideo:24 张表、34 个真实外键、66 个检查约束、22 个触发器。图由实际表结构生成,箭头从父表指向子表。品牌库与共享身份库之间的 owner_id、asset_id 靠应用层归属检查,不画成外键。

共 61 条关系:一对多 37 条,一对一 10 条,一对多(父可选) 10 条,一对一(父可选) 4 条;多对多 2 组(经关联表)。

用户与登录 素材 积分账本 支付与订阅 模型目录 项目与会话 画布 · 时间轴 · 导出 Agent 交互 生成 · 媒体 · 交付

基数标注(线上的文字,箭头从父表指向子表):1:N 一对多,子表外键必填;0..1:N 一对多,但外键可空,所以父是可选的;1:1 一对一,子表外键上有唯一键;0..1:1 一对一且父可选。红色粗双向线 = N:M 多对多,经带 ⇄ 的关联表(它的主键就是两个外键)。实线 = 数据库真实外键;灰色虚线 = 跨库关系(两个库之间数据库无法建外键,由应用层检查);橙色粗框与粗线 = Agent 交互相关。每张表都带 owner_id(指向用户),为了图清楚没有逐条画线。

全景图:全部实体与关系

默认缩放到页面宽度,先看整体结构;点一下图切换到原始大小(看清表名,在框内上下左右滚动),再点一下缩回。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":26,"rankSpacing":80}}}%%
flowchart LR
  subgraph sg_i_user["用户与登录"]
    direction TB
    t_studio_users["users"]
    t_studio_sessions["sessions"]
  end
  subgraph sg_i_asset["素材"]
    direction TB
    t_studio_assets["assets"]
  end
  subgraph sg_i_credit["积分账本"]
    direction TB
    t_studio_credit_accounts["credit_accounts"]
    t_studio_credit_lots["credit_lots"]
    t_studio_credit_authorizations["credit_authorizations"]
    t_studio_credit_authorization_allocations["⇄ credit_authorization_allocations"]
    t_studio_credit_ledger_entries["credit_ledger_entries"]
    t_studio_credit_plan_versions["credit_plan_versions"]
    t_studio_billing_cycles["billing_cycles"]
  end
  subgraph sg_i_pay["支付与订阅"]
    direction TB
    t_studio_payment_orders["payment_orders"]
    t_studio_payment_events["payment_events"]
    t_studio_subscriptions["subscriptions"]
  end
  subgraph sg_i_model["模型目录"]
    direction TB
    t_studio_media_models["media_models"]
    t_studio_media_model_versions["media_model_versions"]
  end
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  subgraph sg_media["生成 · 媒体 · 交付"]
    direction TB
    t_studio_media_requests["media_requests"]
    t_studio_media_jobs["media_jobs"]
    t_studio_media_submission_attempts["media_submission_attempts"]
    t_studio_media_outputs["media_outputs"]
    t_generation_jobs["gen_jobs"]
    t_generation_reservations["gen_reservations"]
    t_generation_deliveries["gen_deliveries"]
    t_generation_development_budgets["gen_development_budgets"]
    t_studio_credit_recovery_intents["credit_recovery_intents"]
  end
  t_studio_users -->|"1:N"| t_studio_assets
  t_studio_payment_orders -->|"1:1"| t_studio_billing_cycles
  t_studio_subscriptions -->|"1:N"| t_studio_billing_cycles
  t_studio_users -->|"1:1"| t_studio_credit_accounts
  t_studio_credit_authorizations -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_lots -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_authorizations
  t_studio_credit_authorization_allocations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_authorizations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_lots -->|"1:N"| t_studio_credit_ledger_entries
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_lots
  t_studio_billing_cycles -->|"0..1:1"| t_studio_credit_lots
  t_studio_payment_orders -->|"0..1:1"| t_studio_credit_lots
  t_studio_media_models -->|"1:N"| t_studio_media_model_versions
  t_studio_media_model_versions -->|"0..1:1"| t_studio_media_models
  t_studio_payment_orders -->|"0..1:N"| t_studio_payment_events
  t_studio_subscriptions -->|"0..1:N"| t_studio_payment_events
  t_studio_users -->|"1:N"| t_studio_payment_events
  t_studio_credit_plan_versions -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_sessions
  t_studio_credit_plan_versions -->|"1:N"| t_studio_subscriptions
  t_studio_users -->|"1:N"| t_studio_subscriptions
  t_generation_jobs -->|"1:1"| t_generation_deliveries
  t_studio_projects -->|"1:N"| t_generation_deliveries
  t_studio_conversations -->|"1:N"| t_generation_deliveries
  t_generation_development_budgets -->|"1:N"| t_generation_jobs
  t_studio_projects -->|"1:N"| t_generation_jobs
  t_studio_conversations -->|"1:N"| t_generation_jobs
  t_generation_jobs -->|"1:1"| t_generation_reservations
  t_studio_conversations -->|"1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"0..1:1"| t_maa_bindings
  t_maa_interactions -->|"1:1"| t_maa_interaction_actions
  t_maa_runs -->|"1:N"| t_maa_interactions
  t_maa_bindings -->|"1:N"| t_maa_message_actions
  t_maa_runs -->|"0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"1:N"| t_maa_model_change_actions
  t_maa_runs -->|"1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"1:N"| t_maa_runs
  t_studio_canvases -->|"1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"1:1"| t_studio_canvases
  t_studio_projects -->|"1:N"| t_studio_conversations
  t_studio_media_jobs -->|"1:N"| t_studio_credit_recovery_intents
  t_studio_projects -->|"1:N"| t_studio_export_jobs
  t_studio_media_requests -->|"1:1"| t_studio_media_jobs
  t_studio_media_jobs -->|"1:N"| t_studio_media_outputs
  t_studio_conversations -->|"1:N"| t_studio_media_requests
  t_maa_runs -->|"0..1:N"| t_studio_media_requests
  t_studio_media_jobs -->|"1:N"| t_studio_media_submission_attempts
  t_studio_projects -->|"1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"1:1"| t_studio_video_documents
  t_studio_assets -.->|"1:N"| t_studio_project_asset_refs
  t_studio_assets -.->|"1:N"| t_studio_media_outputs
  t_studio_assets -.->|"1:N"| t_generation_deliveries
  t_studio_media_model_versions -.->|"1:N"| t_studio_media_requests
  t_studio_media_models -.->|"1:N"| t_studio_media_requests
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_media_jobs
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_credit_recovery_intents
  t_studio_credit_authorizations <-.->|"N:M · 经 credit_authorization_allocations"| t_studio_credit_lots
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_users,t_studio_sessions user
class t_studio_assets asset
class t_studio_credit_accounts,t_studio_credit_lots,t_studio_credit_authorizations,t_studio_credit_authorization_allocations,t_studio_credit_ledger_entries,t_studio_credit_plan_versions,t_studio_billing_cycles credit
class t_studio_payment_orders,t_studio_payment_events,t_studio_subscriptions pay
class t_studio_media_models,t_studio_media_model_versions model
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
class t_studio_media_requests,t_studio_media_jobs,t_studio_media_submission_attempts,t_studio_media_outputs,t_generation_jobs,t_generation_reservations,t_generation_deliveries,t_generation_development_budgets,t_studio_credit_recovery_intents media
class t_studio_credit_authorization_allocations,t_studio_project_asset_refs junction
  style sg_i_user fill:none,stroke:#6b7280,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_asset fill:none,stroke:#06b6d4,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_credit fill:none,stroke:#eab308,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_pay fill:none,stroke:#ec4899,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_model fill:none,stroke:#6366f1,stroke-width:1px,stroke-dasharray:5 4
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  style sg_media fill:none,stroke:#22c55e,stroke-width:1px
  linkStyle 30,31,32,33,34,35,36,37,38,48 stroke:#f97316,stroke-width:2.5px
  linkStyle 54,55,56,57,58,59,60 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 61,62 stroke:#dc2626,stroke-width:3px

多对多(N:M)

数据库里没有“多对多”这种列,它总是靠一张关联表表达。下面这几张表的主键由两个外键组成,所以它们两端是多对多:

多对多的两端关联表关联表的主键 / 唯一键
studio_credit_authorizations ⇄ studio_credit_lotsstudio_credit_authorization_allocationsowner_id, authorization_id, lot_id
studio_projects ⇄ studio_assetsstudio_project_asset_refsproject_id, asset_id

Agent 交互相关(圈出来)

绑定 → Run → 消息 / 澄清卡 / 切换模型 / 停止,每个动作一张回执表;线上是“外键列 · 基数”。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  t_studio_conversations -->|"conversation_id · 1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"pending_model_change_id · 0..1:1"| t_maa_bindings
  t_maa_interactions -->|"interaction_id · 1:1"| t_maa_interaction_actions
  t_maa_runs -->|"public_run_id · 1:N"| t_maa_interactions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_message_actions
  t_maa_runs -->|"public_run_id · 0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_model_change_actions
  t_maa_runs -->|"public_run_id · 1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_runs
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  linkStyle 0,1,2,3,4,5,6,7,8 stroke:#f97316,stroke-width:2.5px

画布与时间轴

项目下是一份视频文档,画布、画布版本、时间线版本和导出都挂在这份文档上;素材放在共享身份库,项目只记引用,项目与素材是多对多,经 project_asset_refs。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_asset["素材(共享身份库)"]
    direction TB
    t_studio_assets["assets"]
  end
  t_studio_canvases -->|"project_id · 1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"restored_from_revision · 0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_canvases
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
  t_studio_projects -->|"project_id · 1:N"| t_studio_export_jobs
  t_studio_projects -->|"project_id · 1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"project_id · 1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"restored_from_revision · 0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_video_documents
  t_studio_assets -.->|"asset_id · 1:N"| t_studio_project_asset_refs
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_studio_assets asset
class t_studio_project_asset_refs junction
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px
  linkStyle 9 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 10 stroke:#dc2626,stroke-width:3px
关系清单 逐条 61 条,含基数
父表子表外键列(子表里)基数约束域
generation_jobsgeneration_deliveriesjob_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_assetsgeneration_deliveriesasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_conversationsgeneration_deliveriesowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_deliveriesowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_development_budgetsgeneration_jobsowner_id, budget_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_conversationsgeneration_jobsowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_jobsgeneration_reservationsjob_id, budget_id, owner_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_model_change_actionsmaa_bindingsowner_id, project_id, conversation_id, id, pending_model_change_id0..1:1 一对一(父可选)DB 外键Agent 交互 → Agent 交互
studio_conversationsmaa_bindingsowner_id, project_id, conversation_id1:1 一对一DB 外键项目与会话 → Agent 交互
maa_interactionsmaa_interaction_actionsowner_id, project_id, conversation_id, binding_id, public_run_id, interaction_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_runsmaa_interactionsowner_id, project_id, conversation_id, binding_id, public_run_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_message_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_message_actionsowner_id, project_id, conversation_id, binding_id, public_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_model_change_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_run_stop_actionsowner_id, project_id, conversation_id, binding_id, public_run_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_runsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
studio_usersstudio_assetsowner_id1:N 一对多DB 外键用户与登录 → 素材
studio_payment_ordersstudio_billing_cyclesowner_id, payment_order_id, plan_version_id1:1 一对一DB 外键支付与订阅 → 积分账本
studio_subscriptionsstudio_billing_cyclesowner_id, subscription_id, provider_key, provider_account1:N 一对多DB 外键支付与订阅 → 积分账本
studio_canvas_revisionsstudio_canvas_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_canvasesstudio_canvas_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_canvasesowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_projectsstudio_conversationsowner_id, project_id1:N 一对多DB 外键项目与会话 → 项目与会话
studio_usersstudio_credit_accountsowner_id1:1 一对一DB 外键用户与登录 → 积分账本
studio_credit_authorizationsstudio_credit_authorization_allocationsowner_id, authorization_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_authorization_allocationsowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_authorizationsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_authorization_allocationsstudio_credit_ledger_entriesowner_id, authorization_id, lot_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_authorizationsstudio_credit_ledger_entriesowner_id, authorization_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_ledger_entriesowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_billing_cyclesstudio_credit_lotsowner_id, source_cycle_id0..1:1 一对一(父可选)DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_lotsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_payment_ordersstudio_credit_lotsowner_id, source_order_id0..1:1 一对一(父可选)DB 外键支付与订阅 → 积分账本
studio_credit_authorizationsstudio_credit_recovery_intentsauthorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_jobsstudio_credit_recovery_intentsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_projectsstudio_export_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_credit_authorizationsstudio_media_jobscredit_authorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_requestsstudio_media_jobsowner_id, project_id, conversation_id, request_id, request_sha256, quote_sha2561:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_model_versionsmodel_id1:N 一对多DB 外键模型目录 → 模型目录
studio_media_model_versionsstudio_media_modelsid, published_version_id0..1:1 一对一(父可选)DB 外键模型目录 → 模型目录
studio_assetsstudio_media_outputsasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_outputsowner_id, project_id, conversation_id, job_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_runsstudio_media_requestsowner_id, project_id, conversation_id, source_binding_id, source_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → 生成 · 媒体 · 交付
studio_conversationsstudio_media_requestsowner_id, project_id, conversation_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_media_model_versionsstudio_media_requestsmodel_version_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_requestsmodel_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_submission_attemptsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_payment_ordersstudio_payment_eventsowner_id, order_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_subscriptionsstudio_payment_eventsowner_id, subscription_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_usersstudio_payment_eventsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_credit_plan_versionsstudio_payment_ordersplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_payment_ordersowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_assetsstudio_project_asset_refsasset_id1:N 一对多跨库(应用层检查)素材 → 画布 · 时间轴 · 导出
studio_projectsstudio_project_asset_refsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_usersstudio_sessionsuser_id1:N 一对多DB 外键用户与登录 → 用户与登录
studio_credit_plan_versionsstudio_subscriptionsplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_subscriptionsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_timeline_revisionsstudio_timeline_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_video_documentsstudio_timeline_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_video_documentsowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
完整 ER:39 张表的全部列,带主键 / 外键与基数符号(鸦脚)
erDiagram
  studio_assets {
    CHAR id PK
    CHAR owner_id FK
    CHAR sha256
    JSON metadata_json
    VARCHAR storage_key
    DATETIME created_at
    VARCHAR publication_state
  }
  studio_billing_cycles {
    CHAR id PK
    CHAR owner_id FK
    CHAR subscription_id FK
    CHAR plan_version_id FK
    CHAR payment_order_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_invoice_id
    DATETIME period_start
    DATETIME period_end
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_accounts {
    CHAR owner_id PK
    VARCHAR unit
    VARCHAR status
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_credit_authorization_allocations {
    CHAR owner_id FK
    CHAR authorization_id PK
    CHAR lot_id PK
    BIGINT_UNSIGNED allocated_credits
    DATETIME created_at
  }
  studio_credit_authorizations {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR profile
    CHAR project_id
    CHAR conversation_id
    CHAR job_id
    CHAR request_id
    CHAR request_sha256
    CHAR quote_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    MEDIUMBLOB reservation_bytes
    CHAR reservation_sha256
    MEDIUMBLOB policy_bytes
    CHAR policy_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_ledger_entries {
    CHAR id PK
    CHAR owner_id FK
    CHAR lot_id FK
    CHAR authorization_id FK
    VARCHAR operation_key
    VARCHAR kind
    BIGINT grant_delta
    BIGINT held_delta
    BIGINT captured_delta
    MEDIUMBLOB facts_bytes
    CHAR facts_sha256
    DATETIME created_at
  }
  studio_credit_lots {
    CHAR id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED grant_credits
    BIGINT_UNSIGNED held_credits
    BIGINT_UNSIGNED captured_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    CHAR source_order_id FK
    CHAR source_cycle_id FK
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_plan_versions {
    CHAR id PK
    VARCHAR plan_key
    VARCHAR version_key
    VARCHAR kind
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    INT_UNSIGNED credit_validity_seconds
    INT_UNSIGNED billing_interval_months
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    DATETIME published_at
    DATETIME retired_at
  }
  studio_media_model_versions {
    CHAR id PK
    CHAR model_id FK
    VARCHAR version_key
    MEDIUMBLOB config_bytes
    CHAR config_sha256
    DATETIME published_at
  }
  studio_media_models {
    CHAR id PK
    VARCHAR model_key
    VARCHAR kind
    VARCHAR display_name
    VARCHAR status
    CHAR published_version_id FK
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_payment_events {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_event_id
    CHAR order_id FK
    CHAR subscription_id FK
    MEDIUMBLOB body_bytes
    CHAR body_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_payment_orders {
    CHAR id PK
    CHAR owner_id FK
    CHAR client_action_id
    CHAR plan_version_id FK
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_idempotency_key
    VARCHAR provider_order_id
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_sessions {
    CHAR token_hash PK
    CHAR user_id FK
    DATETIME created_at
    DATETIME expires_at
    DATETIME revoked_at
  }
  studio_subscriptions {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_subscription_id
    CHAR plan_version_id FK
    VARCHAR state
    DATETIME period_start
    DATETIME period_end
    TINYINT cancel_at_period_end
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_users {
    CHAR id PK
    VARCHAR email
    VARBINARY password_hash
    TINYINT enabled
    DATETIME created_at
  }
  generation_deliveries {
    CHAR job_id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR asset_id FK
    CHAR request_sha256
    CHAR maa_source_sha256
    CHAR provider_source_sha256
    VARCHAR model
    VARCHAR tool_name
    CHAR input_sha256
    JSON metadata_json
    DATETIME created_at
  }
  generation_development_budgets {
    CHAR id PK
    CHAR owner_id
    VARCHAR policy_version
    BIGINT_UNSIGNED credits_limit
    BIGINT_UNSIGNED credits_reserved
    INT_UNSIGNED calls_limit
    INT_UNSIGNED calls_reserved
    DATETIME expires_at
  }
  generation_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR native_conversation_id
    CHAR native_run_id
    CHAR source_sha256
    CHAR budget_id FK
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    VARCHAR status
    INT_UNSIGNED attempts
    MEDIUMBLOB result_bytes
    DATETIME updated_at
  }
  generation_reservations {
    CHAR job_id PK
    CHAR budget_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED credits
    INT_UNSIGNED calls
    VARCHAR status
  }
  maa_bindings {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR provider
    VARCHAR model_name
    VARCHAR status
    VARCHAR native_cid
    DATETIME created_at
    BIGINT_UNSIGNED model_revision
    CHAR pending_model_change_id FK
  }
  maa_interaction_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR interaction_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR client_action_id
    CHAR card_sha256
    CHAR request_sha256
    CHAR content_sha256
    VARCHAR response_action
    JSON values_payload
    VARCHAR status
    DATETIME created_at
  }
  maa_interactions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR card_sha256
    JSON card_payload
    DATETIME created_at
  }
  maa_message_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_action_id
    CHAR request_sha256
    MEDIUMTEXT content
    VARCHAR status
    CHAR public_run_id FK
    CHAR proposed_run_id
    DATETIME created_at
  }
  maa_model_change_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    CHAR client_action_id
    CHAR request_sha256
    CHAR native_request_sha256
    BIGINT_UNSIGNED expected_revision
    VARCHAR from_provider
    VARCHAR from_model_name
    VARCHAR target_provider
    VARCHAR target_model_name
    VARCHAR status
    BIGINT_UNSIGNED dispatch_attempt
    JSON receipt_payload
    DATETIME created_at
    DATETIME updated_at
  }
  maa_run_stop_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    VARCHAR native_rid
    CHAR client_action_id
    CHAR request_sha256
    VARCHAR stop_action
    VARCHAR status
    BIGINT_UNSIGNED attempt
    VARCHAR terminal_run_status
    DATETIME created_at
    DATETIME updated_at
  }
  maa_runs {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_rid
    VARCHAR status
    DATETIME created_at
    CHAR native_assistant_sha256
    CHAR public_assistant_sha256
    BIGINT_UNSIGNED native_assistant_bytes
    BIGINT_UNSIGNED assistant_event_seq
    BIGINT_UNSIGNED last_event_seq
  }
  studio_canvas_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_canvases {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_conversations {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    VARCHAR title
    DATETIME created_at
  }
  studio_credit_recovery_intents {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256
    VARCHAR kind
    CHAR authorization_id FK
    MEDIUMBLOB intent_bytes
    CHAR intent_sha256
    VARCHAR state
    CHAR lease_token_sha256
    DATETIME lease_until
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_export_jobs {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED document_revision
    CHAR input_hash
    VARCHAR status
    JSON input_json
    JSON output_json
    VARCHAR error_code
    CHAR lease_token_hash
    DATETIME lease_until
    INT_UNSIGNED attempts
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR request_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256 FK
    CHAR client_approval_id
    CHAR approval_sha256
    CHAR credit_authorization_id FK
    VARCHAR authorization_mode
    DATETIME authorization_expires_at
    VARCHAR status
    BIGINT_UNSIGNED revision
    CHAR lease_token_sha256
    DATETIME lease_until
    VARCHAR provider_task_id
    CHAR client_cancel_id
    CHAR cancel_request_sha256
    DATETIME cancel_requested_at
    CHAR staged_asset_id
    CHAR staged_asset_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_outputs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED ordinal
    CHAR asset_id FK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    CHAR provider_result_sha256
    DATETIME created_at
  }
  studio_media_requests {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_request_id
    CHAR model_id FK
    CHAR model_version_id FK
    CHAR model_config_sha256
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    MEDIUMBLOB quote_bytes
    CHAR quote_sha256
    BIGINT_UNSIGNED quoted_credits
    DATETIME expires_at
    VARCHAR origin
    CHAR source_binding_id FK
    CHAR source_run_id FK
    CHAR source_assistant_sha256
    MEDIUMBLOB source_snapshot_bytes
    DATETIME created_at
  }
  studio_media_submission_attempts {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED attempt_no
    CHAR request_sha256 FK
    VARCHAR provider_idempotency_key
    VARCHAR stage
    DATETIME dispatch_started_at
    VARCHAR provider_task_id
    CHAR receipt_sha256
    CHAR terminal_receipt_sha256
    CHAR reconciliation_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_project_asset_refs {
    CHAR project_id PK
    CHAR owner_id FK
    CHAR asset_id PK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
    DATETIME detached_at
  }
  studio_projects {
    CHAR id PK
    CHAR owner_id
    VARCHAR title
    DATETIME created_at
  }
  studio_timeline_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_video_documents {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_users ||--o{ studio_assets : "owner_id"
  studio_payment_orders ||--o| studio_billing_cycles : "plan_version_id"
  studio_subscriptions ||--o{ studio_billing_cycles : "provider_account"
  studio_users ||--o| studio_credit_accounts : "owner_id"
  studio_credit_authorizations ||--o{ studio_credit_authorization_allocations : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_authorization_allocations : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_authorizations : "owner_id"
  studio_credit_authorization_allocations |o--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_authorizations |o--o{ studio_credit_ledger_entries : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_lots : "owner_id"
  studio_billing_cycles |o--o| studio_credit_lots : "source_cycle_id"
  studio_payment_orders |o--o| studio_credit_lots : "source_order_id"
  studio_media_models ||--o{ studio_media_model_versions : "model_id"
  studio_media_model_versions |o--o| studio_media_models : "published_version_id"
  studio_payment_orders |o--o{ studio_payment_events : "provider_account"
  studio_subscriptions |o--o{ studio_payment_events : "provider_account"
  studio_users ||--o{ studio_payment_events : "owner_id"
  studio_credit_plan_versions ||--o{ studio_payment_orders : "plan_version_id"
  studio_users ||--o{ studio_payment_orders : "owner_id"
  studio_users ||--o{ studio_sessions : "user_id"
  studio_credit_plan_versions ||--o{ studio_subscriptions : "plan_version_id"
  studio_users ||--o{ studio_subscriptions : "owner_id"
  generation_jobs ||--o| generation_deliveries : "job_id"
  studio_projects ||--o{ generation_deliveries : "project_id"
  studio_conversations ||--o{ generation_deliveries : "session_id"
  generation_development_budgets ||--o{ generation_jobs : "budget_id"
  studio_projects ||--o{ generation_jobs : "project_id"
  studio_conversations ||--o{ generation_jobs : "session_id"
  generation_jobs ||--o| generation_reservations : "budget_id"
  studio_conversations ||--o| maa_bindings : "conversation_id"
  maa_model_change_actions |o--o| maa_bindings : "pending_model_change_id"
  maa_interactions ||--o| maa_interaction_actions : "interaction_id"
  maa_runs ||--o{ maa_interactions : "public_run_id"
  maa_bindings ||--o{ maa_message_actions : "binding_id"
  maa_runs |o--o{ maa_message_actions : "public_run_id"
  maa_bindings ||--o{ maa_model_change_actions : "binding_id"
  maa_runs ||--o| maa_run_stop_actions : "public_run_id"
  maa_bindings ||--o{ maa_runs : "binding_id"
  studio_canvases ||--o{ studio_canvas_revisions : "project_id"
  studio_canvas_revisions |o--o{ studio_canvas_revisions : "restored_from_revision"
  studio_projects ||--o| studio_canvases : "project_id"
  studio_projects ||--o{ studio_conversations : "project_id"
  studio_media_jobs ||--o{ studio_credit_recovery_intents : "job_id"
  studio_projects ||--o{ studio_export_jobs : "project_id"
  studio_media_requests ||--o| studio_media_jobs : "request_id"
  studio_media_jobs ||--o{ studio_media_outputs : "job_id"
  studio_conversations ||--o{ studio_media_requests : "conversation_id"
  maa_runs |o--o{ studio_media_requests : "source_run_id"
  studio_media_jobs ||--o{ studio_media_submission_attempts : "job_id"
  studio_projects ||--o{ studio_project_asset_refs : "project_id"
  studio_video_documents ||--o{ studio_timeline_revisions : "project_id"
  studio_timeline_revisions |o--o{ studio_timeline_revisions : "restored_from_revision"
  studio_projects ||--o| studio_video_documents : "project_id"
  studio_assets ||--o{ studio_project_asset_refs : "asset_id"
  studio_assets ||--o{ studio_media_outputs : "asset_id"
  studio_assets ||--o{ generation_deliveries : "asset_id"
  studio_media_model_versions ||--o{ studio_media_requests : "model_version_id"
  studio_media_models ||--o{ studio_media_requests : "model_id"
  studio_credit_authorizations |o--o{ studio_media_jobs : "credit_authorization_id"
  studio_credit_authorizations |o--o{ studio_credit_recovery_intents : "authorization_id"
自动生成的整体图(按真实表结构,备查)
invideo AI 品牌库 ER 图

共享身份库 ER 图

共享身份库 ER 图
实测验证与设计原因 七个品牌相同,展开查看

实测验证(本机 MySQL 实库)

对照本机真实运行的数据库 studio_profile_invideo 和 studio_platform_identity,逐项核对本页的表、列、关系与说明。18 / 18 项通过。

查看全部核对项
核对项结果
表数 = 发布值 24通过
列数 = 289通过
外键数 = 34通过
检查约束数 = 66通过
触发器数 = 22通过
索引数(含主键) = 85通过
列定义与 pollo 逐列一致通过
外键与 pollo 完全一致通过
索引与 pollo 一致通过
触发器与 pollo 一致通过
外键 CSV 行数 = 实库外键数通过
mmd 实体 = 实库表通过
svg 标题 24 tables / 34 actual foreign keys = 实库通过
参考 SQL 表集合与列顺序 = 实库通过
15 张表通过
外键 CSV 行数 = 实库外键数通过
svg 标题与实库一致通过
参考 SQL 表集合与列顺序 = 实库通过

实库里的关系是这样的

  • 34 个外键里 31 个是复合外键,且都带着 owner_id,其余 3 个是单列外键;22 张子表都带 owner 外键。所以一行数据不可能指向别的用户的项目或会话。
  • 删除与更新规则全部是 RESTRICT / NO ACTION,没有任何级联:被引用的行不能被删除。
  • 品牌库与共享身份库之间的关系(owner_id、asset_id、model_id + model_version_id)没有数据库外键,靠应用层检查;我们逐表核对了现有数据,没有孤儿。

主动破坏测试(在事务里尝试,全部回滚)

尝试数据库的反应
外键:导出任务.project_id 改成不存在的项目被拒绝拒绝
复合外键:项目素材引用.project_id 改成别的用户的项目被拒绝(owner 与 project 必须成对)拒绝
触发器:已提交的消息动作不可改写拒绝
触发器:媒体产出不可改写拒绝
外键 RESTRICT:被会话引用的项目不能删除拒绝
复合外键:被引用的会话不能把 owner 改成别人拒绝
检查约束:Run 状态只能是约定的六个值拒绝
触发器:账本分录不可修改拒绝
触发器:账本分录不可删除拒绝
验证范围的说明:这几个库里的数据量很小(每个品牌库约 70–270 行),孤儿检查能证明现有数据没有违反关系,不能证明大数据量下的表现;主动破坏测试在 Pollo 库上执行,另外六个品牌库的列、外键、索引、触发器已逐项核对与 Pollo 完全一致。
为什么这样设计外键用真实数据库约束表达,复合外键把 owner/project/conversation 一起带上,所以任何一行数据都不可能指向别人的项目。跨库关系不画外键而用应用层检查,是因为不同库之间数据库本身保证不了,画出来反而误导(决策 D12、D17)。

invideo AI · 表结构

MySQL 8,字符集 utf8mb4,ID 为 char(36) ascii_bin。点击展开 DDL。

按职责分组

项目与会话 2 张
  • studio_projects:用户拥有的创作项目
  • studio_conversations:项目内的对话会话
Agent执行与并发控制 7 张
  • maa_bindings:业务会话与Native Maa会话映射、模型版本与未决操作
  • maa_runs:公开Run映射、状态与原生/公开助手证据摘要
  • maa_message_actions:原消息动作及幂等回执
  • maa_interactions:澄清卡片及Run归属
  • maa_interaction_actions:原卡片回答/取消动作
  • maa_model_change_actions:带预期模型版本的切换动作
  • maa_run_stop_actions:停止原Run的幂等动作与观测终态
画布、时间轴与素材引用 5 张
  • studio_canvases:项目当前画布文档
  • studio_canvas_revisions:历史画布修订
  • studio_video_documents:项目当前时间轴文档
  • studio_timeline_revisions:历史时间轴修订
  • studio_project_asset_refs:项目对用户级素材的引用;不复制素材所有权
媒体生成、结算恢复与导出 6 张
  • studio_media_requests:用户输入/Agent来源、报价、批准摘要与准入确认
  • studio_media_jobs:已批准媒体任务及状态机
  • studio_media_submission_attempts:一次提交的任务证据与不确定结果
  • studio_media_outputs:受验证的输出与素材映射
  • studio_credit_recovery_intents:积分结算恢复意图;不是用户钱包全账本
  • studio_export_jobs:时间轴导出队列和发布输出
既有generation兼容路径 22 张
  • generation_jobs:既有generation任务,不等同新PublicMedia任务
  • generation_reservations:既有任务费用预留
  • generation_development_budgets:既有开发测试预算
  • generation_deliveries:既有生成交付回执
  • owner_id:归属用户;不是允许跨用户查询的参数。
  • model_revision:并发准入版本;数据库默认0不能代替Native权威读回。
  • pending_model_change_id:未决模型切换引用,阻止其他writer绕过。
  • studio_users:用户账号记录;不导出密码哈希或任何业务行
  • studio_sessions:登录会话;这里只公开字段结构,不公开会话值
  • studio_assets:用户级素材元数据与存储键结构,项目仅引用素材ID
  • studio_media_models:公开媒体模型目录结构
  • studio_media_model_versions:媒体模型不可变版本/计价能力结构
  • studio_subscriptions:订阅状态持久化
  • studio_billing_cycles:订阅账期
  • studio_payment_orders:订单记录
  • studio_payment_events:支付事件幂等记录;不代表已有公开支付路由
  • studio_credit_accounts:用户积分账户
  • studio_credit_lots:积分批次
  • studio_credit_authorizations:费用预留与结算授权
  • studio_credit_authorization_allocations:授权对批次的分配
  • studio_credit_ledger_entries:积分流水
  • studio_credit_plan_versions:积分方案不可变版本

品牌库 studio_profile_invideo(24 张)

generation_deliveries 既有生成交付回执
CREATE TABLE `generation_deliveries` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `maa_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `tool_name` varchar(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_delivery_owner_project` (`owner_id`,`project_id`,`session_id`,`job_id`),
  KEY `generation_delivery_owned_asset` (`owner_id`,`asset_id`),
  KEY `generation_delivery_session_fk` (`session_id`),
  CONSTRAINT `generation_delivery_job_fk` FOREIGN KEY (`job_id`) REFERENCES `generation_jobs` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`, `job_id`) REFERENCES `generation_jobs` (`owner_id`, `project_id`, `session_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_session_fk` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_model_tool` CHECK ((((`model` = _ascii'seedance-2.0') and (`tool_name` = _ascii'seedance2_video_generator')) or ((`model` in (_ascii'minimax-h3',_ascii'seedance-2.5')) and (`tool_name` = _ascii'insai_video_generate'))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_development_budgets 既有开发测试预算
CREATE TABLE `generation_development_budgets` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_version` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits_limit` bigint unsigned NOT NULL,
  `credits_reserved` bigint unsigned NOT NULL DEFAULT '0',
  `calls_limit` int unsigned NOT NULL,
  `calls_reserved` int unsigned NOT NULL DEFAULT '0',
  `expires_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_budget_owner` (`owner_id`,`id`),
  CONSTRAINT `generation_budget_limits` CHECK (((`credits_limit` between 1 and 1000000000) and (`credits_reserved` <= `credits_limit`) and (`calls_limit` between 1 and 100) and (`calls_reserved` <= `calls_limit`))),
  CONSTRAINT `generation_budget_policy` CHECK ((`policy_version` = _latin1'operator-test/v1'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_jobs 既有generation任务,不等同新PublicMedia任务
CREATE TABLE `generation_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_conversation_id` char(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_run_id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `result_bytes` mediumblob,
  `updated_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_job_budget_owner` (`id`,`budget_id`,`owner_id`),
  UNIQUE KEY `generation_job_owned_scope` (`owner_id`,`project_id`,`session_id`,`id`),
  KEY `generation_job_owner` (`owner_id`,`project_id`,`session_id`),
  KEY `generation_job_session` (`session_id`),
  KEY `generation_job_budget` (`owner_id`,`budget_id`),
  CONSTRAINT `generation_job_budget` FOREIGN KEY (`owner_id`, `budget_id`) REFERENCES `generation_development_budgets` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_project` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_session` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_attempt` CHECK ((((`status` = _ascii'APPROVED') and (`attempts` = 0) and (`result_bytes` is null)) or ((`status` = _ascii'SUBMITTING') and (`attempts` = 1) and (`result_bytes` is null)) or ((`status` in (_ascii'SUCCEEDED',_ascii'FAILED',_ascii'UNKNOWN')) and (`attempts` = 1) and (`result_bytes` is not null)))),
  CONSTRAINT `generation_job_credit` CHECK ((`credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_reservations 既有任务费用预留
CREATE TABLE `generation_reservations` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `calls` int unsigned NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_reservation_job` (`job_id`,`budget_id`,`owner_id`),
  CONSTRAINT `generation_reservation_job` FOREIGN KEY (`job_id`, `budget_id`, `owner_id`) REFERENCES `generation_jobs` (`id`, `budget_id`, `owner_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_reservation_fixed` CHECK (((`credits` between 1 and 1000000000) and (`calls` = 1) and (`status` = _latin1'HELD')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_bindings 业务会话与Native Maa会话映射、模型版本与未决操作
CREATE TABLE `maa_bindings` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `model_revision` bigint unsigned NOT NULL DEFAULT '0',
  `pending_model_change_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_binding_business_scope` (`owner_id`,`project_id`,`conversation_id`),
  UNIQUE KEY `maa_binding_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `maa_binding_native_identity` (`native_cid`),
  KEY `maa_model_change_pending_fk` (`owner_id`,`project_id`,`conversation_id`,`id`,`pending_model_change_id`),
  CONSTRAINT `maa_binding_owned_conversation_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_pending_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `id`, `pending_model_change_id`) REFERENCES `maa_model_change_actions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_binding_receipt` CHECK ((((`status` = _ascii'dispatching') and (`native_cid` is null)) or ((`status` = _ascii'bound') and (`native_cid` is not null)))),
  CONSTRAINT `maa_model_binding_revision` CHECK ((`model_revision` <= 9007199254740991))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interaction_actions 原卡片回答/取消动作
CREATE TABLE `maa_interaction_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `interaction_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `response_action` varchar(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `values_payload` json NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_action_business_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_interaction_action_one_card` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`interaction_id`),
  CONSTRAINT `maa_interaction_action_owned_card_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `interaction_id`) REFERENCES `maa_interactions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_interaction_action_kind` CHECK ((`response_action` in (_latin1'submit',_latin1'cancel'))),
  CONSTRAINT `maa_interaction_action_status` CHECK ((`status` in (_latin1'dispatching',_latin1'accepted')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interactions 澄清卡片及Run归属
CREATE TABLE `maa_interactions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_payload` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_native_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`native_iid`),
  UNIQUE KEY `maa_interaction_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`id`),
  CONSTRAINT `maa_interaction_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_message_actions 原消息动作及幂等回执
CREATE TABLE `maa_message_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `proposed_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_action_business_identity` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  KEY `maa_action_owned_run_fk` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_action_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_receipt` CHECK ((((`status` = _latin1'dispatching') and (`public_run_id` is null)) or ((`status` = _latin1'accepted') and (`public_run_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_model_change_actions 带预期模型版本的切换动作
CREATE TABLE `maa_model_change_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `expected_revision` bigint unsigned NOT NULL,
  `from_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `from_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_attempt` bigint unsigned NOT NULL,
  `receipt_payload` json DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_model_change_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_model_change_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  CONSTRAINT `maa_model_change_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_attempt` CHECK ((`dispatch_attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_model_change_expected` CHECK ((`expected_revision` <= 9007199254740990)),
  CONSTRAINT `maa_model_change_status` CHECK ((((cast(`status` as char charset binary) = _latin1'dispatching') and (`receipt_payload` is null)) or ((cast(`status` as char charset binary) in (_latin1'accepted',_latin1'rejected')) and (`receipt_payload` is not null) and (json_type(`receipt_payload`) = _latin1'OBJECT')))),
  CONSTRAINT `maa_model_change_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_run_stop_actions 停止原Run的幂等动作与观测终态
CREATE TABLE `maa_run_stop_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stop_action` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt` bigint unsigned NOT NULL,
  `terminal_run_status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_stop_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_stop_one_run` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_stop_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_stop_action` CHECK ((`stop_action` = _latin1'interrupt')),
  CONSTRAINT `maa_stop_attempt` CHECK ((`attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_stop_state` CHECK ((((`status` = _latin1'unknown') and (`terminal_run_status` is null)) or ((`status` = _latin1'terminal_observed') and (`terminal_run_status` is not null) and (`terminal_run_status` in (_latin1'completed',_latin1'failed',_latin1'cancelled'))))),
  CONSTRAINT `maa_stop_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_runs 公开Run映射、状态与原生/公开助手证据摘要
CREATE TABLE `maa_runs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `native_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `public_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `native_assistant_bytes` bigint unsigned NOT NULL DEFAULT '0',
  `assistant_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  `last_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_run_native_identity` (`native_rid`),
  UNIQUE KEY `maa_run_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  KEY `maa_run_owned_list` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  CONSTRAINT `maa_run_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_run_source_evidence` CHECK ((((`native_assistant_sha256` is null) and (`public_assistant_sha256` is null) and (`native_assistant_bytes` = 0) and (`assistant_event_seq` = 0)) or ((`native_assistant_sha256` is not null) and (`public_assistant_sha256` is not null) and (`native_assistant_bytes` > 0) and (`assistant_event_seq` > 0) and (`assistant_event_seq` <= `last_event_seq`)))),
  CONSTRAINT `maa_run_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'waiting_interaction',_latin1'completed',_latin1'failed',_latin1'cancelled')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvas_revisions 历史画布修订
CREATE TABLE `studio_canvas_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_canvas_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_canvas_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_canvases` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_canvas_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_canvas_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_canvas_revision_kind` CHECK ((`kind` = _latin1'canvas')),
  CONSTRAINT `studio_canvas_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_canvas_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_canvas_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvases 项目当前画布文档
CREATE TABLE `studio_canvases` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_canvas_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_canvas_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_conversations 项目内的对话会话
CREATE TABLE `studio_conversations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_conversation_owned_scope` (`owner_id`,`project_id`,`id`),
  KEY `studio_conversation_list` (`owner_id`,`project_id`,`created_at`,`id`),
  CONSTRAINT `studio_conversation_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_recovery_intents 积分结算恢复意图;不是用户钱包全账本
CREATE TABLE `studio_credit_recovery_intents` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `intent_bytes` mediumblob NOT NULL,
  `intent_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_intent_job_kind` (`job_id`,`kind`),
  KEY `credit_intent_recovery` (`state`,`updated_at`,`id`),
  KEY `credit_intent_owned_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `credit_intent_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `credit_intent_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_intent_kind` CHECK (((`kind` in (_latin1'RESERVE_BIND',_latin1'CAPTURE',_latin1'RELEASE')) and (((`kind` = _latin1'RESERVE_BIND') and (`authorization_id` is null)) or ((`kind` in (_latin1'CAPTURE',_latin1'RELEASE')) and (`authorization_id` is not null))))),
  CONSTRAINT `credit_intent_lease` CHECK ((((`state` = _latin1'leased') and (`lease_token_sha256` is not null) and regexp_like(`lease_token_sha256`,_latin1'^[a-f0-9]{64}$') and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`state` <> _latin1'leased') and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `credit_intent_payload_sha` CHECK ((`intent_sha256` = lower(sha2(`intent_bytes`,256)))),
  CONSTRAINT `credit_intent_payload_size` CHECK ((length(`intent_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_intent_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_intent_state` CHECK ((`state` in (_latin1'pending',_latin1'leased',_latin1'confirmed',_latin1'unknown'))),
  CONSTRAINT `credit_intent_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_export_jobs 时间轴导出队列和发布输出
CREATE TABLE `studio_export_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `document_revision` bigint unsigned NOT NULL,
  `input_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_json` json NOT NULL,
  `output_json` json DEFAULT NULL,
  `error_code` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
  `lease_token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_export_input` (`project_id`,`document_revision`,`input_hash`),
  KEY `studio_export_claim` (`status`,`created_at`,`id`),
  KEY `studio_export_owner_list` (`owner_id`,`project_id`,`created_at`,`id`),
  KEY `studio_export_expiry` (`status`,`lease_until`),
  CONSTRAINT `studio_export_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_export_lease` CHECK ((((`status` = _latin1'running') and (`lease_token_hash` is not null) and (`lease_until` is not null)) or ((`status` <> _latin1'running') and (`lease_token_hash` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_export_output` CHECK ((((`status` = _latin1'succeeded') and (`output_json` is not null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'queued',_latin1'running')) and (`output_json` is null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'failed',_latin1'unknown')) and (`output_json` is null) and (`error_code` <> _latin1'')))),
  CONSTRAINT `studio_export_revision` CHECK ((`document_revision` between 1 and 2147483647)),
  CONSTRAINT `studio_export_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'succeeded',_latin1'failed',_latin1'unknown')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_jobs 已批准媒体任务及状态机
CREATE TABLE `studio_media_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_approval_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `approval_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credit_authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_mode` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_expires_at` datetime(6) DEFAULT NULL,
  `status` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `client_cancel_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_requested_at` datetime(6) DEFAULT NULL,
  `staged_asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `staged_asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_job_request` (`request_id`),
  UNIQUE KEY `studio_media_job_client` (`owner_id`,`client_approval_id`),
  UNIQUE KEY `studio_media_job_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_job_owned_request` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`),
  KEY `studio_media_job_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_job_queue` (`status`,`updated_at`,`id`),
  KEY `studio_media_job_request_fk` (`owner_id`,`project_id`,`conversation_id`,`request_id`,`request_sha256`,`quote_sha256`),
  CONSTRAINT `studio_media_job_request_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `request_id`, `request_sha256`, `quote_sha256`) REFERENCES `studio_media_requests` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`, `quote_sha256`),
  CONSTRAINT `studio_media_job_authorization` CHECK ((((`credit_authorization_id` is null) and (`authorization_mode` is null) and (`authorization_expires_at` is null) and (`status` in (_latin1'approval_pending',_latin1'cancelled'))) or ((`credit_authorization_id` is not null) and (`authorization_mode` is not null) and (`authorization_mode` in (_latin1'commercial',_latin1'test_allowance')) and (`authorization_expires_at` is not null) and (`authorization_expires_at` > `created_at`) and (`status` <> _latin1'approval_pending')))),
  CONSTRAINT `studio_media_job_cancel` CHECK ((((`client_cancel_id` is null) and (`cancel_request_sha256` is null) and (`cancel_requested_at` is null)) or ((`client_cancel_id` is not null) and (`cancel_request_sha256` is not null) and (`cancel_requested_at` is not null) and (`cancel_requested_at` between `created_at` and `updated_at`)))),
  CONSTRAINT `studio_media_job_lease` CHECK ((((`status` in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is not null) and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`status` not in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_media_job_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_job_staged` CHECK ((((`staged_asset_id` is null) and (`staged_asset_sha256` is null)) or ((`staged_asset_id` is not null) and (`staged_asset_sha256` is not null)))),
  CONSTRAINT `studio_media_job_status` CHECK ((`status` in (_latin1'approval_pending',_latin1'queued',_latin1'leased',_latin1'submitting',_latin1'running',_latin1'submission_unknown',_latin1'succeeded',_latin1'failed',_latin1'cancelled'))),
  CONSTRAINT `studio_media_job_task` CHECK (((`status` not in (_latin1'running',_latin1'succeeded')) or (`provider_task_id` is not null))),
  CONSTRAINT `studio_media_job_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_outputs 受验证的输出与素材映射
CREATE TABLE `studio_media_outputs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `ordinal` int unsigned NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_result_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_output_job` (`job_id`,`ordinal`),
  KEY `studio_media_output_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`),
  CONSTRAINT `studio_media_output_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`),
  CONSTRAINT `studio_media_output_ordinal` CHECK ((`ordinal` = 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_requests 用户输入/Agent来源、报价、批准摘要与准入确认
CREATE TABLE `studio_media_requests` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_bytes` mediumblob NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quoted_credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_snapshot_bytes` mediumblob,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_request_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_request_quoted` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`,`quote_sha256`),
  UNIQUE KEY `studio_media_request_client` (`owner_id`,`project_id`,`conversation_id`,`client_request_id`),
  KEY `studio_media_request_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_request_source_fk` (`owner_id`,`project_id`,`conversation_id`,`source_binding_id`,`source_run_id`),
  CONSTRAINT `studio_media_request_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`),
  CONSTRAINT `studio_media_request_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `source_binding_id`, `source_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`),
  CONSTRAINT `studio_media_quote_credits` CHECK ((`quoted_credits` between 1 and 1000000000)),
  CONSTRAINT `studio_media_quote_sha` CHECK ((`quote_sha256` = lower(sha2(`quote_bytes`,256)))),
  CONSTRAINT `studio_media_quote_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)))),
  CONSTRAINT `studio_media_request_origin` CHECK ((((`origin` in (_latin1'ui',_latin1'user_edit')) and (`source_binding_id` is null) and (`source_run_id` is null) and (`source_assistant_sha256` is null) and (`source_snapshot_bytes` is null)) or ((`origin` = _latin1'agent') and (`source_binding_id` is not null) and (`source_run_id` is not null) and (`source_assistant_sha256` is not null) and (`source_snapshot_bytes` is not null) and (length(`source_snapshot_bytes`) between 1 and 65536) and (`source_assistant_sha256` = lower(sha2(`source_snapshot_bytes`,256)))))),
  CONSTRAINT `studio_media_request_sha` CHECK ((`request_sha256` = lower(sha2(`request_bytes`,256)))),
  CONSTRAINT `studio_media_request_size` CHECK (((length(`request_bytes`) between 1 and 65536) and (length(`quote_bytes`) between 1 and 65536)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_submission_attempts 一次提交的任务证据与不确定结果
CREATE TABLE `studio_media_submission_attempts` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt_no` int unsigned NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stage` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_started_at` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `terminal_receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `reconciliation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_attempt_job` (`job_id`,`attempt_no`),
  UNIQUE KEY `studio_media_attempt_idempotency` (`provider_idempotency_key`),
  KEY `studio_media_attempt_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `studio_media_attempt_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `studio_media_attempt_number` CHECK ((`attempt_no` = 1)),
  CONSTRAINT `studio_media_attempt_state` CHECK ((((`stage` = _latin1'prepared') and (`dispatch_started_at` is null) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'dispatching',_latin1'unknown')) and (`dispatch_started_at` is not null) and (`dispatch_started_at` >= `created_at`) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` = _latin1'acknowledged') and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'completed',_latin1'failed')) and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is not null)) or ((`stage` = _latin1'reconciled_not_sent') and (`dispatch_started_at` is not null) and (`provider_task_id` is null) and (`reconciliation_sha256` is not null)))),
  CONSTRAINT `studio_media_attempt_times` CHECK (((`updated_at` >= `created_at`) and ((`dispatch_started_at` is null) or (`dispatch_started_at` between `created_at` and `updated_at`))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_project_asset_refs 项目对用户级素材的引用;不复制素材所有权
CREATE TABLE `studio_project_asset_refs` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `detached_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`project_id`,`asset_id`),
  KEY `studio_project_asset_owned_page` (`owner_id`,`project_id`,`asset_id`),
  KEY `studio_project_asset_reverse` (`owner_id`,`asset_id`,`project_id`),
  CONSTRAINT `studio_project_asset_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_project_asset_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_project_asset_state` CHECK ((((`state` = _latin1'attached') and (`detached_at` is null)) or ((`state` = _latin1'detached') and (`detached_at` is not null) and (`detached_at` = `updated_at`)))),
  CONSTRAINT `studio_project_asset_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_projects 用户拥有的创作项目
CREATE TABLE `studio_projects` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_project_owner_identity` (`owner_id`,`id`),
  KEY `studio_project_owner_list` (`owner_id`,`created_at`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_timeline_revisions 历史时间轴修订
CREATE TABLE `studio_timeline_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_timeline_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_timeline_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_video_documents` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_timeline_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_timeline_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_timeline_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_timeline_revision_kind` CHECK ((`kind` = _latin1'timeline')),
  CONSTRAINT `studio_timeline_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_timeline_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_timeline_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_video_documents 项目当前时间轴文档
CREATE TABLE `studio_video_documents` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_timeline_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_document_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_document_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
共享身份库(15 张)与积分账本 七个品牌相同,展开查看
studio_assets
CREATE TABLE `studio_assets` (
  `id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `storage_key` varchar(512) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `publication_state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT 'visible',
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_asset_owner_digest` (`owner_id`,`sha256`),
  KEY `studio_asset_owner_list` (`owner_id`,`created_at`,`id`),
  KEY `studio_asset_visible_list` (`owner_id`,`publication_state`,`created_at`,`id`),
  CONSTRAINT `studio_asset_publication_state` CHECK (CAST(publication_state AS BINARY) IN ('pending','visible')),
  CONSTRAINT `studio_asset_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_billing_cycles
CREATE TABLE `studio_billing_cycles` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `payment_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_invoice_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `billing_cycle_owned` (`owner_id`,`id`),
  UNIQUE KEY `billing_cycle_period` (`subscription_id`,`period_start`),
  UNIQUE KEY `billing_cycle_invoice` (`provider_key`,`provider_account`,`provider_invoice_id`),
  UNIQUE KEY `billing_cycle_order` (`payment_order_id`),
  KEY `billing_cycle_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  KEY `billing_cycle_order_fk` (`owner_id`,`payment_order_id`,`plan_version_id`),
  KEY `billing_cycle_order_provider_fk` (`owner_id`,`payment_order_id`,`provider_key`,`provider_account`),
  CONSTRAINT `billing_cycle_order_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `plan_version_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `plan_version_id`),
  CONSTRAINT `billing_cycle_order_provider_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_amount` CHECK ((`grant_credits` between 1 and 1000000000)),
  CONSTRAINT `billing_cycle_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `billing_cycle_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `billing_cycle_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `billing_cycle_state` CHECK (((`state` in (_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `billing_cycle_times` CHECK (((`period_end` > `period_start`) and (`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_accounts
CREATE TABLE `studio_credit_accounts` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `unit` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`),
  CONSTRAINT `credit_account_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `credit_account_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_account_status` CHECK ((`status` in (_latin1'open',_latin1'suspended'))),
  CONSTRAINT `credit_account_unit` CHECK ((`unit` = _latin1'credits'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorization_allocations
CREATE TABLE `studio_credit_authorization_allocations` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `allocated_credits` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`authorization_id`,`lot_id`),
  UNIQUE KEY `credit_allocation_owned` (`owner_id`,`authorization_id`,`lot_id`),
  KEY `credit_allocation_lot` (`owner_id`,`lot_id`,`authorization_id`),
  CONSTRAINT `credit_allocation_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_amount` CHECK ((`allocated_credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorizations
CREATE TABLE `studio_credit_authorizations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `profile` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `reservation_bytes` mediumblob NOT NULL,
  `reservation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_bytes` mediumblob NOT NULL,
  `policy_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_authorization_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_authorization_job` (`owner_id`,`profile`,`job_id`),
  CONSTRAINT `credit_authorization_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_authorization_amount` CHECK ((`credits` between 1 and 1000000000)),
  CONSTRAINT `credit_authorization_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_authorization_policy_sha` CHECK ((`policy_sha256` = lower(sha2(`policy_bytes`,256)))),
  CONSTRAINT `credit_authorization_policy_size` CHECK ((length(`policy_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_profile` CHECK ((`profile` in (_latin1'topview',_latin1'pollo',_latin1'invideo',_latin1'libtv',_latin1'flova',_latin1'creatok',_latin1'creatify'))),
  CONSTRAINT `credit_authorization_reservation_sha` CHECK ((`reservation_sha256` = lower(sha2(`reservation_bytes`,256)))),
  CONSTRAINT `credit_authorization_reservation_size` CHECK ((length(`reservation_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_authorization_state` CHECK ((`state` in (_latin1'HELD',_latin1'CAPTURED',_latin1'RELEASED'))),
  CONSTRAINT `credit_authorization_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_ledger_entries
CREATE TABLE `studio_credit_ledger_entries` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `operation_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_delta` bigint NOT NULL,
  `held_delta` bigint NOT NULL,
  `captured_delta` bigint NOT NULL,
  `facts_bytes` mediumblob NOT NULL,
  `facts_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_entry_operation` (`owner_id`,`operation_key`,`lot_id`),
  KEY `credit_entry_page` (`owner_id`,`created_at`,`id`),
  KEY `credit_entry_lot_fk` (`owner_id`,`lot_id`),
  KEY `credit_entry_allocation_fk` (`owner_id`,`authorization_id`,`lot_id`),
  CONSTRAINT `credit_entry_allocation_fk` FOREIGN KEY (`owner_id`, `authorization_id`, `lot_id`) REFERENCES `studio_credit_authorization_allocations` (`owner_id`, `authorization_id`, `lot_id`),
  CONSTRAINT `credit_entry_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_delta` CHECK ((((`kind` = _latin1'grant') and (`authorization_id` is null) and (`grant_delta` between 1 and 1000000000) and (`held_delta` = 0) and (`captured_delta` = 0)) or ((`kind` = _latin1'hold') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between 1 and 1000000000) and (`captured_delta` = 0)) or ((`kind` = _latin1'capture') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`captured_delta` between 1 and 1000000000) and (`held_delta` = -(`captured_delta`))) or ((`kind` = _latin1'release') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between -(1000000000) and -(1)) and (`captured_delta` = 0)))),
  CONSTRAINT `credit_entry_facts_sha` CHECK ((`facts_sha256` = lower(sha2(`facts_bytes`,256)))),
  CONSTRAINT `credit_entry_facts_size` CHECK ((length(`facts_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_entry_operation_key` CHECK ((((`kind` = _latin1'grant') and (`operation_key` = concat(`lot_id`,_latin1':grant'))) or ((`kind` <> _latin1'grant') and (`operation_key` = concat(`authorization_id`,_latin1':',`kind`)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_lots
CREATE TABLE `studio_credit_lots` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `held_credits` bigint unsigned NOT NULL,
  `captured_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_cycle_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_lot_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_lot_order` (`source_order_id`),
  UNIQUE KEY `credit_lot_cycle` (`source_cycle_id`),
  KEY `credit_lot_eligible` (`owner_id`,`expires_at`,`id`),
  KEY `credit_lot_order_fk` (`owner_id`,`source_order_id`),
  KEY `credit_lot_cycle_fk` (`owner_id`,`source_cycle_id`),
  CONSTRAINT `credit_lot_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_lot_cycle_fk` FOREIGN KEY (`owner_id`, `source_cycle_id`) REFERENCES `studio_billing_cycles` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_order_fk` FOREIGN KEY (`owner_id`, `source_order_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_counters` CHECK (((`grant_credits` between 1 and 1000000000) and (`held_credits` <= 1000000000) and (`captured_credits` <= 1000000000) and ((`held_credits` + `captured_credits`) <= `grant_credits`))),
  CONSTRAINT `credit_lot_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_lot_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_lot_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_lot_source` CHECK ((((`source_order_id` is not null) and (`source_cycle_id` is null)) or ((`source_order_id` is null) and (`source_cycle_id` is not null)))),
  CONSTRAINT `credit_lot_times` CHECK (((`expires_at` > `valid_from`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_plan_versions
CREATE TABLE `studio_credit_plan_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `credit_validity_seconds` int unsigned NOT NULL,
  `billing_interval_months` int unsigned NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  `retired_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_plan_version_key` (`plan_key`,`version_key`),
  CONSTRAINT `credit_plan_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `credit_plan_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `credit_plan_duration` CHECK (((`credit_validity_seconds` between 1 and 31622400) and ((`retired_at` is null) or (`retired_at` >= `published_at`)))),
  CONSTRAINT `credit_plan_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_plan_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_plan_kind` CHECK (((`kind` in (_latin1'subscription',_latin1'topup')) and (((`kind` = _latin1'subscription') and (`billing_interval_months` in (1,3,12))) or ((`kind` = _latin1'topup') and (`billing_interval_months` = 0)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_model_versions
CREATE TABLE `studio_media_model_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `config_bytes` mediumblob NOT NULL,
  `config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_version_key` (`model_id`,`version_key`),
  UNIQUE KEY `studio_media_version_owned_model` (`model_id`,`id`),
  CONSTRAINT `studio_media_version_model_fk` FOREIGN KEY (`model_id`) REFERENCES `studio_media_models` (`id`),
  CONSTRAINT `studio_media_version_sha` CHECK ((`config_sha256` = lower(sha2(`config_bytes`,256)))),
  CONSTRAINT `studio_media_version_size` CHECK ((length(`config_bytes`) between 1 and 65536))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_models
CREATE TABLE `studio_media_models` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `display_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_model_key` (`model_key`),
  KEY `studio_media_model_published_fk` (`id`,`published_version_id`),
  CONSTRAINT `studio_media_model_published_fk` FOREIGN KEY (`id`, `published_version_id`) REFERENCES `studio_media_model_versions` (`model_id`, `id`),
  CONSTRAINT `studio_media_model_kind` CHECK ((`kind` = _ascii'video')),
  CONSTRAINT `studio_media_model_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_model_status` CHECK (((`status` in (_ascii'draft',_ascii'active',_ascii'disabled')) and ((`status` <> _ascii'active') or (`published_version_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_events
CREATE TABLE `studio_payment_events` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_event_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `body_bytes` mediumblob NOT NULL,
  `body_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_event_provider` (`provider_key`,`provider_account`,`provider_event_id`),
  KEY `payment_event_order_fk` (`owner_id`,`order_id`,`provider_key`,`provider_account`),
  KEY `payment_event_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  CONSTRAINT `payment_event_order_fk` FOREIGN KEY (`owner_id`, `order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_event_body_sha` CHECK ((`body_sha256` = lower(sha2(`body_bytes`,256)))),
  CONSTRAINT `payment_event_body_size` CHECK ((length(`body_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_event_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_event_state` CHECK ((`state` in (_latin1'VERIFIED',_latin1'PROCESSED'))),
  CONSTRAINT `payment_event_subject` CHECK (((`order_id` is not null) or (`subscription_id` is not null))),
  CONSTRAINT `payment_event_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_orders
CREATE TABLE `studio_payment_orders` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_order_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_order_owned` (`owner_id`,`id`),
  UNIQUE KEY `payment_order_owned_plan` (`owner_id`,`id`,`plan_version_id`),
  UNIQUE KEY `payment_order_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `payment_order_client` (`owner_id`,`client_action_id`),
  UNIQUE KEY `payment_order_provider_key` (`provider_key`,`provider_account`,`provider_idempotency_key`),
  UNIQUE KEY `payment_order_provider_id` (`provider_key`,`provider_account`,`provider_order_id`),
  KEY `payment_order_plan_fk` (`plan_version_id`),
  CONSTRAINT `payment_order_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `payment_order_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_order_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `payment_order_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `payment_order_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `payment_order_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_order_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_order_state` CHECK (((`state` in (_latin1'CREATING',_latin1'CREATE_UNKNOWN',_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`provider_order_id` is not null) and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `payment_order_times` CHECK (((`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_sessions
CREATE TABLE `studio_sessions` (
  `token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `user_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `revoked_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`token_hash`),
  KEY `studio_session_expiry` (`expires_at`),
  KEY `studio_session_user` (`user_id`,`expires_at`),
  CONSTRAINT `studio_session_user_fk` FOREIGN KEY (`user_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_session_times` CHECK ((`expires_at` > `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_subscriptions
CREATE TABLE `studio_subscriptions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_subscription_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `cancel_at_period_end` tinyint(1) NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `subscription_owned` (`owner_id`,`id`),
  UNIQUE KEY `subscription_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `subscription_provider` (`provider_key`,`provider_account`,`provider_subscription_id`),
  KEY `subscription_plan_fk` (`plan_version_id`),
  CONSTRAINT `subscription_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `subscription_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `subscription_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `subscription_state` CHECK (((`state` in (_latin1'PENDING',_latin1'ACTIVE',_latin1'PAST_DUE',_latin1'CANCELLED',_latin1'ENDED')) and (`cancel_at_period_end` in (0,1)))),
  CONSTRAINT `subscription_times` CHECK (((`period_end` > `period_start`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_users
CREATE TABLE `studio_users` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `email` varchar(254) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `password_hash` varbinary(72) NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT '1',
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_user_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

积分账本链路

plan_versions → payment_orders(CREATING→PAID)→ payment_events(VERIFIED)→ credit_lots(带适用模型与品牌)→ ledger_entries(grant / hold / capture / release,只追加)→ authorizations / allocations。批准生成时按批次分配冻结额度,扣费与释放都有幂等键。
为什么这样设计账本只追加、用触发器禁止修改,才有可信的对账;批准时按批次分配冻结额度,才能在失败时准确释放。约束写在数据库里而不是应用里,是因为应用会有 bug,而约束不会(决策 D3、D17)。

invideo AI · 接口清单

共 55 个公开接口,取自源码路由盘点。路径都以 /v1 开头;写操作需要会话 Cookie、可信 Host/Origin 与 CSRF;带品牌的路径用 ?profile=invideo;共享的登录、素材、模型目录路径不接受 profile 参数。

Agent/统一动作 7 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactionsrunInteractions200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactions/{interaction_id}/responsesrespondInteractionclient_action_id, card_sha256, action, values202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/interaction-actions/{client_action_id}interactionAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changeschangeConversationModelclient_action_id, expected_revision, agent_model, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changes/{client_action_id}modelChangeAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stopstopRunclient_action_id, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/stop-actions/{client_action_id}stopAction200
Asset 4 个
方法路径operationId请求字段成功
GET/v1/assetslistAssets200
POST/v1/assetsuploadAssetfile201
GET/v1/assets/{asset_id}getAsset200
GET/v1/assets/{asset_id}/contentassetContent200/206
LoginSession 1 个
方法路径operationId请求字段成功
POST/v1/auth/logoutlogout200
MaaBinding 5 个
方法路径operationId请求字段成功
GET/v1/maa/model-optionsmaaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/messagessendMaaMessageclient_action_id, content202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/actions/{client_action_id}getMaaAction200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stategetMaaState200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/eventsmaaEvents200
Media 11 个
方法路径operationId请求字段成功
GET/v1/media/modelspublicMediaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requestscreateMediaRequestclient_request_id, model_id, model_version_id, prompt, duration_seconds, ratio, resolution, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requestsmediaRequestByClient200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}getMediaRequest200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}getMediaJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}/outputgetMediaOutput200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/jobmediaJobByRequest200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/approveapproveMediaRequestclient_approval_id, request_sha256, quote_sha256200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/from-maacreateMediaRequestFromMaainput, source_run_id, source_assistant_sha256, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobslistMediaJobs200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/intentslistMediaIntents200
Other 9 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/canvas/revisionslistCanvasRevisions200
GET/v1/projects/{project_id}/canvas/revisions/{revision}getCanvasRevision200
POST/v1/projects/{project_id}/canvas/revisions/{revision}/restorerestoreCanvasRevisionexpected_revision200
GET/v1/projects/{project_id}/document/revisionslistTimelineRevisions200
GET/v1/projects/{project_id}/document/revisions/{revision}getTimelineRevision200
POST/v1/projects/{project_id}/document/revisions/{revision}/restorerestoreTimelineRevisionexpected_revision200
GET/v1/projects/{project_id}/assetslistProjectAssets200
POST/v1/projects/{project_id}/assetsattachProjectAssetasset_id, expected_revision200
POST/v1/projects/{project_id}/assets/{asset_id}/detachdetachProjectAssetexpected_revision200
Project 15 个
方法路径operationId请求字段成功
GET/v1/projectslistProjects200
POST/v1/projectscreateProjecttitle201
GET/v1/projects/{project_id}getProject200
GET/v1/projects/{project_id}/conversationslistConversations200
POST/v1/projects/{project_id}/conversationscreateConversationtitle201
GET/v1/projects/{project_id}/conversations/{conversation_id}getConversation200
GET/v1/projects/{project_id}/canvasgetCanvas200
POST/v1/projects/{project_id}/canvassaveCanvasexpected_revision, state200
GET/v1/projects/{project_id}/documentgetDocument200
POST/v1/projects/{project_id}/documentsaveDocumentexpected_revision, state200
GET/v1/projects/{project_id}/export-jobslistExportJobs200
POST/v1/projects/{project_id}/export-jobsqueueExportdocument_revision, output?202
GET/v1/projects/{project_id}/export-jobs/{job_id}getExportJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindinggetMaaBinding200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindingbindMaaagent_model XOR use_default:true200
User 3 个
方法路径operationId请求字段成功
POST/v1/auth/registerregisteremail, password201
POST/v1/auth/loginloginemail, password200
GET/v1/auth/sessionsession200

invideo AI 自己公开的接口调用流程(来自其官方资料)

步骤调用关系操作证据
打开项目编辑用户 → 项目入口进入 Project,再从 Notebook 的 Agent Chat 找到目标 Timeline。W
准备素材编辑用户 → 项目入口上传媒体并用文件夹整理;等待上传完成后再把它用于 Agent 工作。W
提交剪辑目标编辑用户 → Agent编排描述片段、长度和保留内容;项目中的 Agent 可共享创作背景并接收各自任务。W
检查Agent结果Agent编排 → 编辑状态Agent 更新剪辑后,用户在 Monitor/Timeline 检查可继续手改的结果;此处候选指待检查的结果。W
人工细调编辑用户 → 编辑状态手改剪辑,或把局部修订要求发回 Agent;人工与 Agent 面向同一 Timeline。W
保留方向与历史编辑用户 → 编辑状态用 Branch 尝试替代剪辑,Tag 标记阶段,History 返回之前状态。W
发送审阅版本编辑用户 → 交付模块选择 Timeline 建 Playlist 审阅链接,设置观看/下载权限并收集时间码反馈。W
导出作品编辑用户 → 交付模块选择范围和参数导出视频、音频、静帧或工程,再检查最终文件。W
可选外部接入外部Agent → 项目入口经 https://mcp.invideo.io/mcp 授权后使用 Editor 能力;没有调用任何工具或取得参数 schema。A
为什么这样设计所有写动作都带客户端幂等键(client_action_id / client_request_id / client_approval_id),重复提交返回原回执;报价和批准分成两个接口;停止、切换模型、澄清卡回答都是「动作」而不是「状态修改」,所以每一步都能查回执。这样前端可以放心地重试,后端也不会因为重试产生第二次副作用(决策 D2、D8、D14)。

invideo AI · 代码与下载

invideo-replica.zip
1.1 MB · 54 个文件 · SHA-256 93e68e0022be0bf46af283e3389b8932b5f058b1eb2537e972770fa65051ecdc
下载:invideo-replica.zip · 后端与合同 · 全部代码
在线试用:http://119.23.106.60:8103/。测试环境,整套独立部署(前端、BFF、Maa 智能体、数据库),可以注册、对话、生成视频(Seedance,测试积分)。页面右下角有“内部复刻研究”标识。
目录内容
frontend/invideo AI 前端:<code>candidate-8875-unified-v22.html</code> 及它引用的全部脚本与样式
contracts/本品牌的表结构 SQL、ER(svg / mmd)、外键清单;接口目录(json / csv);共享身份库表结构
README.md怎么运行、需要自备什么

运行前端

python3 -m http.server -d frontend 8791
# 打开 http://127.0.0.1:8791/agent-integration/apps-production-brand-v14/invideo/candidate-8875-unified-v22.html
后端和全部品牌:Go 后端、Python 媒体桥、全部测试和 7 个品牌的合同在 backend-and-contracts.zip;all-brands-code.zip 是全部。包里没有任何凭据、数据库备份或媒体文件,已做敏感串扫描。
需自备:MySQL 与账号(按 contracts 建库)、Maa 智能体服务(MAA_AGENT_ROOT)、模型网关与对象存储配置。静态前端单独打开不需要后端,但登录、对话、生成、导出需要整套服务。

复刻完成度(审计 2026-10-19)

结论:不是完美复刻。官方资料里的 58 项能力:实测跑通 1、只有通用对话 1、后端有但没跑过 11、没有后端 45。

真正跑通的:脚本协写与逐镜头规划。

界面对照(首页(已登录 Agent Two)):真实页面采集到 18 个控件 / 标题文字,复刻页找到 17 个;位置偏差中位数 200 px(1/17 个在 24 px 内)。缺的有:Agent Two Lite。

逐个点击(已登录首页):123 个可点控件,点了有效 20 个,只提示“此功能正在接入” 10 个,没反应或只变样式 89 个,禁用 4 个,未点(发送 / 删除 / 退出等)0 个。

只提示“正在接入”的控件(10 次点击,10 种)

Upgrade、Agent、Editor、Product Ads、Create AI shorts、Use my script、Agents & models、Projects、Workflows、Explore

点了没反应或只变样式的控件(89 次点击,30 种)

Upgrade with 55% off、Dismiss offer banner、Create a first cut、Use my Premiere Pro / DaVinci Resolve project、Home ×2、(图标) ×23、Library ×2、Explore ×2、You ×2、Create new、See all ×5、Seedance 2.5 ×3、Premium video with reference images, video & audio ×2、Seedance 2.0 ×3、GPT Image 2.5 Sunburst ×4、OpenAI's most capable image generation and editing、GPT Image 2.5 Flare ×3、OpenAI's fastest high-quality image model、MiniMax H3 Max ×3、fal's post-trained MiniMax-H3, tuned for prompt ad、GPT Image 2 ×4、Seed Audio 1.0 ×4、Prompt-driven audio scenes in 20 languages — voice、New agent ×2、Looks ×3、Boards ×2、Angles ×3、Bold Text Behind ×3、Difference Text ×2、Gradient Center Text ×3

复刻页自报的绑定状态(页面内嵌的绑定清单):原生工作区控件(新建项目、会话、登录等)8 / 10 个没有绑定到原页面的控件,由统一的工作区条代替;路由 canvas、timeline、assets 缺失或只做了一部分;

首屏截图对照(左:真实产品 invideo/auth-core/home.jpg;右:复刻页)· 粗粒度差异 11%

真实截图是测试账号登录后的画面,内容(作品、项目、横幅)本来就不同,差异百分比只作参考,重点看布局。

invideo AI:左为真实产品截图,右为复刻页

LibTV · 总览

重点LibTV 看这三行就够
  • 它是什么:画布 + 故事板 + Agent;技能社区;Agent 交互:技能固定版本绑定会话;预算只提醒。
  • 我们借鉴:技能版本固定。<b>不采纳</b>“只提醒不拦截”的预算(决策 D1 D9)。
  • 复刻现状:5 项已验证,在线试用 http://119.23.106.60:8104/;后端与其余六个品牌共用一套(24 张表、55 个接口),所以 ER / 表结构 / 接口页的通用部分默认折叠。

以 Agent、Skill 社区和节点画布承载影视创作;公开页面可见多类生成和影视工具,客户端代码显示独立项目、会话、素材、任务及版本。

证据:W 官方说明,A 官方接口合同,C 客户端代码,O 实测。“未实测”= 只核对了官方页面,没有上传素材或生成。

能力目录
34 项,分 11 类
入口
首页 + 节点画布 + TV Director
复刻后端
24 张表 · 34 个外键 · 85 个索引

能力目录

智能 Agent 与 Skill 6 项
能力说明证据限制
LibTV Agent BETA在影视创作中提供 Agent 对话入口。WBeta 运行能力、任务权限和计费未测
流式回复与断线恢复客户端先补会话历史再接续实时消息。C静态 WS 解析,不是已抓到登录帧;seq/replay 保证未知
生成审批模式会话存在审批模式更新能力。CapprovalMode 枚举与何种动作需审批未知
固定版本 Skill 绑定会话建会话时客户端携带 Skill 标识和版本。C没有执行创建,版本是否锁定及升级行为未知
剧本生成提供文字剧本创作入口。W长篇一致性与分镜合同未测
剧本原创与改编为原创或改编影视文本提供工具。W没有运行,输入边界与输出结构未知
项目与会话 7 项
能力说明证据限制
Agent 项目多会话按项目列会话并建立不同创作对话。CprojectId/sessionId 请求构造可见;未创建项目
历史消息查询按会话或序号获取历史消息。C保留期、分页上限、删除与分叉未知
画布项目与视口保存存储画布草稿、配置及视口位置。CAgent Project 与 Canvas UUID 映射未登录核实
画布版本读取按项目和 versionId 读取版本快照。C仅确认版本查询;不认定可一键恢复/分叉或不可变存储
节点和连线编辑画布组织节点及连接形成创作流程。C节点类型全集与可连端口规则未确认
节点和连线批量提交一次提交多项创建、更新或删除。C带 requestId 不证明后端原子性/幂等
独立编辑会话上下文画布修改携带编辑 session 与时间戳。C不能把画布编辑会话并入 Agent 聊天会话;冲突处理未测
模板与社区 6 项
能力说明证据限制
Skill 分类与搜索搜索或按影视、广告等分类找 Skill。O分类文案在不同页面渲染有差异,不固定全部标签
Skill 收藏与我的入口公开 Skill 页面提供收藏和个人区。O不能由入口推断匿名可收藏或后台 CRUD 合同
Skill 案例与版本信息模板带版本、snapshot 与案例媒体引用。O案例可有画布或成品链接,字段不是物理表
Skill 草稿与发布客户端存在创建、改草稿、应用和发布路径。C发布审查、权限、版本冲突与下架规则未知
TV Show 案例与精选画布展示公开作品及场景分类参考。O作品仅示例;画布复用、发布及分享权限未执行
学院、教程与版本更新提供创作学习及产品更新入口。W活动是否仍接收报名不由历史入口推断
素材与商品 3 项
能力说明证据限制
个人素材库客户端提供素材创建与列表能力。C文件归属、上传审核和删除回收未测
任务生成结果列表从任务查询生成的媒体产物。CArtifact 列表存在;是否自动入素材库未知
生成任务进度客户端查询任务运行进度。C进度状态、重试、取消及并发上限未知
协作与权益 3 项
能力说明证据限制
素材复制到团队存在个人素材复制到团队范围的操作。C只确认范围转换,不推测共享素材外键/成员完整角色
生成费用预估客户端有任务费用计算入口。C未实际查询价格,不当成预占/结算保证
会员与限时优惠入口首页展示会员及模型相关限时优惠。W促销不是完整订阅方案;积分结算、价格和到期规则待登录核实
视频生成 2 项
能力说明证据限制
视频生成提供 AI 视频生成入口。W各模型输入、片段上限、价格与可用性依账号
导演执导提供导演构思和影视创作工具入口。W具体镜头/调度参数及任务流程未执行
图片生成 2 项
能力说明证据限制
图片生成提供 AI 图片生成入口。W未确认所有模型或高级参数
3D-BOX官方提供名为 3D-BOX 的创作工具。W仅确定工具名;不补造 3D 引擎/视角/格式实现
声音与人物 2 项
能力说明证据限制
音频生成提供音频生成入口。WTTS、音色克隆及声音授权范围未知
角色造型室提供角色形象准备入口。W跨镜头一致性与人物版本机制未知
编辑与导出 1 项
能力说明证据限制
智能剪辑提供智能剪辑入口。W不等于确认传统多轨时间轴;导出格式未知
发现与分析 1 项
能力说明证据限制
作品分类与搜索按作品类型筛选并检索案例。O只确认入口,没有批量查询用户数据
开放接入与客户端 1 项
能力说明证据限制
Web 独立窗口声明manifest 声明 standalone Web 展示。O这是 Web 声明,不是 APK/IPA 或可安装性已验证

尚不清楚的部分

竞品技术栈观察

当前Web公开构件直接标注Next.js16.3.4、React19.3.0-canary-cbb046ab-20260731,采用App Router/Flight和Turbopack,并出现TypeScript源路径。网关及静态CDN与业务后端分别标注。

项目观察到的内容状态置信度
Nginx 与代理版本入口banner为istio-envoy;静态CDN为Tengine。Nginx本身是否部署及准确版本未确认。unknownmedium
前端运行语言浏览器交付并加载JavaScript;当前公开构件包含React与Next运行时。observedhigh
前端源码语言公开构件出现apps/liblibtv/src下多个.ts源路径,可确认部分业务前端源文件采用TypeScript。observedhigh
前端框架与版本当前公开构件:Next.js16.3.4;React19.3.0-canary-cbb046ab-20260731。observedhigh
构建与服务端渲染Turbopack运行时;Next App Router/appDir:true及Flight载荷;返回预渲染HTML。observedhigh
业务后端语言api.liblib.tv与im.liblib.tv业务后端语言未知;Next前端不能证明全部业务API为Node.js。unknownlow
业务后端框架业务后端框架与版本未知;istio-envoy只说明入口banner,不说明应用框架。unknownlow
分发层与源站线索Web/API入口域分别指向北京命名的阿里API Gateway/NLB;静态与媒体走独立CDN。上游源站未知。observedhigh
代码仓库与目录关系apps/liblibtv/src是前端路径线索,可能采用多应用目录;前后端是否同仓或同目录未知。inferredmedium
竞品服务调用图

一次匿名Skill查询证明JSON目录;公开客户端分别定义Canvas HTTP、Agent会话HTTP和IM WebSocket。运行/生成职责仅推断,两个Project及两类Session映射仍未知。

节点层职责证据
Web浏览器clientSkill、Canvas和Agent的发布客户端。C
Web入口网关entrywww.liblib.tv匿名HTML入口;API Gateway DNS与istio-envoy banner。O
静态脚本CDNentry独立官方静态JS入口与Tengine/cache响应。O
业务HTTP入口entryapi.liblib.tv承接已观测Skill请求及客户端业务路由基址。O
IM与WS入口entryim.liblib.tv的会话HTTP与/ws客户端连接入口。C
Skill目录资料domain匿名列表包含Skill身份、version/snapshotId和案例。O
Canvas编辑职责domainprojectUuid、nodes/connections、draft/config与版本路由的逻辑职责。C
会话消息资料dataAgentProject、ChatSession、Message与afterSeq的客户端资源关系。C
Agent执行职责runtime解释run事件与增量消息的可能运行或代理职责。I
生成处理职责runtime解释generation任务、进度与产物结果的可能执行/代理职责。I
素材产物资料data媒体资源、个人/团队复制与artifact列表的逻辑结果模型。C
媒体分发入口externallibtv-res.liblib.art公开媒体CNAME及客户端资源地址线索。O

增长与运营机制

作品社区带动创作,视频 sitemap 与作品 llms 索引有真实源文件证据。(源 HTML + robots + 视频 sitemap + llms)

复刻状态

能力状态依据
首页与顶栏(会员入口 + 免费使用/账号)已验证去掉不属于实站的促销条;登录前显示「免费使用」并打开登录框
工作区条、登录框、新建项目/会话框已验证统一为 LibTV 深色令牌
TV Director 对话已验证新建项目→新建会话→自动选默认模型→发送→收到回复
停止回复与「已停止」标记已验证maa_run_stop_actions=terminal_observed,run=cancelled
节点画布、故事板、Skill 社区已加载沿用早期静态页,没有对照实站逐项核对
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8104 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
素材页未验证没有对照实站逐项核对
前端入口:agent-integration/apps-production-brand-v14/libtv/candidate-8875-unified-v22.html
复刻的架构 七个品牌相同,展开查看
flowchart LR
  U[浏览器] -->|静态页| FE[品牌前端<br/>令牌 + 通用层 + 运行时脚本]
  FE -->|/v1/* Cookie + CSRF| BFF[BFF 业务服务 Go]
  BFF --> MY[(MySQL<br/>共享身份库 + 品牌库)]
  BFF -->|会话 / 消息 / 事件| MAA[Maa Agent]
  BFF -->|任务| WK[媒体 worker]
  WK -->|submit_video| GW[模型网关]
  GW --> MOD[视频模型]
  MAA --> GW
  BFF --> OBJ[(素材 / 导出存储)]
所有七个复刻共用同一套后端:24 张表的品牌库 + 15 张表的共享身份库,55 个公开接口(路径里用 ?profile=libtv 区分品牌)。品牌差异在前端:令牌(颜色、圆角、字体)、入口与页面结构。
为什么这样设计七个竞品的后端结构在我们的实测里几乎一致,差别只在入口、模板和视觉。所以复刻把差异收敛到前端令牌,后端只维护一份;这也是最终设计里「共享内核 + 品牌配置」的来源(决策 D17)。

LibTV · Agent 交互过程

竞品里观察到的

NOT_RUN_COST_GATE 已登录独立虚构测试画布中准备纯文字对话。

编号操作结果
LT11-D01打开 TV Director 全局设置推理实时消耗积分,素材生成积分另计;普通文字不能当免费。
LT11-D02查看执行与预算设置自动生成关闭;预算开启,阈值1000。说明达到阈值提醒,非硬预算阻断。没有改变任何开关或阈值。
LT11-D03填写统一纯文字需求输入成功且Send可用,未提交。
LT11-D04停止收费入口体验并清空准备的输入输入框已清空,没有发送。保持原预算提醒阈值及自动生成设置;20积分未变。

对话维度

维度观察
clarificationNOT_RUN
planNOT_RUN
action_confirmation只观察手动/自动菜单,不是对话执行确认
targeted_revisionNOT_RUN
tool_callsNOT_RUN
artifactsNOT_RUN
history_resume本轮没有对话生成历史可恢复

费用与准入

项值
before_visible_credits20
after_visible_credits20
ledger_verifiedfalse
text_inference_freefalse
automatic_media_enabledfalse
budget_reminder_enabledtrue
observed_reminder_threshold1000
hard_cap_verifiedfalse
authorized_spend0
observed_cost_statementTV Director 推理将实时消耗积分,素材生成积分另计。

对我们设计的启示

结论内容做法 / 原因
adapt向用户分别展示文字推理成本与媒体任务成本,不标文字为免费。须将现有 LLM 用量与媒体预扣账本在 UI 分开展示,并在平台内落实硬预算。
reject不复制仅提醒阈值作为本平台最大预算约束。达到提醒阈值不等于阻止下一笔扣费或取消在途费用。

竞品公开的调用流程

步骤调用关系操作证据
加载Web与脚本浏览器 → Web入口GET https://www.liblib.tv/;返回HTML并加载liblibai-web-static.liblib.cloud官方JS;入口与静态资源响应分别为istio-envoy/Tengine。O
查询Skill模板浏览器 → 业务HTTP入口POST https://api.liblib.tv/api/community/skill/template/feed/stream;body含page/pageSize/query/tagId;返回JSON list/hasMore/total与skillKey/version/snapshotO
读取Canvas上下文浏览器 → 业务HTTP入口客户端GET /api/canvas/project/detail,uuid来自projectUuid;Skill引用在session create中可携带skillKey/skillVersion,画布draft与版本由独立路由管理。C
加载或创建会话浏览器 → IM入口GET https://im.liblib.tv/api/v1/project/session/list含projectId/type;POST /api/v1/project/session/create含projectId/name及可选canvasId/nodeKey/skillKey/skiC
发送Agent消息浏览器 → IM入口客户端构造wss://im.liblib.tv/ws;query字段名含token/project_id及可选canvas_id/team_id;上行JSON有threadId/messages/forwardedProps.extra。C
消费增量事件IM入口 → 浏览器客户端消费RUN_STARTED、TEXT_MESSAGE_START/CONTENT/END及type/seq/runId/messageId/role/delta;处理ping/pong、重连与缓存入站事件。C
保存画布修改浏览器 → 业务HTTP入口POST /api/canvas/nodes/batch含projectUuid/requestId/nodes与connections增改删;POST draft/update含draftJson/canvasConfigJson/viewport坐标;编辑上下文可带sessionId/timesC
读取生成与产物浏览器 → 业务HTTP入口客户端存在/api/task/generation/create、progress、power/calculator与/api/task/artifact/list,以及媒体创建/list/copy-to-team等路径。C

在本品牌复刻上的测试结果

能力状态依据
TV Director 对话已验证新建项目→新建会话→自动选默认模型→发送→收到回复
停止回复与「已停止」标记已验证maa_run_stop_actions=terminal_observed,run=cancelled
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8104 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
复刻里的 Agent 交互设计(时序图、状态机、规则) 七个品牌相同,展开查看
sequenceDiagram
  autonumber
  participant B as 浏览器
  participant S as BFF
  participant DB as MySQL
  participant M as Maa Agent
  participant W as 媒体 worker
  participant G as 模型网关
  B->>S: POST /projects, /conversations
  B->>S: POST maa/binding
  S->>DB: maa_bindings dispatching
  S->>M: 创建原生会话
  M-->>S: native_cid
  S->>DB: maa_bindings bound
  B->>S: POST maa/messages {client_action_id, content}
  S->>DB: maa_message_actions 幂等回执
  S-->>B: 202 + run_id
  S->>M: 提交消息
  S->>DB: maa_runs queued 到 running
  loop 事件游标
    B->>S: GET runs/{id}/events
    S-->>B: assistant.delta / assistant.message
  end
  M-->>S: run.completed
  S->>DB: 记录助手内容 SHA-256
  B->>S: POST media/requests/from-maa {source_run_id, sha256}
  S-->>B: 报价 + 有效期 + quote_sha256
  B->>S: POST approve {client_approval_id, request_sha256, quote_sha256}
  S->>DB: 冻结积分 + 建任务
  W->>G: submit_video
  G-->>W: 视频
  W->>DB: 产出入库 + 扣费
  B->>S: GET media/jobs/{id}

Run 状态机

stateDiagram-v2
  [*] --> queued: POST maa/messages 202
  queued --> running
  running --> waiting_interaction: 需要澄清卡
  waiting_interaction --> running: respondInteraction
  running --> completed
  running --> failed
  running --> cancelled: stopRun
  waiting_interaction --> cancelled
  completed --> [*]
  failed --> [*]
  cancelled --> [*]

媒体任务状态机

stateDiagram-v2
  [*] --> quoted
  quoted --> expired: 约 5 分钟
  quoted --> approved: approve
  approved --> held: 冻结积分
  held --> queued
  queued --> running: worker 领取
  running --> succeeded: 产出入库
  succeeded --> captured: 扣费确认
  running --> failed
  failed --> 待核对: 供应商已受理后失败,冻结保留,界面写「费用待核对」

交互规则

环节规则落库
绑定会话bindMaa 传 agent_model 或 use_default;先 dispatching,拿到原生会话号后才 bound(约束保证二者一致)maa_bindings
发消息client_action_id 幂等;返回 202 + run_id;同一个动作 ID 内容不同返回 409maa_message_actions, maa_runs
事件按 seq 游标读取;服务端先清洗路径、内部地址和令牌再公开maa_runs.last_event_seq
完成证据完成时记录助手内容的两份 SHA-256 与字节数maa_runs
澄清卡(HITL)Run 进入 waiting_interaction;respondInteraction 带 card_sha256;不能恢复两次maa_interactions, maa_interaction_actions
停止stopRun 幂等,回执只说「已观测到终态」;对话里保留「已停止回复」maa_run_stop_actions
切换模型必须带 expected_revision;有未决切换时拒绝发新消息maa_model_change_actions
生成视频from-maa → 报价 → 批准 → 冻结 → worker → 入库 → 扣费;批准带请求哈希与报价哈希studio_media_*, 积分账本
报价过期批准被拒,不建任务不扣费;界面提示重新报价studio_media_requests.expires_at
失败后的积分供应商已受理后失败:冻结保留,界面写「费用待核对」,不自动释放;本部署的账本里还没出现过释放分录(12 笔冻结、8 笔扣费、其余 4 笔仍冻结)studio_credit_ledger_entries
会话过期每 8 秒检查登录状态;失效时横幅提示,登录后恢复—
为什么这样设计把事件做成带序号的流、把动作做成带幂等键的回执,是因为长回复一定会遇到断线、刷新和重复点击。序号让续读不丢不重;幂等让重复点击安全;诚实的回执(只说「已观测到终态」)避免把「已停止」说成「不收费」。我们还在复刻里修掉了一个停止按钮从不发请求的缺陷,这说明每条交互路径都需要端到端测试(决策 D8)。

LibTV · ER 图

品牌库 studio_profile_libtv:24 张表、34 个真实外键、66 个检查约束、22 个触发器。图由实际表结构生成,箭头从父表指向子表。品牌库与共享身份库之间的 owner_id、asset_id 靠应用层归属检查,不画成外键。

共 61 条关系:一对多 37 条,一对一 10 条,一对多(父可选) 10 条,一对一(父可选) 4 条;多对多 2 组(经关联表)。

用户与登录 素材 积分账本 支付与订阅 模型目录 项目与会话 画布 · 时间轴 · 导出 Agent 交互 生成 · 媒体 · 交付

基数标注(线上的文字,箭头从父表指向子表):1:N 一对多,子表外键必填;0..1:N 一对多,但外键可空,所以父是可选的;1:1 一对一,子表外键上有唯一键;0..1:1 一对一且父可选。红色粗双向线 = N:M 多对多,经带 ⇄ 的关联表(它的主键就是两个外键)。实线 = 数据库真实外键;灰色虚线 = 跨库关系(两个库之间数据库无法建外键,由应用层检查);橙色粗框与粗线 = Agent 交互相关。每张表都带 owner_id(指向用户),为了图清楚没有逐条画线。

全景图:全部实体与关系

默认缩放到页面宽度,先看整体结构;点一下图切换到原始大小(看清表名,在框内上下左右滚动),再点一下缩回。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":26,"rankSpacing":80}}}%%
flowchart LR
  subgraph sg_i_user["用户与登录"]
    direction TB
    t_studio_users["users"]
    t_studio_sessions["sessions"]
  end
  subgraph sg_i_asset["素材"]
    direction TB
    t_studio_assets["assets"]
  end
  subgraph sg_i_credit["积分账本"]
    direction TB
    t_studio_credit_accounts["credit_accounts"]
    t_studio_credit_lots["credit_lots"]
    t_studio_credit_authorizations["credit_authorizations"]
    t_studio_credit_authorization_allocations["⇄ credit_authorization_allocations"]
    t_studio_credit_ledger_entries["credit_ledger_entries"]
    t_studio_credit_plan_versions["credit_plan_versions"]
    t_studio_billing_cycles["billing_cycles"]
  end
  subgraph sg_i_pay["支付与订阅"]
    direction TB
    t_studio_payment_orders["payment_orders"]
    t_studio_payment_events["payment_events"]
    t_studio_subscriptions["subscriptions"]
  end
  subgraph sg_i_model["模型目录"]
    direction TB
    t_studio_media_models["media_models"]
    t_studio_media_model_versions["media_model_versions"]
  end
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  subgraph sg_media["生成 · 媒体 · 交付"]
    direction TB
    t_studio_media_requests["media_requests"]
    t_studio_media_jobs["media_jobs"]
    t_studio_media_submission_attempts["media_submission_attempts"]
    t_studio_media_outputs["media_outputs"]
    t_generation_jobs["gen_jobs"]
    t_generation_reservations["gen_reservations"]
    t_generation_deliveries["gen_deliveries"]
    t_generation_development_budgets["gen_development_budgets"]
    t_studio_credit_recovery_intents["credit_recovery_intents"]
  end
  t_studio_users -->|"1:N"| t_studio_assets
  t_studio_payment_orders -->|"1:1"| t_studio_billing_cycles
  t_studio_subscriptions -->|"1:N"| t_studio_billing_cycles
  t_studio_users -->|"1:1"| t_studio_credit_accounts
  t_studio_credit_authorizations -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_lots -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_authorizations
  t_studio_credit_authorization_allocations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_authorizations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_lots -->|"1:N"| t_studio_credit_ledger_entries
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_lots
  t_studio_billing_cycles -->|"0..1:1"| t_studio_credit_lots
  t_studio_payment_orders -->|"0..1:1"| t_studio_credit_lots
  t_studio_media_models -->|"1:N"| t_studio_media_model_versions
  t_studio_media_model_versions -->|"0..1:1"| t_studio_media_models
  t_studio_payment_orders -->|"0..1:N"| t_studio_payment_events
  t_studio_subscriptions -->|"0..1:N"| t_studio_payment_events
  t_studio_users -->|"1:N"| t_studio_payment_events
  t_studio_credit_plan_versions -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_sessions
  t_studio_credit_plan_versions -->|"1:N"| t_studio_subscriptions
  t_studio_users -->|"1:N"| t_studio_subscriptions
  t_generation_jobs -->|"1:1"| t_generation_deliveries
  t_studio_projects -->|"1:N"| t_generation_deliveries
  t_studio_conversations -->|"1:N"| t_generation_deliveries
  t_generation_development_budgets -->|"1:N"| t_generation_jobs
  t_studio_projects -->|"1:N"| t_generation_jobs
  t_studio_conversations -->|"1:N"| t_generation_jobs
  t_generation_jobs -->|"1:1"| t_generation_reservations
  t_studio_conversations -->|"1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"0..1:1"| t_maa_bindings
  t_maa_interactions -->|"1:1"| t_maa_interaction_actions
  t_maa_runs -->|"1:N"| t_maa_interactions
  t_maa_bindings -->|"1:N"| t_maa_message_actions
  t_maa_runs -->|"0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"1:N"| t_maa_model_change_actions
  t_maa_runs -->|"1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"1:N"| t_maa_runs
  t_studio_canvases -->|"1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"1:1"| t_studio_canvases
  t_studio_projects -->|"1:N"| t_studio_conversations
  t_studio_media_jobs -->|"1:N"| t_studio_credit_recovery_intents
  t_studio_projects -->|"1:N"| t_studio_export_jobs
  t_studio_media_requests -->|"1:1"| t_studio_media_jobs
  t_studio_media_jobs -->|"1:N"| t_studio_media_outputs
  t_studio_conversations -->|"1:N"| t_studio_media_requests
  t_maa_runs -->|"0..1:N"| t_studio_media_requests
  t_studio_media_jobs -->|"1:N"| t_studio_media_submission_attempts
  t_studio_projects -->|"1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"1:1"| t_studio_video_documents
  t_studio_assets -.->|"1:N"| t_studio_project_asset_refs
  t_studio_assets -.->|"1:N"| t_studio_media_outputs
  t_studio_assets -.->|"1:N"| t_generation_deliveries
  t_studio_media_model_versions -.->|"1:N"| t_studio_media_requests
  t_studio_media_models -.->|"1:N"| t_studio_media_requests
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_media_jobs
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_credit_recovery_intents
  t_studio_credit_authorizations <-.->|"N:M · 经 credit_authorization_allocations"| t_studio_credit_lots
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_users,t_studio_sessions user
class t_studio_assets asset
class t_studio_credit_accounts,t_studio_credit_lots,t_studio_credit_authorizations,t_studio_credit_authorization_allocations,t_studio_credit_ledger_entries,t_studio_credit_plan_versions,t_studio_billing_cycles credit
class t_studio_payment_orders,t_studio_payment_events,t_studio_subscriptions pay
class t_studio_media_models,t_studio_media_model_versions model
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
class t_studio_media_requests,t_studio_media_jobs,t_studio_media_submission_attempts,t_studio_media_outputs,t_generation_jobs,t_generation_reservations,t_generation_deliveries,t_generation_development_budgets,t_studio_credit_recovery_intents media
class t_studio_credit_authorization_allocations,t_studio_project_asset_refs junction
  style sg_i_user fill:none,stroke:#6b7280,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_asset fill:none,stroke:#06b6d4,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_credit fill:none,stroke:#eab308,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_pay fill:none,stroke:#ec4899,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_model fill:none,stroke:#6366f1,stroke-width:1px,stroke-dasharray:5 4
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  style sg_media fill:none,stroke:#22c55e,stroke-width:1px
  linkStyle 30,31,32,33,34,35,36,37,38,48 stroke:#f97316,stroke-width:2.5px
  linkStyle 54,55,56,57,58,59,60 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 61,62 stroke:#dc2626,stroke-width:3px

多对多(N:M)

数据库里没有“多对多”这种列,它总是靠一张关联表表达。下面这几张表的主键由两个外键组成,所以它们两端是多对多:

多对多的两端关联表关联表的主键 / 唯一键
studio_credit_authorizations ⇄ studio_credit_lotsstudio_credit_authorization_allocationsowner_id, authorization_id, lot_id
studio_projects ⇄ studio_assetsstudio_project_asset_refsproject_id, asset_id

Agent 交互相关(圈出来)

绑定 → Run → 消息 / 澄清卡 / 切换模型 / 停止,每个动作一张回执表;线上是“外键列 · 基数”。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  t_studio_conversations -->|"conversation_id · 1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"pending_model_change_id · 0..1:1"| t_maa_bindings
  t_maa_interactions -->|"interaction_id · 1:1"| t_maa_interaction_actions
  t_maa_runs -->|"public_run_id · 1:N"| t_maa_interactions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_message_actions
  t_maa_runs -->|"public_run_id · 0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_model_change_actions
  t_maa_runs -->|"public_run_id · 1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_runs
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  linkStyle 0,1,2,3,4,5,6,7,8 stroke:#f97316,stroke-width:2.5px

画布与时间轴

项目下是一份视频文档,画布、画布版本、时间线版本和导出都挂在这份文档上;素材放在共享身份库,项目只记引用,项目与素材是多对多,经 project_asset_refs。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_asset["素材(共享身份库)"]
    direction TB
    t_studio_assets["assets"]
  end
  t_studio_canvases -->|"project_id · 1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"restored_from_revision · 0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_canvases
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
  t_studio_projects -->|"project_id · 1:N"| t_studio_export_jobs
  t_studio_projects -->|"project_id · 1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"project_id · 1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"restored_from_revision · 0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_video_documents
  t_studio_assets -.->|"asset_id · 1:N"| t_studio_project_asset_refs
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_studio_assets asset
class t_studio_project_asset_refs junction
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px
  linkStyle 9 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 10 stroke:#dc2626,stroke-width:3px
关系清单 逐条 61 条,含基数
父表子表外键列(子表里)基数约束域
generation_jobsgeneration_deliveriesjob_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_assetsgeneration_deliveriesasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_conversationsgeneration_deliveriesowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_deliveriesowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_development_budgetsgeneration_jobsowner_id, budget_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_conversationsgeneration_jobsowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_jobsgeneration_reservationsjob_id, budget_id, owner_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_model_change_actionsmaa_bindingsowner_id, project_id, conversation_id, id, pending_model_change_id0..1:1 一对一(父可选)DB 外键Agent 交互 → Agent 交互
studio_conversationsmaa_bindingsowner_id, project_id, conversation_id1:1 一对一DB 外键项目与会话 → Agent 交互
maa_interactionsmaa_interaction_actionsowner_id, project_id, conversation_id, binding_id, public_run_id, interaction_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_runsmaa_interactionsowner_id, project_id, conversation_id, binding_id, public_run_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_message_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_message_actionsowner_id, project_id, conversation_id, binding_id, public_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_model_change_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_run_stop_actionsowner_id, project_id, conversation_id, binding_id, public_run_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_runsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
studio_usersstudio_assetsowner_id1:N 一对多DB 外键用户与登录 → 素材
studio_payment_ordersstudio_billing_cyclesowner_id, payment_order_id, plan_version_id1:1 一对一DB 外键支付与订阅 → 积分账本
studio_subscriptionsstudio_billing_cyclesowner_id, subscription_id, provider_key, provider_account1:N 一对多DB 外键支付与订阅 → 积分账本
studio_canvas_revisionsstudio_canvas_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_canvasesstudio_canvas_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_canvasesowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_projectsstudio_conversationsowner_id, project_id1:N 一对多DB 外键项目与会话 → 项目与会话
studio_usersstudio_credit_accountsowner_id1:1 一对一DB 外键用户与登录 → 积分账本
studio_credit_authorizationsstudio_credit_authorization_allocationsowner_id, authorization_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_authorization_allocationsowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_authorizationsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_authorization_allocationsstudio_credit_ledger_entriesowner_id, authorization_id, lot_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_authorizationsstudio_credit_ledger_entriesowner_id, authorization_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_ledger_entriesowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_billing_cyclesstudio_credit_lotsowner_id, source_cycle_id0..1:1 一对一(父可选)DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_lotsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_payment_ordersstudio_credit_lotsowner_id, source_order_id0..1:1 一对一(父可选)DB 外键支付与订阅 → 积分账本
studio_credit_authorizationsstudio_credit_recovery_intentsauthorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_jobsstudio_credit_recovery_intentsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_projectsstudio_export_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_credit_authorizationsstudio_media_jobscredit_authorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_requestsstudio_media_jobsowner_id, project_id, conversation_id, request_id, request_sha256, quote_sha2561:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_model_versionsmodel_id1:N 一对多DB 外键模型目录 → 模型目录
studio_media_model_versionsstudio_media_modelsid, published_version_id0..1:1 一对一(父可选)DB 外键模型目录 → 模型目录
studio_assetsstudio_media_outputsasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_outputsowner_id, project_id, conversation_id, job_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_runsstudio_media_requestsowner_id, project_id, conversation_id, source_binding_id, source_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → 生成 · 媒体 · 交付
studio_conversationsstudio_media_requestsowner_id, project_id, conversation_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_media_model_versionsstudio_media_requestsmodel_version_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_requestsmodel_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_submission_attemptsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_payment_ordersstudio_payment_eventsowner_id, order_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_subscriptionsstudio_payment_eventsowner_id, subscription_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_usersstudio_payment_eventsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_credit_plan_versionsstudio_payment_ordersplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_payment_ordersowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_assetsstudio_project_asset_refsasset_id1:N 一对多跨库(应用层检查)素材 → 画布 · 时间轴 · 导出
studio_projectsstudio_project_asset_refsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_usersstudio_sessionsuser_id1:N 一对多DB 外键用户与登录 → 用户与登录
studio_credit_plan_versionsstudio_subscriptionsplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_subscriptionsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_timeline_revisionsstudio_timeline_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_video_documentsstudio_timeline_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_video_documentsowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
完整 ER:39 张表的全部列,带主键 / 外键与基数符号(鸦脚)
erDiagram
  studio_assets {
    CHAR id PK
    CHAR owner_id FK
    CHAR sha256
    JSON metadata_json
    VARCHAR storage_key
    DATETIME created_at
    VARCHAR publication_state
  }
  studio_billing_cycles {
    CHAR id PK
    CHAR owner_id FK
    CHAR subscription_id FK
    CHAR plan_version_id FK
    CHAR payment_order_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_invoice_id
    DATETIME period_start
    DATETIME period_end
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_accounts {
    CHAR owner_id PK
    VARCHAR unit
    VARCHAR status
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_credit_authorization_allocations {
    CHAR owner_id FK
    CHAR authorization_id PK
    CHAR lot_id PK
    BIGINT_UNSIGNED allocated_credits
    DATETIME created_at
  }
  studio_credit_authorizations {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR profile
    CHAR project_id
    CHAR conversation_id
    CHAR job_id
    CHAR request_id
    CHAR request_sha256
    CHAR quote_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    MEDIUMBLOB reservation_bytes
    CHAR reservation_sha256
    MEDIUMBLOB policy_bytes
    CHAR policy_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_ledger_entries {
    CHAR id PK
    CHAR owner_id FK
    CHAR lot_id FK
    CHAR authorization_id FK
    VARCHAR operation_key
    VARCHAR kind
    BIGINT grant_delta
    BIGINT held_delta
    BIGINT captured_delta
    MEDIUMBLOB facts_bytes
    CHAR facts_sha256
    DATETIME created_at
  }
  studio_credit_lots {
    CHAR id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED grant_credits
    BIGINT_UNSIGNED held_credits
    BIGINT_UNSIGNED captured_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    CHAR source_order_id FK
    CHAR source_cycle_id FK
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_plan_versions {
    CHAR id PK
    VARCHAR plan_key
    VARCHAR version_key
    VARCHAR kind
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    INT_UNSIGNED credit_validity_seconds
    INT_UNSIGNED billing_interval_months
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    DATETIME published_at
    DATETIME retired_at
  }
  studio_media_model_versions {
    CHAR id PK
    CHAR model_id FK
    VARCHAR version_key
    MEDIUMBLOB config_bytes
    CHAR config_sha256
    DATETIME published_at
  }
  studio_media_models {
    CHAR id PK
    VARCHAR model_key
    VARCHAR kind
    VARCHAR display_name
    VARCHAR status
    CHAR published_version_id FK
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_payment_events {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_event_id
    CHAR order_id FK
    CHAR subscription_id FK
    MEDIUMBLOB body_bytes
    CHAR body_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_payment_orders {
    CHAR id PK
    CHAR owner_id FK
    CHAR client_action_id
    CHAR plan_version_id FK
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_idempotency_key
    VARCHAR provider_order_id
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_sessions {
    CHAR token_hash PK
    CHAR user_id FK
    DATETIME created_at
    DATETIME expires_at
    DATETIME revoked_at
  }
  studio_subscriptions {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_subscription_id
    CHAR plan_version_id FK
    VARCHAR state
    DATETIME period_start
    DATETIME period_end
    TINYINT cancel_at_period_end
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_users {
    CHAR id PK
    VARCHAR email
    VARBINARY password_hash
    TINYINT enabled
    DATETIME created_at
  }
  generation_deliveries {
    CHAR job_id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR asset_id FK
    CHAR request_sha256
    CHAR maa_source_sha256
    CHAR provider_source_sha256
    VARCHAR model
    VARCHAR tool_name
    CHAR input_sha256
    JSON metadata_json
    DATETIME created_at
  }
  generation_development_budgets {
    CHAR id PK
    CHAR owner_id
    VARCHAR policy_version
    BIGINT_UNSIGNED credits_limit
    BIGINT_UNSIGNED credits_reserved
    INT_UNSIGNED calls_limit
    INT_UNSIGNED calls_reserved
    DATETIME expires_at
  }
  generation_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR native_conversation_id
    CHAR native_run_id
    CHAR source_sha256
    CHAR budget_id FK
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    VARCHAR status
    INT_UNSIGNED attempts
    MEDIUMBLOB result_bytes
    DATETIME updated_at
  }
  generation_reservations {
    CHAR job_id PK
    CHAR budget_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED credits
    INT_UNSIGNED calls
    VARCHAR status
  }
  maa_bindings {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR provider
    VARCHAR model_name
    VARCHAR status
    VARCHAR native_cid
    DATETIME created_at
    BIGINT_UNSIGNED model_revision
    CHAR pending_model_change_id FK
  }
  maa_interaction_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR interaction_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR client_action_id
    CHAR card_sha256
    CHAR request_sha256
    CHAR content_sha256
    VARCHAR response_action
    JSON values_payload
    VARCHAR status
    DATETIME created_at
  }
  maa_interactions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR card_sha256
    JSON card_payload
    DATETIME created_at
  }
  maa_message_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_action_id
    CHAR request_sha256
    MEDIUMTEXT content
    VARCHAR status
    CHAR public_run_id FK
    CHAR proposed_run_id
    DATETIME created_at
  }
  maa_model_change_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    CHAR client_action_id
    CHAR request_sha256
    CHAR native_request_sha256
    BIGINT_UNSIGNED expected_revision
    VARCHAR from_provider
    VARCHAR from_model_name
    VARCHAR target_provider
    VARCHAR target_model_name
    VARCHAR status
    BIGINT_UNSIGNED dispatch_attempt
    JSON receipt_payload
    DATETIME created_at
    DATETIME updated_at
  }
  maa_run_stop_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    VARCHAR native_rid
    CHAR client_action_id
    CHAR request_sha256
    VARCHAR stop_action
    VARCHAR status
    BIGINT_UNSIGNED attempt
    VARCHAR terminal_run_status
    DATETIME created_at
    DATETIME updated_at
  }
  maa_runs {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_rid
    VARCHAR status
    DATETIME created_at
    CHAR native_assistant_sha256
    CHAR public_assistant_sha256
    BIGINT_UNSIGNED native_assistant_bytes
    BIGINT_UNSIGNED assistant_event_seq
    BIGINT_UNSIGNED last_event_seq
  }
  studio_canvas_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_canvases {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_conversations {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    VARCHAR title
    DATETIME created_at
  }
  studio_credit_recovery_intents {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256
    VARCHAR kind
    CHAR authorization_id FK
    MEDIUMBLOB intent_bytes
    CHAR intent_sha256
    VARCHAR state
    CHAR lease_token_sha256
    DATETIME lease_until
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_export_jobs {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED document_revision
    CHAR input_hash
    VARCHAR status
    JSON input_json
    JSON output_json
    VARCHAR error_code
    CHAR lease_token_hash
    DATETIME lease_until
    INT_UNSIGNED attempts
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR request_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256 FK
    CHAR client_approval_id
    CHAR approval_sha256
    CHAR credit_authorization_id FK
    VARCHAR authorization_mode
    DATETIME authorization_expires_at
    VARCHAR status
    BIGINT_UNSIGNED revision
    CHAR lease_token_sha256
    DATETIME lease_until
    VARCHAR provider_task_id
    CHAR client_cancel_id
    CHAR cancel_request_sha256
    DATETIME cancel_requested_at
    CHAR staged_asset_id
    CHAR staged_asset_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_outputs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED ordinal
    CHAR asset_id FK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    CHAR provider_result_sha256
    DATETIME created_at
  }
  studio_media_requests {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_request_id
    CHAR model_id FK
    CHAR model_version_id FK
    CHAR model_config_sha256
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    MEDIUMBLOB quote_bytes
    CHAR quote_sha256
    BIGINT_UNSIGNED quoted_credits
    DATETIME expires_at
    VARCHAR origin
    CHAR source_binding_id FK
    CHAR source_run_id FK
    CHAR source_assistant_sha256
    MEDIUMBLOB source_snapshot_bytes
    DATETIME created_at
  }
  studio_media_submission_attempts {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED attempt_no
    CHAR request_sha256 FK
    VARCHAR provider_idempotency_key
    VARCHAR stage
    DATETIME dispatch_started_at
    VARCHAR provider_task_id
    CHAR receipt_sha256
    CHAR terminal_receipt_sha256
    CHAR reconciliation_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_project_asset_refs {
    CHAR project_id PK
    CHAR owner_id FK
    CHAR asset_id PK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
    DATETIME detached_at
  }
  studio_projects {
    CHAR id PK
    CHAR owner_id
    VARCHAR title
    DATETIME created_at
  }
  studio_timeline_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_video_documents {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_users ||--o{ studio_assets : "owner_id"
  studio_payment_orders ||--o| studio_billing_cycles : "plan_version_id"
  studio_subscriptions ||--o{ studio_billing_cycles : "provider_account"
  studio_users ||--o| studio_credit_accounts : "owner_id"
  studio_credit_authorizations ||--o{ studio_credit_authorization_allocations : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_authorization_allocations : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_authorizations : "owner_id"
  studio_credit_authorization_allocations |o--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_authorizations |o--o{ studio_credit_ledger_entries : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_lots : "owner_id"
  studio_billing_cycles |o--o| studio_credit_lots : "source_cycle_id"
  studio_payment_orders |o--o| studio_credit_lots : "source_order_id"
  studio_media_models ||--o{ studio_media_model_versions : "model_id"
  studio_media_model_versions |o--o| studio_media_models : "published_version_id"
  studio_payment_orders |o--o{ studio_payment_events : "provider_account"
  studio_subscriptions |o--o{ studio_payment_events : "provider_account"
  studio_users ||--o{ studio_payment_events : "owner_id"
  studio_credit_plan_versions ||--o{ studio_payment_orders : "plan_version_id"
  studio_users ||--o{ studio_payment_orders : "owner_id"
  studio_users ||--o{ studio_sessions : "user_id"
  studio_credit_plan_versions ||--o{ studio_subscriptions : "plan_version_id"
  studio_users ||--o{ studio_subscriptions : "owner_id"
  generation_jobs ||--o| generation_deliveries : "job_id"
  studio_projects ||--o{ generation_deliveries : "project_id"
  studio_conversations ||--o{ generation_deliveries : "session_id"
  generation_development_budgets ||--o{ generation_jobs : "budget_id"
  studio_projects ||--o{ generation_jobs : "project_id"
  studio_conversations ||--o{ generation_jobs : "session_id"
  generation_jobs ||--o| generation_reservations : "budget_id"
  studio_conversations ||--o| maa_bindings : "conversation_id"
  maa_model_change_actions |o--o| maa_bindings : "pending_model_change_id"
  maa_interactions ||--o| maa_interaction_actions : "interaction_id"
  maa_runs ||--o{ maa_interactions : "public_run_id"
  maa_bindings ||--o{ maa_message_actions : "binding_id"
  maa_runs |o--o{ maa_message_actions : "public_run_id"
  maa_bindings ||--o{ maa_model_change_actions : "binding_id"
  maa_runs ||--o| maa_run_stop_actions : "public_run_id"
  maa_bindings ||--o{ maa_runs : "binding_id"
  studio_canvases ||--o{ studio_canvas_revisions : "project_id"
  studio_canvas_revisions |o--o{ studio_canvas_revisions : "restored_from_revision"
  studio_projects ||--o| studio_canvases : "project_id"
  studio_projects ||--o{ studio_conversations : "project_id"
  studio_media_jobs ||--o{ studio_credit_recovery_intents : "job_id"
  studio_projects ||--o{ studio_export_jobs : "project_id"
  studio_media_requests ||--o| studio_media_jobs : "request_id"
  studio_media_jobs ||--o{ studio_media_outputs : "job_id"
  studio_conversations ||--o{ studio_media_requests : "conversation_id"
  maa_runs |o--o{ studio_media_requests : "source_run_id"
  studio_media_jobs ||--o{ studio_media_submission_attempts : "job_id"
  studio_projects ||--o{ studio_project_asset_refs : "project_id"
  studio_video_documents ||--o{ studio_timeline_revisions : "project_id"
  studio_timeline_revisions |o--o{ studio_timeline_revisions : "restored_from_revision"
  studio_projects ||--o| studio_video_documents : "project_id"
  studio_assets ||--o{ studio_project_asset_refs : "asset_id"
  studio_assets ||--o{ studio_media_outputs : "asset_id"
  studio_assets ||--o{ generation_deliveries : "asset_id"
  studio_media_model_versions ||--o{ studio_media_requests : "model_version_id"
  studio_media_models ||--o{ studio_media_requests : "model_id"
  studio_credit_authorizations |o--o{ studio_media_jobs : "credit_authorization_id"
  studio_credit_authorizations |o--o{ studio_credit_recovery_intents : "authorization_id"
自动生成的整体图(按真实表结构,备查)
LibTV 品牌库 ER 图

共享身份库 ER 图

共享身份库 ER 图
实测验证与设计原因 七个品牌相同,展开查看

实测验证(本机 MySQL 实库)

对照本机真实运行的数据库 studio_profile_libtv 和 studio_platform_identity,逐项核对本页的表、列、关系与说明。18 / 18 项通过。

查看全部核对项
核对项结果
表数 = 发布值 24通过
列数 = 289通过
外键数 = 34通过
检查约束数 = 66通过
触发器数 = 22通过
索引数(含主键) = 85通过
列定义与 pollo 逐列一致通过
外键与 pollo 完全一致通过
索引与 pollo 一致通过
触发器与 pollo 一致通过
外键 CSV 行数 = 实库外键数通过
mmd 实体 = 实库表通过
svg 标题 24 tables / 34 actual foreign keys = 实库通过
参考 SQL 表集合与列顺序 = 实库通过
15 张表通过
外键 CSV 行数 = 实库外键数通过
svg 标题与实库一致通过
参考 SQL 表集合与列顺序 = 实库通过

实库里的关系是这样的

  • 34 个外键里 31 个是复合外键,且都带着 owner_id,其余 3 个是单列外键;22 张子表都带 owner 外键。所以一行数据不可能指向别的用户的项目或会话。
  • 删除与更新规则全部是 RESTRICT / NO ACTION,没有任何级联:被引用的行不能被删除。
  • 品牌库与共享身份库之间的关系(owner_id、asset_id、model_id + model_version_id)没有数据库外键,靠应用层检查;我们逐表核对了现有数据,没有孤儿。

主动破坏测试(在事务里尝试,全部回滚)

尝试数据库的反应
外键:导出任务.project_id 改成不存在的项目被拒绝拒绝
复合外键:项目素材引用.project_id 改成别的用户的项目被拒绝(owner 与 project 必须成对)拒绝
触发器:已提交的消息动作不可改写拒绝
触发器:媒体产出不可改写拒绝
外键 RESTRICT:被会话引用的项目不能删除拒绝
复合外键:被引用的会话不能把 owner 改成别人拒绝
检查约束:Run 状态只能是约定的六个值拒绝
触发器:账本分录不可修改拒绝
触发器:账本分录不可删除拒绝
验证范围的说明:这几个库里的数据量很小(每个品牌库约 70–270 行),孤儿检查能证明现有数据没有违反关系,不能证明大数据量下的表现;主动破坏测试在 Pollo 库上执行,另外六个品牌库的列、外键、索引、触发器已逐项核对与 Pollo 完全一致。
为什么这样设计外键用真实数据库约束表达,复合外键把 owner/project/conversation 一起带上,所以任何一行数据都不可能指向别人的项目。跨库关系不画外键而用应用层检查,是因为不同库之间数据库本身保证不了,画出来反而误导(决策 D12、D17)。

LibTV · 表结构

MySQL 8,字符集 utf8mb4,ID 为 char(36) ascii_bin。点击展开 DDL。

按职责分组

项目与会话 2 张
  • studio_projects:用户拥有的创作项目
  • studio_conversations:项目内的对话会话
Agent执行与并发控制 7 张
  • maa_bindings:业务会话与Native Maa会话映射、模型版本与未决操作
  • maa_runs:公开Run映射、状态与原生/公开助手证据摘要
  • maa_message_actions:原消息动作及幂等回执
  • maa_interactions:澄清卡片及Run归属
  • maa_interaction_actions:原卡片回答/取消动作
  • maa_model_change_actions:带预期模型版本的切换动作
  • maa_run_stop_actions:停止原Run的幂等动作与观测终态
画布、时间轴与素材引用 5 张
  • studio_canvases:项目当前画布文档
  • studio_canvas_revisions:历史画布修订
  • studio_video_documents:项目当前时间轴文档
  • studio_timeline_revisions:历史时间轴修订
  • studio_project_asset_refs:项目对用户级素材的引用;不复制素材所有权
媒体生成、结算恢复与导出 6 张
  • studio_media_requests:用户输入/Agent来源、报价、批准摘要与准入确认
  • studio_media_jobs:已批准媒体任务及状态机
  • studio_media_submission_attempts:一次提交的任务证据与不确定结果
  • studio_media_outputs:受验证的输出与素材映射
  • studio_credit_recovery_intents:积分结算恢复意图;不是用户钱包全账本
  • studio_export_jobs:时间轴导出队列和发布输出
既有generation兼容路径 22 张
  • generation_jobs:既有generation任务,不等同新PublicMedia任务
  • generation_reservations:既有任务费用预留
  • generation_development_budgets:既有开发测试预算
  • generation_deliveries:既有生成交付回执
  • owner_id:归属用户;不是允许跨用户查询的参数。
  • model_revision:并发准入版本;数据库默认0不能代替Native权威读回。
  • pending_model_change_id:未决模型切换引用,阻止其他writer绕过。
  • studio_users:用户账号记录;不导出密码哈希或任何业务行
  • studio_sessions:登录会话;这里只公开字段结构,不公开会话值
  • studio_assets:用户级素材元数据与存储键结构,项目仅引用素材ID
  • studio_media_models:公开媒体模型目录结构
  • studio_media_model_versions:媒体模型不可变版本/计价能力结构
  • studio_subscriptions:订阅状态持久化
  • studio_billing_cycles:订阅账期
  • studio_payment_orders:订单记录
  • studio_payment_events:支付事件幂等记录;不代表已有公开支付路由
  • studio_credit_accounts:用户积分账户
  • studio_credit_lots:积分批次
  • studio_credit_authorizations:费用预留与结算授权
  • studio_credit_authorization_allocations:授权对批次的分配
  • studio_credit_ledger_entries:积分流水
  • studio_credit_plan_versions:积分方案不可变版本

品牌库 studio_profile_libtv(24 张)

generation_deliveries 既有生成交付回执
CREATE TABLE `generation_deliveries` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `maa_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `tool_name` varchar(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_delivery_owner_project` (`owner_id`,`project_id`,`session_id`,`job_id`),
  KEY `generation_delivery_owned_asset` (`owner_id`,`asset_id`),
  KEY `generation_delivery_session_fk` (`session_id`),
  CONSTRAINT `generation_delivery_job_fk` FOREIGN KEY (`job_id`) REFERENCES `generation_jobs` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`, `job_id`) REFERENCES `generation_jobs` (`owner_id`, `project_id`, `session_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_session_fk` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_model_tool` CHECK ((((`model` = _ascii'seedance-2.0') and (`tool_name` = _ascii'seedance2_video_generator')) or ((`model` in (_ascii'minimax-h3',_ascii'seedance-2.5')) and (`tool_name` = _ascii'insai_video_generate'))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_development_budgets 既有开发测试预算
CREATE TABLE `generation_development_budgets` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_version` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits_limit` bigint unsigned NOT NULL,
  `credits_reserved` bigint unsigned NOT NULL DEFAULT '0',
  `calls_limit` int unsigned NOT NULL,
  `calls_reserved` int unsigned NOT NULL DEFAULT '0',
  `expires_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_budget_owner` (`owner_id`,`id`),
  CONSTRAINT `generation_budget_limits` CHECK (((`credits_limit` between 1 and 1000000000) and (`credits_reserved` <= `credits_limit`) and (`calls_limit` between 1 and 100) and (`calls_reserved` <= `calls_limit`))),
  CONSTRAINT `generation_budget_policy` CHECK ((`policy_version` = _latin1'operator-test/v1'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_jobs 既有generation任务,不等同新PublicMedia任务
CREATE TABLE `generation_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_conversation_id` char(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_run_id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `result_bytes` mediumblob,
  `updated_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_job_budget_owner` (`id`,`budget_id`,`owner_id`),
  UNIQUE KEY `generation_job_owned_scope` (`owner_id`,`project_id`,`session_id`,`id`),
  KEY `generation_job_owner` (`owner_id`,`project_id`,`session_id`),
  KEY `generation_job_session` (`session_id`),
  KEY `generation_job_budget` (`owner_id`,`budget_id`),
  CONSTRAINT `generation_job_budget` FOREIGN KEY (`owner_id`, `budget_id`) REFERENCES `generation_development_budgets` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_project` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_session` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_attempt` CHECK ((((`status` = _ascii'APPROVED') and (`attempts` = 0) and (`result_bytes` is null)) or ((`status` = _ascii'SUBMITTING') and (`attempts` = 1) and (`result_bytes` is null)) or ((`status` in (_ascii'SUCCEEDED',_ascii'FAILED',_ascii'UNKNOWN')) and (`attempts` = 1) and (`result_bytes` is not null)))),
  CONSTRAINT `generation_job_credit` CHECK ((`credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_reservations 既有任务费用预留
CREATE TABLE `generation_reservations` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `calls` int unsigned NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_reservation_job` (`job_id`,`budget_id`,`owner_id`),
  CONSTRAINT `generation_reservation_job` FOREIGN KEY (`job_id`, `budget_id`, `owner_id`) REFERENCES `generation_jobs` (`id`, `budget_id`, `owner_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_reservation_fixed` CHECK (((`credits` between 1 and 1000000000) and (`calls` = 1) and (`status` = _latin1'HELD')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_bindings 业务会话与Native Maa会话映射、模型版本与未决操作
CREATE TABLE `maa_bindings` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `model_revision` bigint unsigned NOT NULL DEFAULT '0',
  `pending_model_change_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_binding_business_scope` (`owner_id`,`project_id`,`conversation_id`),
  UNIQUE KEY `maa_binding_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `maa_binding_native_identity` (`native_cid`),
  KEY `maa_model_change_pending_fk` (`owner_id`,`project_id`,`conversation_id`,`id`,`pending_model_change_id`),
  CONSTRAINT `maa_binding_owned_conversation_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_pending_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `id`, `pending_model_change_id`) REFERENCES `maa_model_change_actions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_binding_receipt` CHECK ((((`status` = _ascii'dispatching') and (`native_cid` is null)) or ((`status` = _ascii'bound') and (`native_cid` is not null)))),
  CONSTRAINT `maa_model_binding_revision` CHECK ((`model_revision` <= 9007199254740991))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interaction_actions 原卡片回答/取消动作
CREATE TABLE `maa_interaction_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `interaction_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `response_action` varchar(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `values_payload` json NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_action_business_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_interaction_action_one_card` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`interaction_id`),
  CONSTRAINT `maa_interaction_action_owned_card_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `interaction_id`) REFERENCES `maa_interactions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_interaction_action_kind` CHECK ((`response_action` in (_latin1'submit',_latin1'cancel'))),
  CONSTRAINT `maa_interaction_action_status` CHECK ((`status` in (_latin1'dispatching',_latin1'accepted')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interactions 澄清卡片及Run归属
CREATE TABLE `maa_interactions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_payload` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_native_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`native_iid`),
  UNIQUE KEY `maa_interaction_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`id`),
  CONSTRAINT `maa_interaction_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_message_actions 原消息动作及幂等回执
CREATE TABLE `maa_message_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `proposed_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_action_business_identity` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  KEY `maa_action_owned_run_fk` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_action_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_receipt` CHECK ((((`status` = _latin1'dispatching') and (`public_run_id` is null)) or ((`status` = _latin1'accepted') and (`public_run_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_model_change_actions 带预期模型版本的切换动作
CREATE TABLE `maa_model_change_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `expected_revision` bigint unsigned NOT NULL,
  `from_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `from_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_attempt` bigint unsigned NOT NULL,
  `receipt_payload` json DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_model_change_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_model_change_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  CONSTRAINT `maa_model_change_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_attempt` CHECK ((`dispatch_attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_model_change_expected` CHECK ((`expected_revision` <= 9007199254740990)),
  CONSTRAINT `maa_model_change_status` CHECK ((((cast(`status` as char charset binary) = _latin1'dispatching') and (`receipt_payload` is null)) or ((cast(`status` as char charset binary) in (_latin1'accepted',_latin1'rejected')) and (`receipt_payload` is not null) and (json_type(`receipt_payload`) = _latin1'OBJECT')))),
  CONSTRAINT `maa_model_change_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_run_stop_actions 停止原Run的幂等动作与观测终态
CREATE TABLE `maa_run_stop_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stop_action` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt` bigint unsigned NOT NULL,
  `terminal_run_status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_stop_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_stop_one_run` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_stop_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_stop_action` CHECK ((`stop_action` = _latin1'interrupt')),
  CONSTRAINT `maa_stop_attempt` CHECK ((`attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_stop_state` CHECK ((((`status` = _latin1'unknown') and (`terminal_run_status` is null)) or ((`status` = _latin1'terminal_observed') and (`terminal_run_status` is not null) and (`terminal_run_status` in (_latin1'completed',_latin1'failed',_latin1'cancelled'))))),
  CONSTRAINT `maa_stop_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_runs 公开Run映射、状态与原生/公开助手证据摘要
CREATE TABLE `maa_runs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `native_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `public_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `native_assistant_bytes` bigint unsigned NOT NULL DEFAULT '0',
  `assistant_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  `last_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_run_native_identity` (`native_rid`),
  UNIQUE KEY `maa_run_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  KEY `maa_run_owned_list` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  CONSTRAINT `maa_run_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_run_source_evidence` CHECK ((((`native_assistant_sha256` is null) and (`public_assistant_sha256` is null) and (`native_assistant_bytes` = 0) and (`assistant_event_seq` = 0)) or ((`native_assistant_sha256` is not null) and (`public_assistant_sha256` is not null) and (`native_assistant_bytes` > 0) and (`assistant_event_seq` > 0) and (`assistant_event_seq` <= `last_event_seq`)))),
  CONSTRAINT `maa_run_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'waiting_interaction',_latin1'completed',_latin1'failed',_latin1'cancelled')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvas_revisions 历史画布修订
CREATE TABLE `studio_canvas_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_canvas_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_canvas_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_canvases` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_canvas_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_canvas_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_canvas_revision_kind` CHECK ((`kind` = _latin1'canvas')),
  CONSTRAINT `studio_canvas_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_canvas_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_canvas_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvases 项目当前画布文档
CREATE TABLE `studio_canvases` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_canvas_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_canvas_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_conversations 项目内的对话会话
CREATE TABLE `studio_conversations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_conversation_owned_scope` (`owner_id`,`project_id`,`id`),
  KEY `studio_conversation_list` (`owner_id`,`project_id`,`created_at`,`id`),
  CONSTRAINT `studio_conversation_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_recovery_intents 积分结算恢复意图;不是用户钱包全账本
CREATE TABLE `studio_credit_recovery_intents` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `intent_bytes` mediumblob NOT NULL,
  `intent_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_intent_job_kind` (`job_id`,`kind`),
  KEY `credit_intent_recovery` (`state`,`updated_at`,`id`),
  KEY `credit_intent_owned_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `credit_intent_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `credit_intent_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_intent_kind` CHECK (((`kind` in (_latin1'RESERVE_BIND',_latin1'CAPTURE',_latin1'RELEASE')) and (((`kind` = _latin1'RESERVE_BIND') and (`authorization_id` is null)) or ((`kind` in (_latin1'CAPTURE',_latin1'RELEASE')) and (`authorization_id` is not null))))),
  CONSTRAINT `credit_intent_lease` CHECK ((((`state` = _latin1'leased') and (`lease_token_sha256` is not null) and regexp_like(`lease_token_sha256`,_latin1'^[a-f0-9]{64}$') and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`state` <> _latin1'leased') and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `credit_intent_payload_sha` CHECK ((`intent_sha256` = lower(sha2(`intent_bytes`,256)))),
  CONSTRAINT `credit_intent_payload_size` CHECK ((length(`intent_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_intent_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_intent_state` CHECK ((`state` in (_latin1'pending',_latin1'leased',_latin1'confirmed',_latin1'unknown'))),
  CONSTRAINT `credit_intent_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_export_jobs 时间轴导出队列和发布输出
CREATE TABLE `studio_export_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `document_revision` bigint unsigned NOT NULL,
  `input_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_json` json NOT NULL,
  `output_json` json DEFAULT NULL,
  `error_code` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
  `lease_token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_export_input` (`project_id`,`document_revision`,`input_hash`),
  KEY `studio_export_claim` (`status`,`created_at`,`id`),
  KEY `studio_export_owner_list` (`owner_id`,`project_id`,`created_at`,`id`),
  KEY `studio_export_expiry` (`status`,`lease_until`),
  CONSTRAINT `studio_export_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_export_lease` CHECK ((((`status` = _latin1'running') and (`lease_token_hash` is not null) and (`lease_until` is not null)) or ((`status` <> _latin1'running') and (`lease_token_hash` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_export_output` CHECK ((((`status` = _latin1'succeeded') and (`output_json` is not null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'queued',_latin1'running')) and (`output_json` is null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'failed',_latin1'unknown')) and (`output_json` is null) and (`error_code` <> _latin1'')))),
  CONSTRAINT `studio_export_revision` CHECK ((`document_revision` between 1 and 2147483647)),
  CONSTRAINT `studio_export_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'succeeded',_latin1'failed',_latin1'unknown')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_jobs 已批准媒体任务及状态机
CREATE TABLE `studio_media_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_approval_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `approval_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credit_authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_mode` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_expires_at` datetime(6) DEFAULT NULL,
  `status` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `client_cancel_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_requested_at` datetime(6) DEFAULT NULL,
  `staged_asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `staged_asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_job_request` (`request_id`),
  UNIQUE KEY `studio_media_job_client` (`owner_id`,`client_approval_id`),
  UNIQUE KEY `studio_media_job_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_job_owned_request` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`),
  KEY `studio_media_job_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_job_queue` (`status`,`updated_at`,`id`),
  KEY `studio_media_job_request_fk` (`owner_id`,`project_id`,`conversation_id`,`request_id`,`request_sha256`,`quote_sha256`),
  CONSTRAINT `studio_media_job_request_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `request_id`, `request_sha256`, `quote_sha256`) REFERENCES `studio_media_requests` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`, `quote_sha256`),
  CONSTRAINT `studio_media_job_authorization` CHECK ((((`credit_authorization_id` is null) and (`authorization_mode` is null) and (`authorization_expires_at` is null) and (`status` in (_latin1'approval_pending',_latin1'cancelled'))) or ((`credit_authorization_id` is not null) and (`authorization_mode` is not null) and (`authorization_mode` in (_latin1'commercial',_latin1'test_allowance')) and (`authorization_expires_at` is not null) and (`authorization_expires_at` > `created_at`) and (`status` <> _latin1'approval_pending')))),
  CONSTRAINT `studio_media_job_cancel` CHECK ((((`client_cancel_id` is null) and (`cancel_request_sha256` is null) and (`cancel_requested_at` is null)) or ((`client_cancel_id` is not null) and (`cancel_request_sha256` is not null) and (`cancel_requested_at` is not null) and (`cancel_requested_at` between `created_at` and `updated_at`)))),
  CONSTRAINT `studio_media_job_lease` CHECK ((((`status` in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is not null) and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`status` not in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_media_job_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_job_staged` CHECK ((((`staged_asset_id` is null) and (`staged_asset_sha256` is null)) or ((`staged_asset_id` is not null) and (`staged_asset_sha256` is not null)))),
  CONSTRAINT `studio_media_job_status` CHECK ((`status` in (_latin1'approval_pending',_latin1'queued',_latin1'leased',_latin1'submitting',_latin1'running',_latin1'submission_unknown',_latin1'succeeded',_latin1'failed',_latin1'cancelled'))),
  CONSTRAINT `studio_media_job_task` CHECK (((`status` not in (_latin1'running',_latin1'succeeded')) or (`provider_task_id` is not null))),
  CONSTRAINT `studio_media_job_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_outputs 受验证的输出与素材映射
CREATE TABLE `studio_media_outputs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `ordinal` int unsigned NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_result_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_output_job` (`job_id`,`ordinal`),
  KEY `studio_media_output_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`),
  CONSTRAINT `studio_media_output_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`),
  CONSTRAINT `studio_media_output_ordinal` CHECK ((`ordinal` = 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_requests 用户输入/Agent来源、报价、批准摘要与准入确认
CREATE TABLE `studio_media_requests` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_bytes` mediumblob NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quoted_credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_snapshot_bytes` mediumblob,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_request_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_request_quoted` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`,`quote_sha256`),
  UNIQUE KEY `studio_media_request_client` (`owner_id`,`project_id`,`conversation_id`,`client_request_id`),
  KEY `studio_media_request_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_request_source_fk` (`owner_id`,`project_id`,`conversation_id`,`source_binding_id`,`source_run_id`),
  CONSTRAINT `studio_media_request_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`),
  CONSTRAINT `studio_media_request_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `source_binding_id`, `source_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`),
  CONSTRAINT `studio_media_quote_credits` CHECK ((`quoted_credits` between 1 and 1000000000)),
  CONSTRAINT `studio_media_quote_sha` CHECK ((`quote_sha256` = lower(sha2(`quote_bytes`,256)))),
  CONSTRAINT `studio_media_quote_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)))),
  CONSTRAINT `studio_media_request_origin` CHECK ((((`origin` in (_latin1'ui',_latin1'user_edit')) and (`source_binding_id` is null) and (`source_run_id` is null) and (`source_assistant_sha256` is null) and (`source_snapshot_bytes` is null)) or ((`origin` = _latin1'agent') and (`source_binding_id` is not null) and (`source_run_id` is not null) and (`source_assistant_sha256` is not null) and (`source_snapshot_bytes` is not null) and (length(`source_snapshot_bytes`) between 1 and 65536) and (`source_assistant_sha256` = lower(sha2(`source_snapshot_bytes`,256)))))),
  CONSTRAINT `studio_media_request_sha` CHECK ((`request_sha256` = lower(sha2(`request_bytes`,256)))),
  CONSTRAINT `studio_media_request_size` CHECK (((length(`request_bytes`) between 1 and 65536) and (length(`quote_bytes`) between 1 and 65536)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_submission_attempts 一次提交的任务证据与不确定结果
CREATE TABLE `studio_media_submission_attempts` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt_no` int unsigned NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stage` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_started_at` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `terminal_receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `reconciliation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_attempt_job` (`job_id`,`attempt_no`),
  UNIQUE KEY `studio_media_attempt_idempotency` (`provider_idempotency_key`),
  KEY `studio_media_attempt_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `studio_media_attempt_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `studio_media_attempt_number` CHECK ((`attempt_no` = 1)),
  CONSTRAINT `studio_media_attempt_state` CHECK ((((`stage` = _latin1'prepared') and (`dispatch_started_at` is null) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'dispatching',_latin1'unknown')) and (`dispatch_started_at` is not null) and (`dispatch_started_at` >= `created_at`) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` = _latin1'acknowledged') and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'completed',_latin1'failed')) and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is not null)) or ((`stage` = _latin1'reconciled_not_sent') and (`dispatch_started_at` is not null) and (`provider_task_id` is null) and (`reconciliation_sha256` is not null)))),
  CONSTRAINT `studio_media_attempt_times` CHECK (((`updated_at` >= `created_at`) and ((`dispatch_started_at` is null) or (`dispatch_started_at` between `created_at` and `updated_at`))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_project_asset_refs 项目对用户级素材的引用;不复制素材所有权
CREATE TABLE `studio_project_asset_refs` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `detached_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`project_id`,`asset_id`),
  KEY `studio_project_asset_owned_page` (`owner_id`,`project_id`,`asset_id`),
  KEY `studio_project_asset_reverse` (`owner_id`,`asset_id`,`project_id`),
  CONSTRAINT `studio_project_asset_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_project_asset_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_project_asset_state` CHECK ((((`state` = _latin1'attached') and (`detached_at` is null)) or ((`state` = _latin1'detached') and (`detached_at` is not null) and (`detached_at` = `updated_at`)))),
  CONSTRAINT `studio_project_asset_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_projects 用户拥有的创作项目
CREATE TABLE `studio_projects` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_project_owner_identity` (`owner_id`,`id`),
  KEY `studio_project_owner_list` (`owner_id`,`created_at`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_timeline_revisions 历史时间轴修订
CREATE TABLE `studio_timeline_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_timeline_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_timeline_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_video_documents` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_timeline_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_timeline_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_timeline_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_timeline_revision_kind` CHECK ((`kind` = _latin1'timeline')),
  CONSTRAINT `studio_timeline_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_timeline_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_timeline_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_video_documents 项目当前时间轴文档
CREATE TABLE `studio_video_documents` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_timeline_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_document_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_document_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
共享身份库(15 张)与积分账本 七个品牌相同,展开查看
studio_assets
CREATE TABLE `studio_assets` (
  `id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `storage_key` varchar(512) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `publication_state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT 'visible',
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_asset_owner_digest` (`owner_id`,`sha256`),
  KEY `studio_asset_owner_list` (`owner_id`,`created_at`,`id`),
  KEY `studio_asset_visible_list` (`owner_id`,`publication_state`,`created_at`,`id`),
  CONSTRAINT `studio_asset_publication_state` CHECK (CAST(publication_state AS BINARY) IN ('pending','visible')),
  CONSTRAINT `studio_asset_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_billing_cycles
CREATE TABLE `studio_billing_cycles` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `payment_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_invoice_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `billing_cycle_owned` (`owner_id`,`id`),
  UNIQUE KEY `billing_cycle_period` (`subscription_id`,`period_start`),
  UNIQUE KEY `billing_cycle_invoice` (`provider_key`,`provider_account`,`provider_invoice_id`),
  UNIQUE KEY `billing_cycle_order` (`payment_order_id`),
  KEY `billing_cycle_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  KEY `billing_cycle_order_fk` (`owner_id`,`payment_order_id`,`plan_version_id`),
  KEY `billing_cycle_order_provider_fk` (`owner_id`,`payment_order_id`,`provider_key`,`provider_account`),
  CONSTRAINT `billing_cycle_order_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `plan_version_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `plan_version_id`),
  CONSTRAINT `billing_cycle_order_provider_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_amount` CHECK ((`grant_credits` between 1 and 1000000000)),
  CONSTRAINT `billing_cycle_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `billing_cycle_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `billing_cycle_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `billing_cycle_state` CHECK (((`state` in (_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `billing_cycle_times` CHECK (((`period_end` > `period_start`) and (`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_accounts
CREATE TABLE `studio_credit_accounts` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `unit` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`),
  CONSTRAINT `credit_account_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `credit_account_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_account_status` CHECK ((`status` in (_latin1'open',_latin1'suspended'))),
  CONSTRAINT `credit_account_unit` CHECK ((`unit` = _latin1'credits'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorization_allocations
CREATE TABLE `studio_credit_authorization_allocations` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `allocated_credits` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`authorization_id`,`lot_id`),
  UNIQUE KEY `credit_allocation_owned` (`owner_id`,`authorization_id`,`lot_id`),
  KEY `credit_allocation_lot` (`owner_id`,`lot_id`,`authorization_id`),
  CONSTRAINT `credit_allocation_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_amount` CHECK ((`allocated_credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorizations
CREATE TABLE `studio_credit_authorizations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `profile` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `reservation_bytes` mediumblob NOT NULL,
  `reservation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_bytes` mediumblob NOT NULL,
  `policy_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_authorization_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_authorization_job` (`owner_id`,`profile`,`job_id`),
  CONSTRAINT `credit_authorization_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_authorization_amount` CHECK ((`credits` between 1 and 1000000000)),
  CONSTRAINT `credit_authorization_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_authorization_policy_sha` CHECK ((`policy_sha256` = lower(sha2(`policy_bytes`,256)))),
  CONSTRAINT `credit_authorization_policy_size` CHECK ((length(`policy_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_profile` CHECK ((`profile` in (_latin1'topview',_latin1'pollo',_latin1'invideo',_latin1'libtv',_latin1'flova',_latin1'creatok',_latin1'creatify'))),
  CONSTRAINT `credit_authorization_reservation_sha` CHECK ((`reservation_sha256` = lower(sha2(`reservation_bytes`,256)))),
  CONSTRAINT `credit_authorization_reservation_size` CHECK ((length(`reservation_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_authorization_state` CHECK ((`state` in (_latin1'HELD',_latin1'CAPTURED',_latin1'RELEASED'))),
  CONSTRAINT `credit_authorization_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_ledger_entries
CREATE TABLE `studio_credit_ledger_entries` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `operation_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_delta` bigint NOT NULL,
  `held_delta` bigint NOT NULL,
  `captured_delta` bigint NOT NULL,
  `facts_bytes` mediumblob NOT NULL,
  `facts_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_entry_operation` (`owner_id`,`operation_key`,`lot_id`),
  KEY `credit_entry_page` (`owner_id`,`created_at`,`id`),
  KEY `credit_entry_lot_fk` (`owner_id`,`lot_id`),
  KEY `credit_entry_allocation_fk` (`owner_id`,`authorization_id`,`lot_id`),
  CONSTRAINT `credit_entry_allocation_fk` FOREIGN KEY (`owner_id`, `authorization_id`, `lot_id`) REFERENCES `studio_credit_authorization_allocations` (`owner_id`, `authorization_id`, `lot_id`),
  CONSTRAINT `credit_entry_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_delta` CHECK ((((`kind` = _latin1'grant') and (`authorization_id` is null) and (`grant_delta` between 1 and 1000000000) and (`held_delta` = 0) and (`captured_delta` = 0)) or ((`kind` = _latin1'hold') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between 1 and 1000000000) and (`captured_delta` = 0)) or ((`kind` = _latin1'capture') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`captured_delta` between 1 and 1000000000) and (`held_delta` = -(`captured_delta`))) or ((`kind` = _latin1'release') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between -(1000000000) and -(1)) and (`captured_delta` = 0)))),
  CONSTRAINT `credit_entry_facts_sha` CHECK ((`facts_sha256` = lower(sha2(`facts_bytes`,256)))),
  CONSTRAINT `credit_entry_facts_size` CHECK ((length(`facts_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_entry_operation_key` CHECK ((((`kind` = _latin1'grant') and (`operation_key` = concat(`lot_id`,_latin1':grant'))) or ((`kind` <> _latin1'grant') and (`operation_key` = concat(`authorization_id`,_latin1':',`kind`)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_lots
CREATE TABLE `studio_credit_lots` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `held_credits` bigint unsigned NOT NULL,
  `captured_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_cycle_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_lot_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_lot_order` (`source_order_id`),
  UNIQUE KEY `credit_lot_cycle` (`source_cycle_id`),
  KEY `credit_lot_eligible` (`owner_id`,`expires_at`,`id`),
  KEY `credit_lot_order_fk` (`owner_id`,`source_order_id`),
  KEY `credit_lot_cycle_fk` (`owner_id`,`source_cycle_id`),
  CONSTRAINT `credit_lot_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_lot_cycle_fk` FOREIGN KEY (`owner_id`, `source_cycle_id`) REFERENCES `studio_billing_cycles` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_order_fk` FOREIGN KEY (`owner_id`, `source_order_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_counters` CHECK (((`grant_credits` between 1 and 1000000000) and (`held_credits` <= 1000000000) and (`captured_credits` <= 1000000000) and ((`held_credits` + `captured_credits`) <= `grant_credits`))),
  CONSTRAINT `credit_lot_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_lot_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_lot_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_lot_source` CHECK ((((`source_order_id` is not null) and (`source_cycle_id` is null)) or ((`source_order_id` is null) and (`source_cycle_id` is not null)))),
  CONSTRAINT `credit_lot_times` CHECK (((`expires_at` > `valid_from`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_plan_versions
CREATE TABLE `studio_credit_plan_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `credit_validity_seconds` int unsigned NOT NULL,
  `billing_interval_months` int unsigned NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  `retired_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_plan_version_key` (`plan_key`,`version_key`),
  CONSTRAINT `credit_plan_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `credit_plan_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `credit_plan_duration` CHECK (((`credit_validity_seconds` between 1 and 31622400) and ((`retired_at` is null) or (`retired_at` >= `published_at`)))),
  CONSTRAINT `credit_plan_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_plan_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_plan_kind` CHECK (((`kind` in (_latin1'subscription',_latin1'topup')) and (((`kind` = _latin1'subscription') and (`billing_interval_months` in (1,3,12))) or ((`kind` = _latin1'topup') and (`billing_interval_months` = 0)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_model_versions
CREATE TABLE `studio_media_model_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `config_bytes` mediumblob NOT NULL,
  `config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_version_key` (`model_id`,`version_key`),
  UNIQUE KEY `studio_media_version_owned_model` (`model_id`,`id`),
  CONSTRAINT `studio_media_version_model_fk` FOREIGN KEY (`model_id`) REFERENCES `studio_media_models` (`id`),
  CONSTRAINT `studio_media_version_sha` CHECK ((`config_sha256` = lower(sha2(`config_bytes`,256)))),
  CONSTRAINT `studio_media_version_size` CHECK ((length(`config_bytes`) between 1 and 65536))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_models
CREATE TABLE `studio_media_models` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `display_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_model_key` (`model_key`),
  KEY `studio_media_model_published_fk` (`id`,`published_version_id`),
  CONSTRAINT `studio_media_model_published_fk` FOREIGN KEY (`id`, `published_version_id`) REFERENCES `studio_media_model_versions` (`model_id`, `id`),
  CONSTRAINT `studio_media_model_kind` CHECK ((`kind` = _ascii'video')),
  CONSTRAINT `studio_media_model_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_model_status` CHECK (((`status` in (_ascii'draft',_ascii'active',_ascii'disabled')) and ((`status` <> _ascii'active') or (`published_version_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_events
CREATE TABLE `studio_payment_events` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_event_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `body_bytes` mediumblob NOT NULL,
  `body_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_event_provider` (`provider_key`,`provider_account`,`provider_event_id`),
  KEY `payment_event_order_fk` (`owner_id`,`order_id`,`provider_key`,`provider_account`),
  KEY `payment_event_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  CONSTRAINT `payment_event_order_fk` FOREIGN KEY (`owner_id`, `order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_event_body_sha` CHECK ((`body_sha256` = lower(sha2(`body_bytes`,256)))),
  CONSTRAINT `payment_event_body_size` CHECK ((length(`body_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_event_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_event_state` CHECK ((`state` in (_latin1'VERIFIED',_latin1'PROCESSED'))),
  CONSTRAINT `payment_event_subject` CHECK (((`order_id` is not null) or (`subscription_id` is not null))),
  CONSTRAINT `payment_event_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_orders
CREATE TABLE `studio_payment_orders` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_order_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_order_owned` (`owner_id`,`id`),
  UNIQUE KEY `payment_order_owned_plan` (`owner_id`,`id`,`plan_version_id`),
  UNIQUE KEY `payment_order_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `payment_order_client` (`owner_id`,`client_action_id`),
  UNIQUE KEY `payment_order_provider_key` (`provider_key`,`provider_account`,`provider_idempotency_key`),
  UNIQUE KEY `payment_order_provider_id` (`provider_key`,`provider_account`,`provider_order_id`),
  KEY `payment_order_plan_fk` (`plan_version_id`),
  CONSTRAINT `payment_order_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `payment_order_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_order_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `payment_order_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `payment_order_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `payment_order_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_order_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_order_state` CHECK (((`state` in (_latin1'CREATING',_latin1'CREATE_UNKNOWN',_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`provider_order_id` is not null) and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `payment_order_times` CHECK (((`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_sessions
CREATE TABLE `studio_sessions` (
  `token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `user_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `revoked_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`token_hash`),
  KEY `studio_session_expiry` (`expires_at`),
  KEY `studio_session_user` (`user_id`,`expires_at`),
  CONSTRAINT `studio_session_user_fk` FOREIGN KEY (`user_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_session_times` CHECK ((`expires_at` > `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_subscriptions
CREATE TABLE `studio_subscriptions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_subscription_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `cancel_at_period_end` tinyint(1) NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `subscription_owned` (`owner_id`,`id`),
  UNIQUE KEY `subscription_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `subscription_provider` (`provider_key`,`provider_account`,`provider_subscription_id`),
  KEY `subscription_plan_fk` (`plan_version_id`),
  CONSTRAINT `subscription_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `subscription_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `subscription_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `subscription_state` CHECK (((`state` in (_latin1'PENDING',_latin1'ACTIVE',_latin1'PAST_DUE',_latin1'CANCELLED',_latin1'ENDED')) and (`cancel_at_period_end` in (0,1)))),
  CONSTRAINT `subscription_times` CHECK (((`period_end` > `period_start`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_users
CREATE TABLE `studio_users` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `email` varchar(254) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `password_hash` varbinary(72) NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT '1',
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_user_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

积分账本链路

plan_versions → payment_orders(CREATING→PAID)→ payment_events(VERIFIED)→ credit_lots(带适用模型与品牌)→ ledger_entries(grant / hold / capture / release,只追加)→ authorizations / allocations。批准生成时按批次分配冻结额度,扣费与释放都有幂等键。
为什么这样设计账本只追加、用触发器禁止修改,才有可信的对账;批准时按批次分配冻结额度,才能在失败时准确释放。约束写在数据库里而不是应用里,是因为应用会有 bug,而约束不会(决策 D3、D17)。

LibTV · 接口清单

共 55 个公开接口,取自源码路由盘点。路径都以 /v1 开头;写操作需要会话 Cookie、可信 Host/Origin 与 CSRF;带品牌的路径用 ?profile=libtv;共享的登录、素材、模型目录路径不接受 profile 参数。

Agent/统一动作 7 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactionsrunInteractions200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactions/{interaction_id}/responsesrespondInteractionclient_action_id, card_sha256, action, values202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/interaction-actions/{client_action_id}interactionAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changeschangeConversationModelclient_action_id, expected_revision, agent_model, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changes/{client_action_id}modelChangeAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stopstopRunclient_action_id, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/stop-actions/{client_action_id}stopAction200
Asset 4 个
方法路径operationId请求字段成功
GET/v1/assetslistAssets200
POST/v1/assetsuploadAssetfile201
GET/v1/assets/{asset_id}getAsset200
GET/v1/assets/{asset_id}/contentassetContent200/206
LoginSession 1 个
方法路径operationId请求字段成功
POST/v1/auth/logoutlogout200
MaaBinding 5 个
方法路径operationId请求字段成功
GET/v1/maa/model-optionsmaaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/messagessendMaaMessageclient_action_id, content202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/actions/{client_action_id}getMaaAction200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stategetMaaState200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/eventsmaaEvents200
Media 11 个
方法路径operationId请求字段成功
GET/v1/media/modelspublicMediaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requestscreateMediaRequestclient_request_id, model_id, model_version_id, prompt, duration_seconds, ratio, resolution, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requestsmediaRequestByClient200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}getMediaRequest200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}getMediaJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}/outputgetMediaOutput200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/jobmediaJobByRequest200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/approveapproveMediaRequestclient_approval_id, request_sha256, quote_sha256200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/from-maacreateMediaRequestFromMaainput, source_run_id, source_assistant_sha256, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobslistMediaJobs200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/intentslistMediaIntents200
Other 9 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/canvas/revisionslistCanvasRevisions200
GET/v1/projects/{project_id}/canvas/revisions/{revision}getCanvasRevision200
POST/v1/projects/{project_id}/canvas/revisions/{revision}/restorerestoreCanvasRevisionexpected_revision200
GET/v1/projects/{project_id}/document/revisionslistTimelineRevisions200
GET/v1/projects/{project_id}/document/revisions/{revision}getTimelineRevision200
POST/v1/projects/{project_id}/document/revisions/{revision}/restorerestoreTimelineRevisionexpected_revision200
GET/v1/projects/{project_id}/assetslistProjectAssets200
POST/v1/projects/{project_id}/assetsattachProjectAssetasset_id, expected_revision200
POST/v1/projects/{project_id}/assets/{asset_id}/detachdetachProjectAssetexpected_revision200
Project 15 个
方法路径operationId请求字段成功
GET/v1/projectslistProjects200
POST/v1/projectscreateProjecttitle201
GET/v1/projects/{project_id}getProject200
GET/v1/projects/{project_id}/conversationslistConversations200
POST/v1/projects/{project_id}/conversationscreateConversationtitle201
GET/v1/projects/{project_id}/conversations/{conversation_id}getConversation200
GET/v1/projects/{project_id}/canvasgetCanvas200
POST/v1/projects/{project_id}/canvassaveCanvasexpected_revision, state200
GET/v1/projects/{project_id}/documentgetDocument200
POST/v1/projects/{project_id}/documentsaveDocumentexpected_revision, state200
GET/v1/projects/{project_id}/export-jobslistExportJobs200
POST/v1/projects/{project_id}/export-jobsqueueExportdocument_revision, output?202
GET/v1/projects/{project_id}/export-jobs/{job_id}getExportJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindinggetMaaBinding200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindingbindMaaagent_model XOR use_default:true200
User 3 个
方法路径operationId请求字段成功
POST/v1/auth/registerregisteremail, password201
POST/v1/auth/loginloginemail, password200
GET/v1/auth/sessionsession200

LibTV 自己公开的接口调用流程(来自其官方资料)

步骤调用关系操作证据
加载Web与脚本浏览器 → Web入口GET https://www.liblib.tv/;返回HTML并加载liblibai-web-static.liblib.cloud官方JS;入口与静态资源响应分别为istio-envoy/Tengine。O
查询Skill模板浏览器 → 业务HTTP入口POST https://api.liblib.tv/api/community/skill/template/feed/stream;body含page/pageSize/query/tagId;返回JSON list/hasMore/total与skillKey/version/snapshotO
读取Canvas上下文浏览器 → 业务HTTP入口客户端GET /api/canvas/project/detail,uuid来自projectUuid;Skill引用在session create中可携带skillKey/skillVersion,画布draft与版本由独立路由管理。C
加载或创建会话浏览器 → IM入口GET https://im.liblib.tv/api/v1/project/session/list含projectId/type;POST /api/v1/project/session/create含projectId/name及可选canvasId/nodeKey/skillKey/skiC
发送Agent消息浏览器 → IM入口客户端构造wss://im.liblib.tv/ws;query字段名含token/project_id及可选canvas_id/team_id;上行JSON有threadId/messages/forwardedProps.extra。C
消费增量事件IM入口 → 浏览器客户端消费RUN_STARTED、TEXT_MESSAGE_START/CONTENT/END及type/seq/runId/messageId/role/delta;处理ping/pong、重连与缓存入站事件。C
保存画布修改浏览器 → 业务HTTP入口POST /api/canvas/nodes/batch含projectUuid/requestId/nodes与connections增改删;POST draft/update含draftJson/canvasConfigJson/viewport坐标;编辑上下文可带sessionId/timesC
读取生成与产物浏览器 → 业务HTTP入口客户端存在/api/task/generation/create、progress、power/calculator与/api/task/artifact/list,以及媒体创建/list/copy-to-team等路径。C
为什么这样设计所有写动作都带客户端幂等键(client_action_id / client_request_id / client_approval_id),重复提交返回原回执;报价和批准分成两个接口;停止、切换模型、澄清卡回答都是「动作」而不是「状态修改」,所以每一步都能查回执。这样前端可以放心地重试,后端也不会因为重试产生第二次副作用(决策 D2、D8、D14)。

LibTV · 代码与下载

libtv-replica.zip
1.4 MB · 54 个文件 · SHA-256 1e333b70c1b66eac71ed5b15c5f508dcce4b0249ce0455701824ff232fcb426b
下载:libtv-replica.zip · 后端与合同 · 全部代码
在线试用:http://119.23.106.60:8104/。测试环境,整套独立部署(前端、BFF、Maa 智能体、数据库),可以注册、对话、生成视频(Seedance,测试积分)。页面右下角有“内部复刻研究”标识。
目录内容
frontend/LibTV 前端:<code>candidate-8875-unified-v22.html</code> 及它引用的全部脚本与样式
contracts/本品牌的表结构 SQL、ER(svg / mmd)、外键清单;接口目录(json / csv);共享身份库表结构
README.md怎么运行、需要自备什么

运行前端

python3 -m http.server -d frontend 8791
# 打开 http://127.0.0.1:8791/agent-integration/apps-production-brand-v14/libtv/candidate-8875-unified-v22.html
后端和全部品牌:Go 后端、Python 媒体桥、全部测试和 7 个品牌的合同在 backend-and-contracts.zip;all-brands-code.zip 是全部。包里没有任何凭据、数据库备份或媒体文件,已做敏感串扫描。
需自备:MySQL 与账号(按 contracts 建库)、Maa 智能体服务(MAA_AGENT_ROOT)、模型网关与对象存储配置。静态前端单独打开不需要后端,但登录、对话、生成、导出需要整套服务。

复刻完成度(审计 2026-10-19)

结论:不是完美复刻。官方资料里的 34 项能力:实测跑通 3、只有通用对话 2、后端有但没跑过 9、没有后端 20。

真正跑通的:LibTV Agent BETA;Agent 项目多会话;导演执导。

界面对照(首页(已登录)):真实页面采集到 59 个控件 / 标题文字,复刻页找到 29 个;位置偏差中位数 88 px(5/29 个在 24 px 内)。缺的有:LibTV Agent BETA、学院 大师开讲、TV Show 全网爆款、创作者挑战赛 王者大赛、版本更新记录 v1.5、积分超市、查看全部、王者荣耀联名投稿活动 进行中、昔年、Flicker2045、炖捣像素、Nibgnahz…。

逐个点击(已登录首页):45 个可点控件,点了有效 15 个,只提示“此功能正在接入” 26 个,没反应或只变样式 0 个,禁用 0 个,未点(发送 / 删除 / 退出等)4 个。

只提示“正在接入”的控件(26 次点击,26 种)

开通会员、账号、首页、插件、学院、TV Show、创作者挑战赛、(图标)、↺ 版本更新记录、王者荣耀联名投稿活动、3D-BOX、全部、LibTV·「X小时之后」AI短片黑客松、全网爆款、王者无界剧场计划、精选画布、专业影视、短剧漫剧、商业广告、动漫游戏、教育生活、TV工具箱、降解师、抵达之前 before arrive、她能预知未来、聊斋系列之《鸦头》

复刻页自报的绑定状态(页面内嵌的绑定清单):原生工作区控件(新建项目、会话、登录等)8 / 10 个没有绑定到原页面的控件,由统一的工作区条代替;路由 projects、timeline、assets 缺失或只做了一部分;

首屏截图对照(左:真实产品 libtv/source-desktop-top-safe.png;右:复刻页)· 粗粒度差异 5%

真实截图是测试账号登录后的画面,内容(作品、项目、横幅)本来就不同,差异百分比只作参考,重点看布局。

LibTV:左为真实产品截图,右为复刻页

Flova · 总览

重点Flova 看这三行就够
  • 它是什么:画布 / 分镜 / 时间轴三视图;Agent 交互:发送前就提示积分够不够。
  • 我们借鉴:发送前说明准入;三视图共用一份项目(决策 D1 D7)。
  • 复刻现状:4 项已验证,在线试用 http://119.23.106.60:8105/;后端与其余六个品牌共用一套(24 张表、55 个接口),所以 ER / 表结构 / 接口页的通用部分默认折叠。

按 12 类盘点 Agent、会话、素材、画布、分镜、时间轴、生成、Skill、文档、导出、订阅团队及外部 CLI/MCP。最新更新日志仅按官方公告 W 标注。

证据:W 官方说明,A 官方接口合同,C 客户端代码,O 实测。“未实测”= 只核对了官方页面,没有上传素材或生成。

能力目录
63 项,分 12 类
入口
首页 + 画布/分镜/时间轴
复刻后端
24 张表 · 34 个外键 · 85 个索引

能力目录

Agent 与规划 6 项
能力说明证据限制
从需求形成创作方案Agent 结合项目状态理解需求,组织剧本、分镜、生成和剪辑。W未核实私有接口、质量表现及当前账号的具体额度。
生成前确认提示词项目可设自动或先确认。W未运行。
显示进度并停止调整可查看执行过程,停止任务后补充信息,再让 Agent 继续;手工编辑可并行进行。W停止与恢复的任务状态、扣费和取消语义没有公开合同。
运行中管理消息队列排队消息可编辑或重试。W未运行。
主动搜索参考资料检索参考并保存为项目文档。W待登录验证。
选项卡片澄清需求用单选或多选卡片补充需求。W待登录验证。
项目与会话 4 项
能力说明证据限制
创建项目与继续历史工作Web 快速开始与 CLI 指南都说明新建或继续已有项目,可从目标或 Skill 开始。W快速开始采用本轮官方检索正文;直读链接失败。项目与会话的内部关联未证实。
从消息恢复或另起分支项目可返回某次 AI 回复的项目状态,或从该时点创建新项目继续探索。W这是产品层的恢复和另起项目语义,不能推断数据库版本 DAG 或合并算法。
画布局部多轮会话局部追问、参考和历史。W未运行。
旧项目迁移至新版旧项目可迁入新版工作区。W历史兼容性待验。
素材管理 5 项
能力说明证据限制
多类型素材和文档上传项目接收图片、视频、音频和文档,供 Agent 与手工创作引用。W不同入口的格式、尺寸和访问权限待验证。
Asset 内保留 Media 版本同一创作意图可收纳多份 Media,通过默认选择或收藏使用合适结果。W公开 UI 语义不证明 Asset、Media 的数据库结构或物理文件去重方式。
移动拆分与批量管理素材文件可移动到其他 Asset、拆成新 Asset;支持选取、删除、下载或继续上传。W未核实私有接口、质量表现及当前账号的具体额度。
跨项目 Asset Library复用素材与生产文档。W授权边界待验。
裁剪素材为新 Asset裁剪图片、视频或音频为新素材。W未运行。
画布与分镜 4 项
能力说明证据限制
素材画布与预览列表Media Space 可自由排列素材,也可用预览列表按故事顺序浏览。W素材画布不等于已验证具有可执行节点边和依赖工作流。
分镜元素与镜头编辑可编辑或增删角色元素、镜头和音频卡片,调整顺序并放入聊天上下文。W未核实私有接口、质量表现及当前账号的具体额度。
Asset 绑定分镜并区分用途同一 Asset 可关联多个分镜,区分参考、关键帧和最终媒体等用途。W官方说明替换 Asset 的 Media 会更新引用位置;我方版本绑定策略需独立设计。
画布缩放与对齐缩放、翻转、旋转和吸附。W操作对象待验。
时间轴编辑 3 项
能力说明证据限制
Agent 初剪与人工精修Agent 先组织剪辑,用户继续调整;时间轴和分镜分别描述节奏与镜头意图。W调整时间轴不等于改写分镜文本,二者同步边界需登录验证。
裁切替换速度与音量调整手工调整片段顺序、时长、速度、音量和视听同步。W未核实私有接口、质量表现及当前账号的具体额度。
片段拆分淡入与撤销拆分音视频片段、淡入和撤销。W操作组合待验。
生成与模型 13 项
能力说明证据限制
模型参数与多模态参考在手工生成工具中选择模型、改提示词和参数,添加参考素材后重新生成版本。W模型的参考类型和参数支持不同,不把品牌数量当成功能数量。
图片放大对图片进行清晰度升级,保留为新媒体版本。W是否扣费、分辨率和套餐准入以当前估价和账号为准。
涂画标注指导再生成部分模型支持图片涂画参考。W限指定模型。
视频延长指定模型可续接并沿用原设置。W限支持模型。
提取视频静帧从视频当前画面提取图片,建立新的素材。W画面尺寸和输出格式待实际检查。
针对单份素材评论生成对素材描述问题后生成新结果,不必重做整个项目;结果按意图归入原 Asset 或新 Asset。W这里是局部生成反馈,不能据此称为多人审阅评论系统。
视频放大对视频进行清晰度升级,保留为新媒体版本。W会员和费用以当前参数预估为准;输出清晰度与帧率待验。
视频字幕去除指定模型可移除烧录字幕并保留新版本。W帮助明确 Seedance 2.0 结果;更新日志另列 Seedance 2.5,不推广到所有模型。
可编辑 Prompt Draft 卡片先修改草稿卡片,再付费生成。W不等于全局审批设置。
文字生成图片以描述生成独立图片素材。W具体模型、参数和费用待账号确认。
文字生成视频用文字描述生成视频。W限支持模型。
图片生成视频参考图片生成视频。W限支持模型。
多媒体参考生成视频组合多类参考生成视频。W参考限制依模型。
声音人物字幕 4 项
能力说明证据限制
音乐生成让 Agent 生成音乐,并用于项目分镜与时间轴。W音乐质量、版权条件和可用模型需按账号验证。
语音输入创作指令聊天支持语音输入。W识别语言待验。
配音与旁白声音选择Agent 生成配音,旁白可选声音和情绪。W克隆与口型同步未知。
提取视频音频从视频抽取音轨供后续复用。W单击抽取入口未实测。
Skill 与模板 5 项
能力说明证据限制
官方社区与个人 Skill可发现官方或社区工作流,选用 Skill 开始项目,并保存到个人库。WSkills 采用本轮官方检索正文;直读链接失败。未验证执行版本和公开审核流程。
自定义和复制 Skill让 Agent 创建或修改创作流程,查看结构化文档,再保存为自己的 Skill。W未核实私有接口、质量表现及当前账号的具体额度。
分享复制和撤销 Skill 链接分享链接供他人预览并复制;撤销旧链接不收回已经保存的副本。W采用官方检索正文;未发布或撤销分享。权限和付费门槛待验。
发布 Skill 固定引用素材固定版本并带入素材副本。W未运行。
Flova TV 作品关联创作流程公开作品可关联 Skill,用于继续同类创作。W发布权限待验。
文档与版本 4 项
能力说明证据限制
项目文档和参考原件项目可创建编辑文档;上传的参考原件可供阅读,生成的 Markdown 可继续修改。W未核实私有接口、质量表现及当前账号的具体额度。
局部文档加入 Agent 上下文选择文档片段放入聊天,维护 FinalVideoSpec 或 Skill 指令,聚焦修改目标。W未核实私有接口、质量表现及当前账号的具体额度。
文档和 Skill 修改对比查看 Agent 修改差异。W未运行。
并行修改时选择保留版本用户与 Agent 处理同一内容产生冲突时,可在界面选择要保留的结果。W不证明字段级自动合并、锁协议或数据库 CAS。
导出与交付 4 项
能力说明证据限制
后台导出当前时间轴导出当前剪辑,查看状态,完成后下载;失败可在界面重试。W导出耗时、重试扣费和幂等合同未验证。
交接外部剪辑工程可导出含素材的剪辑工程包,交给 Premiere、Final Cut 或区域对应的剪映/CapCut。W各软件支持条件不同,媒体路径、转场和音频兼容性需真实导入验证。
项目与素材多种下载支持项目媒体 ZIP、单个文件、Asset 多版本及所选素材批量下载。W批量只包含已成功生成的内容;不能据此认定为完整审计备份。
截取时间轴静帧可把播放头对应的画面保存为图片。W未核实私有接口、质量表现及当前账号的具体额度。
订阅与团队 6 项
能力说明证据限制
模型费用估算与积分用量积分随模型、参数和素材变化;账号可查余额、消耗与估价,生成受限时有原因说明。W不写固定价格或额度;失败返还、有效期和账单结算需按具体订单核实。
订阅分层与资源权益会员权益涵盖模型、并发、清晰度、放大、素材容量、去水印等,支持管理续费和升级。W个人和团队、地区及活动权益可能不同;商业许可按素材与模型条款核验。
个人团队工作区与资源管理切换工作区、席位和积分管理。W多人编辑待验。
订单发票与处理状态查看、开具或重发订单发票。W退款订单关闭入口。
邀请奖励与邀请记录查看邀请条件、奖励和历史。W资格和期限以账号为准。
反馈中心持续对话补充问题并查看官方回复。W不自动修复项目。
外部接入与 App 5 项
能力说明证据限制
CLI 创建继续与检查项目外部 Agent 可经 CLI 登录,创建或继续项目,读状态、上传素材、使用 Skill 并导出。W仅阅读指南,未安装或授权;CLI 不是 Web 会话协议证据。
CLI 分阶段执行与费用阻塞处理CLI 指南支持阶段确认、复用已批准结果,以及积分不足时暂停和读取状态后继续处理。W监控中断后读状态不等于已证明模型任务自动续跑或跨进程恰好执行一次。
官方公布 MCP 授权接入查建项目、上传、运行、恢复、导出。W仅 W 公告;工具 schema 和 Web 协议未知。
浏览器或 API Key 授权外部 Agent 接入支持两种授权。W未取得授权范围和撤销合同。
CLI 引用 Skill 分享码外部 Agent 可引用 Skill 分享码。W未运行。

尚不清楚的部分

竞品技术栈观察

当前能支持公开页面壳、CLI 项目接入的官方说明及 flova.tv 的入口 CNAME。没有当前生产脚本或编译元数据,无法确认 Nginx、前后端语言/框架/版本、源站区域及仓库布局。

项目观察到的内容状态置信度
Nginx 与版本是否使用 Nginx、所在层及版本均未确认。unknownlow
浏览器运行语言公开 Web 壳可读取;业务 JavaScript/Wasm 的具体运行实现未取得。unknownlow
前端源码语言Web 源码语言及 CLI 实现语言均未确认。unknownlow
前端框架与版本React、Next.js 等框架及版本均未确认。unknownlow
构建与 SSRSSR、构建器和 hydration 方案未确认。unknownlow
后端语言Web 后端语言未确认;CLI 合同不代表 Web 后端实现。unknownlow
后端框架与版本Web 后端框架、数据库驱动与版本均未确认。unknownlow
边缘入口与源站flova.tv CNAME 指向 kunlunaq 域;边缘供应商、区域及真实源站未确认。observedhigh
代码仓库与目录前后端同目录、仓库数量及 CLI 与 Web 是否共享仓库均未知。unknownlow
竞品服务调用图

当前官方 Web 过程支持项目、Agent、素材/分镜、Timeline 与导出;历史 v0.0.10 CLI 合同提供 Project/Run/Action/Resource/ExportTask 字段。图只表达逻辑职责与合同边界,当前 Web 包结构和部署拓扑未知。

节点层职责证据
Web创作界面client展示 Agent、Media Space/Canvas、Timeline 与 Docs。W
外部CLI接入client历史 CLI 命令客户端,操作完整 Flova 项目。A
Web公开入口entryflova.tv 公共入口,有同日 kunlunaq CNAME 记录。O
项目上下文职责domain提供同一项目的规格、Skill、分镜、媒体、文档及 Timeline。W
Agent执行职责runtime历史 Run 合同区分 project_id 与 stream_chat_id,并返回执行结果。A
待决项职责domain历史 pending_actions 对应当前消息边界与选项。A
素材组织职责domainAsset 收纳媒体版本,Canvas/预览及用途绑定用于组织素材。W
资源状态职责domain历史资源合同提供状态、预览 URL、错误与不透明版本。A
分镜编辑职责domain维护叙事、镜头与素材用途关系。W
时间轴编辑职责domain编排最终顺序、时长和声音,人工可继续细调。W
历史恢复职责domain历史合同以消息定位 restore 或复制新 Project 的 branch。A
导出任务职责runtime历史 ExportTask 与 Run 分开;最终视频使用 export_url。A
项目状态保留data可能保存可重新读取的项目、历史与资源定位信息。I

增长与运营机制

文档/教程与创作建议内容降低上手门槛,CLI 把完整项目工作流带入外部 Agent。(官方正文;源元数据未知)

复刻状态

能力状态依据
令牌与通用层加载、新建项目与会话已验证页面上的「+ 新建项目」按钮能打开对话框
Agent 对话:自动选默认模型、发送收回复已验证真实 Maa,收到回复
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
画布、分镜、时间轴布局已加载沿用早期静态页
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8105 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
前端入口:agent-integration/apps-production-brand-v14/flova/candidate-8875-unified-v22.html
复刻的架构 七个品牌相同,展开查看
flowchart LR
  U[浏览器] -->|静态页| FE[品牌前端<br/>令牌 + 通用层 + 运行时脚本]
  FE -->|/v1/* Cookie + CSRF| BFF[BFF 业务服务 Go]
  BFF --> MY[(MySQL<br/>共享身份库 + 品牌库)]
  BFF -->|会话 / 消息 / 事件| MAA[Maa Agent]
  BFF -->|任务| WK[媒体 worker]
  WK -->|submit_video| GW[模型网关]
  GW --> MOD[视频模型]
  MAA --> GW
  BFF --> OBJ[(素材 / 导出存储)]
所有七个复刻共用同一套后端:24 张表的品牌库 + 15 张表的共享身份库,55 个公开接口(路径里用 ?profile=flova 区分品牌)。品牌差异在前端:令牌(颜色、圆角、字体)、入口与页面结构。
为什么这样设计七个竞品的后端结构在我们的实测里几乎一致,差别只在入口、模板和视觉。所以复刻把差异收敛到前端令牌,后端只维护一份;这也是最终设计里「共享内核 + 品牌配置」的来源(决策 D17)。

Flova · Agent 交互过程

竞品里观察到的

NOT_RUN_CREDIT_GATE 聊天区在发送前明确提示积分不足,并显示购买入口;0积分下未实际Send,不把手工Markdown文档当Agent计划。

编号操作结果
打开既有空测试工程的对话区暂无消息;积分不足,购买;消息为空时生成按钮禁用。

费用与准入

项值
planFree
visible_balance0
chat_gate积分不足
chargedfalse
top_upfalse

对我们设计的启示

结论内容做法 / 原因
发送前即可显示准入门槛与明确原因,保留手写文档及当前工程,不把用户引导到反复重试。

竞品公开的调用流程

步骤调用关系操作证据
连接账号创作者 → 接入端从 Web 或外部 CLI 进入账号;CLI 官方指南使用浏览器账号授权。W
建立项目上下文接入端 → 项目上下文历史 CLI:project create --name --description 返回 project_id/project_url;已有项目按 ID 读取上下文。A
附上参考素材接入端 → 项目上下文历史 CLI 上传先获得文件对象;随后通过 run 的 --file-from 附给本次输入。A
启动创作Run接入端 → Agent执行历史 CLI:run PROJECT_ID --content;结果含 project_id、stream_chat_id、terminal、assistant_messages、pending_actions。A
报告进度与待决项Agent执行 → 接入端返回消息与 pending_actions;Action 字段有 action_id、resume_message_id、blocking、options[].id/effect。A
形成可用资源Agent执行 → 资源与编辑历史 Resource 含 resource_id、media_type、artifact_role、status、resource_url、error、state_version;官方 Web 把结果展示在相应面板。A
检查并细调工程创作者 → 资源与编辑检查 Media Space/Canvas 与分镜,手改 Timeline;需要时从消息恢复或在新 Project 分叉。W
提交并检查导出接入端 → 导出任务历史 CLI 导出/打包另有 task_id 与 terminal,视频交付含 export_url;官方 Web 导出当前 Timeline。A

在本品牌复刻上的测试结果

能力状态依据
Agent 对话:自动选默认模型、发送收回复已验证真实 Maa,收到回复
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8105 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
复刻里的 Agent 交互设计(时序图、状态机、规则) 七个品牌相同,展开查看
sequenceDiagram
  autonumber
  participant B as 浏览器
  participant S as BFF
  participant DB as MySQL
  participant M as Maa Agent
  participant W as 媒体 worker
  participant G as 模型网关
  B->>S: POST /projects, /conversations
  B->>S: POST maa/binding
  S->>DB: maa_bindings dispatching
  S->>M: 创建原生会话
  M-->>S: native_cid
  S->>DB: maa_bindings bound
  B->>S: POST maa/messages {client_action_id, content}
  S->>DB: maa_message_actions 幂等回执
  S-->>B: 202 + run_id
  S->>M: 提交消息
  S->>DB: maa_runs queued 到 running
  loop 事件游标
    B->>S: GET runs/{id}/events
    S-->>B: assistant.delta / assistant.message
  end
  M-->>S: run.completed
  S->>DB: 记录助手内容 SHA-256
  B->>S: POST media/requests/from-maa {source_run_id, sha256}
  S-->>B: 报价 + 有效期 + quote_sha256
  B->>S: POST approve {client_approval_id, request_sha256, quote_sha256}
  S->>DB: 冻结积分 + 建任务
  W->>G: submit_video
  G-->>W: 视频
  W->>DB: 产出入库 + 扣费
  B->>S: GET media/jobs/{id}

Run 状态机

stateDiagram-v2
  [*] --> queued: POST maa/messages 202
  queued --> running
  running --> waiting_interaction: 需要澄清卡
  waiting_interaction --> running: respondInteraction
  running --> completed
  running --> failed
  running --> cancelled: stopRun
  waiting_interaction --> cancelled
  completed --> [*]
  failed --> [*]
  cancelled --> [*]

媒体任务状态机

stateDiagram-v2
  [*] --> quoted
  quoted --> expired: 约 5 分钟
  quoted --> approved: approve
  approved --> held: 冻结积分
  held --> queued
  queued --> running: worker 领取
  running --> succeeded: 产出入库
  succeeded --> captured: 扣费确认
  running --> failed
  failed --> 待核对: 供应商已受理后失败,冻结保留,界面写「费用待核对」

交互规则

环节规则落库
绑定会话bindMaa 传 agent_model 或 use_default;先 dispatching,拿到原生会话号后才 bound(约束保证二者一致)maa_bindings
发消息client_action_id 幂等;返回 202 + run_id;同一个动作 ID 内容不同返回 409maa_message_actions, maa_runs
事件按 seq 游标读取;服务端先清洗路径、内部地址和令牌再公开maa_runs.last_event_seq
完成证据完成时记录助手内容的两份 SHA-256 与字节数maa_runs
澄清卡(HITL)Run 进入 waiting_interaction;respondInteraction 带 card_sha256;不能恢复两次maa_interactions, maa_interaction_actions
停止stopRun 幂等,回执只说「已观测到终态」;对话里保留「已停止回复」maa_run_stop_actions
切换模型必须带 expected_revision;有未决切换时拒绝发新消息maa_model_change_actions
生成视频from-maa → 报价 → 批准 → 冻结 → worker → 入库 → 扣费;批准带请求哈希与报价哈希studio_media_*, 积分账本
报价过期批准被拒,不建任务不扣费;界面提示重新报价studio_media_requests.expires_at
失败后的积分供应商已受理后失败:冻结保留,界面写「费用待核对」,不自动释放;本部署的账本里还没出现过释放分录(12 笔冻结、8 笔扣费、其余 4 笔仍冻结)studio_credit_ledger_entries
会话过期每 8 秒检查登录状态;失效时横幅提示,登录后恢复—
为什么这样设计把事件做成带序号的流、把动作做成带幂等键的回执,是因为长回复一定会遇到断线、刷新和重复点击。序号让续读不丢不重;幂等让重复点击安全;诚实的回执(只说「已观测到终态」)避免把「已停止」说成「不收费」。我们还在复刻里修掉了一个停止按钮从不发请求的缺陷,这说明每条交互路径都需要端到端测试(决策 D8)。

Flova · ER 图

品牌库 studio_profile_flova:24 张表、34 个真实外键、66 个检查约束、22 个触发器。图由实际表结构生成,箭头从父表指向子表。品牌库与共享身份库之间的 owner_id、asset_id 靠应用层归属检查,不画成外键。

共 61 条关系:一对多 37 条,一对一 10 条,一对多(父可选) 10 条,一对一(父可选) 4 条;多对多 2 组(经关联表)。

用户与登录 素材 积分账本 支付与订阅 模型目录 项目与会话 画布 · 时间轴 · 导出 Agent 交互 生成 · 媒体 · 交付

基数标注(线上的文字,箭头从父表指向子表):1:N 一对多,子表外键必填;0..1:N 一对多,但外键可空,所以父是可选的;1:1 一对一,子表外键上有唯一键;0..1:1 一对一且父可选。红色粗双向线 = N:M 多对多,经带 ⇄ 的关联表(它的主键就是两个外键)。实线 = 数据库真实外键;灰色虚线 = 跨库关系(两个库之间数据库无法建外键,由应用层检查);橙色粗框与粗线 = Agent 交互相关。每张表都带 owner_id(指向用户),为了图清楚没有逐条画线。

全景图:全部实体与关系

默认缩放到页面宽度,先看整体结构;点一下图切换到原始大小(看清表名,在框内上下左右滚动),再点一下缩回。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":26,"rankSpacing":80}}}%%
flowchart LR
  subgraph sg_i_user["用户与登录"]
    direction TB
    t_studio_users["users"]
    t_studio_sessions["sessions"]
  end
  subgraph sg_i_asset["素材"]
    direction TB
    t_studio_assets["assets"]
  end
  subgraph sg_i_credit["积分账本"]
    direction TB
    t_studio_credit_accounts["credit_accounts"]
    t_studio_credit_lots["credit_lots"]
    t_studio_credit_authorizations["credit_authorizations"]
    t_studio_credit_authorization_allocations["⇄ credit_authorization_allocations"]
    t_studio_credit_ledger_entries["credit_ledger_entries"]
    t_studio_credit_plan_versions["credit_plan_versions"]
    t_studio_billing_cycles["billing_cycles"]
  end
  subgraph sg_i_pay["支付与订阅"]
    direction TB
    t_studio_payment_orders["payment_orders"]
    t_studio_payment_events["payment_events"]
    t_studio_subscriptions["subscriptions"]
  end
  subgraph sg_i_model["模型目录"]
    direction TB
    t_studio_media_models["media_models"]
    t_studio_media_model_versions["media_model_versions"]
  end
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  subgraph sg_media["生成 · 媒体 · 交付"]
    direction TB
    t_studio_media_requests["media_requests"]
    t_studio_media_jobs["media_jobs"]
    t_studio_media_submission_attempts["media_submission_attempts"]
    t_studio_media_outputs["media_outputs"]
    t_generation_jobs["gen_jobs"]
    t_generation_reservations["gen_reservations"]
    t_generation_deliveries["gen_deliveries"]
    t_generation_development_budgets["gen_development_budgets"]
    t_studio_credit_recovery_intents["credit_recovery_intents"]
  end
  t_studio_users -->|"1:N"| t_studio_assets
  t_studio_payment_orders -->|"1:1"| t_studio_billing_cycles
  t_studio_subscriptions -->|"1:N"| t_studio_billing_cycles
  t_studio_users -->|"1:1"| t_studio_credit_accounts
  t_studio_credit_authorizations -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_lots -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_authorizations
  t_studio_credit_authorization_allocations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_authorizations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_lots -->|"1:N"| t_studio_credit_ledger_entries
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_lots
  t_studio_billing_cycles -->|"0..1:1"| t_studio_credit_lots
  t_studio_payment_orders -->|"0..1:1"| t_studio_credit_lots
  t_studio_media_models -->|"1:N"| t_studio_media_model_versions
  t_studio_media_model_versions -->|"0..1:1"| t_studio_media_models
  t_studio_payment_orders -->|"0..1:N"| t_studio_payment_events
  t_studio_subscriptions -->|"0..1:N"| t_studio_payment_events
  t_studio_users -->|"1:N"| t_studio_payment_events
  t_studio_credit_plan_versions -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_sessions
  t_studio_credit_plan_versions -->|"1:N"| t_studio_subscriptions
  t_studio_users -->|"1:N"| t_studio_subscriptions
  t_generation_jobs -->|"1:1"| t_generation_deliveries
  t_studio_projects -->|"1:N"| t_generation_deliveries
  t_studio_conversations -->|"1:N"| t_generation_deliveries
  t_generation_development_budgets -->|"1:N"| t_generation_jobs
  t_studio_projects -->|"1:N"| t_generation_jobs
  t_studio_conversations -->|"1:N"| t_generation_jobs
  t_generation_jobs -->|"1:1"| t_generation_reservations
  t_studio_conversations -->|"1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"0..1:1"| t_maa_bindings
  t_maa_interactions -->|"1:1"| t_maa_interaction_actions
  t_maa_runs -->|"1:N"| t_maa_interactions
  t_maa_bindings -->|"1:N"| t_maa_message_actions
  t_maa_runs -->|"0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"1:N"| t_maa_model_change_actions
  t_maa_runs -->|"1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"1:N"| t_maa_runs
  t_studio_canvases -->|"1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"1:1"| t_studio_canvases
  t_studio_projects -->|"1:N"| t_studio_conversations
  t_studio_media_jobs -->|"1:N"| t_studio_credit_recovery_intents
  t_studio_projects -->|"1:N"| t_studio_export_jobs
  t_studio_media_requests -->|"1:1"| t_studio_media_jobs
  t_studio_media_jobs -->|"1:N"| t_studio_media_outputs
  t_studio_conversations -->|"1:N"| t_studio_media_requests
  t_maa_runs -->|"0..1:N"| t_studio_media_requests
  t_studio_media_jobs -->|"1:N"| t_studio_media_submission_attempts
  t_studio_projects -->|"1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"1:1"| t_studio_video_documents
  t_studio_assets -.->|"1:N"| t_studio_project_asset_refs
  t_studio_assets -.->|"1:N"| t_studio_media_outputs
  t_studio_assets -.->|"1:N"| t_generation_deliveries
  t_studio_media_model_versions -.->|"1:N"| t_studio_media_requests
  t_studio_media_models -.->|"1:N"| t_studio_media_requests
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_media_jobs
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_credit_recovery_intents
  t_studio_credit_authorizations <-.->|"N:M · 经 credit_authorization_allocations"| t_studio_credit_lots
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_users,t_studio_sessions user
class t_studio_assets asset
class t_studio_credit_accounts,t_studio_credit_lots,t_studio_credit_authorizations,t_studio_credit_authorization_allocations,t_studio_credit_ledger_entries,t_studio_credit_plan_versions,t_studio_billing_cycles credit
class t_studio_payment_orders,t_studio_payment_events,t_studio_subscriptions pay
class t_studio_media_models,t_studio_media_model_versions model
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
class t_studio_media_requests,t_studio_media_jobs,t_studio_media_submission_attempts,t_studio_media_outputs,t_generation_jobs,t_generation_reservations,t_generation_deliveries,t_generation_development_budgets,t_studio_credit_recovery_intents media
class t_studio_credit_authorization_allocations,t_studio_project_asset_refs junction
  style sg_i_user fill:none,stroke:#6b7280,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_asset fill:none,stroke:#06b6d4,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_credit fill:none,stroke:#eab308,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_pay fill:none,stroke:#ec4899,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_model fill:none,stroke:#6366f1,stroke-width:1px,stroke-dasharray:5 4
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  style sg_media fill:none,stroke:#22c55e,stroke-width:1px
  linkStyle 30,31,32,33,34,35,36,37,38,48 stroke:#f97316,stroke-width:2.5px
  linkStyle 54,55,56,57,58,59,60 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 61,62 stroke:#dc2626,stroke-width:3px

多对多(N:M)

数据库里没有“多对多”这种列,它总是靠一张关联表表达。下面这几张表的主键由两个外键组成,所以它们两端是多对多:

多对多的两端关联表关联表的主键 / 唯一键
studio_credit_authorizations ⇄ studio_credit_lotsstudio_credit_authorization_allocationsowner_id, authorization_id, lot_id
studio_projects ⇄ studio_assetsstudio_project_asset_refsproject_id, asset_id

Agent 交互相关(圈出来)

绑定 → Run → 消息 / 澄清卡 / 切换模型 / 停止,每个动作一张回执表;线上是“外键列 · 基数”。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  t_studio_conversations -->|"conversation_id · 1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"pending_model_change_id · 0..1:1"| t_maa_bindings
  t_maa_interactions -->|"interaction_id · 1:1"| t_maa_interaction_actions
  t_maa_runs -->|"public_run_id · 1:N"| t_maa_interactions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_message_actions
  t_maa_runs -->|"public_run_id · 0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_model_change_actions
  t_maa_runs -->|"public_run_id · 1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_runs
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  linkStyle 0,1,2,3,4,5,6,7,8 stroke:#f97316,stroke-width:2.5px

画布与时间轴

项目下是一份视频文档,画布、画布版本、时间线版本和导出都挂在这份文档上;素材放在共享身份库,项目只记引用,项目与素材是多对多,经 project_asset_refs。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_asset["素材(共享身份库)"]
    direction TB
    t_studio_assets["assets"]
  end
  t_studio_canvases -->|"project_id · 1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"restored_from_revision · 0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_canvases
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
  t_studio_projects -->|"project_id · 1:N"| t_studio_export_jobs
  t_studio_projects -->|"project_id · 1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"project_id · 1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"restored_from_revision · 0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_video_documents
  t_studio_assets -.->|"asset_id · 1:N"| t_studio_project_asset_refs
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_studio_assets asset
class t_studio_project_asset_refs junction
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px
  linkStyle 9 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 10 stroke:#dc2626,stroke-width:3px
关系清单 逐条 61 条,含基数
父表子表外键列(子表里)基数约束域
generation_jobsgeneration_deliveriesjob_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_assetsgeneration_deliveriesasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_conversationsgeneration_deliveriesowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_deliveriesowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_development_budgetsgeneration_jobsowner_id, budget_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_conversationsgeneration_jobsowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_jobsgeneration_reservationsjob_id, budget_id, owner_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_model_change_actionsmaa_bindingsowner_id, project_id, conversation_id, id, pending_model_change_id0..1:1 一对一(父可选)DB 外键Agent 交互 → Agent 交互
studio_conversationsmaa_bindingsowner_id, project_id, conversation_id1:1 一对一DB 外键项目与会话 → Agent 交互
maa_interactionsmaa_interaction_actionsowner_id, project_id, conversation_id, binding_id, public_run_id, interaction_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_runsmaa_interactionsowner_id, project_id, conversation_id, binding_id, public_run_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_message_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_message_actionsowner_id, project_id, conversation_id, binding_id, public_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_model_change_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_run_stop_actionsowner_id, project_id, conversation_id, binding_id, public_run_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_runsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
studio_usersstudio_assetsowner_id1:N 一对多DB 外键用户与登录 → 素材
studio_payment_ordersstudio_billing_cyclesowner_id, payment_order_id, plan_version_id1:1 一对一DB 外键支付与订阅 → 积分账本
studio_subscriptionsstudio_billing_cyclesowner_id, subscription_id, provider_key, provider_account1:N 一对多DB 外键支付与订阅 → 积分账本
studio_canvas_revisionsstudio_canvas_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_canvasesstudio_canvas_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_canvasesowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_projectsstudio_conversationsowner_id, project_id1:N 一对多DB 外键项目与会话 → 项目与会话
studio_usersstudio_credit_accountsowner_id1:1 一对一DB 外键用户与登录 → 积分账本
studio_credit_authorizationsstudio_credit_authorization_allocationsowner_id, authorization_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_authorization_allocationsowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_authorizationsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_authorization_allocationsstudio_credit_ledger_entriesowner_id, authorization_id, lot_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_authorizationsstudio_credit_ledger_entriesowner_id, authorization_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_ledger_entriesowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_billing_cyclesstudio_credit_lotsowner_id, source_cycle_id0..1:1 一对一(父可选)DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_lotsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_payment_ordersstudio_credit_lotsowner_id, source_order_id0..1:1 一对一(父可选)DB 外键支付与订阅 → 积分账本
studio_credit_authorizationsstudio_credit_recovery_intentsauthorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_jobsstudio_credit_recovery_intentsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_projectsstudio_export_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_credit_authorizationsstudio_media_jobscredit_authorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_requestsstudio_media_jobsowner_id, project_id, conversation_id, request_id, request_sha256, quote_sha2561:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_model_versionsmodel_id1:N 一对多DB 外键模型目录 → 模型目录
studio_media_model_versionsstudio_media_modelsid, published_version_id0..1:1 一对一(父可选)DB 外键模型目录 → 模型目录
studio_assetsstudio_media_outputsasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_outputsowner_id, project_id, conversation_id, job_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_runsstudio_media_requestsowner_id, project_id, conversation_id, source_binding_id, source_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → 生成 · 媒体 · 交付
studio_conversationsstudio_media_requestsowner_id, project_id, conversation_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_media_model_versionsstudio_media_requestsmodel_version_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_requestsmodel_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_submission_attemptsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_payment_ordersstudio_payment_eventsowner_id, order_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_subscriptionsstudio_payment_eventsowner_id, subscription_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_usersstudio_payment_eventsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_credit_plan_versionsstudio_payment_ordersplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_payment_ordersowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_assetsstudio_project_asset_refsasset_id1:N 一对多跨库(应用层检查)素材 → 画布 · 时间轴 · 导出
studio_projectsstudio_project_asset_refsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_usersstudio_sessionsuser_id1:N 一对多DB 外键用户与登录 → 用户与登录
studio_credit_plan_versionsstudio_subscriptionsplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_subscriptionsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_timeline_revisionsstudio_timeline_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_video_documentsstudio_timeline_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_video_documentsowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
完整 ER:39 张表的全部列,带主键 / 外键与基数符号(鸦脚)
erDiagram
  studio_assets {
    CHAR id PK
    CHAR owner_id FK
    CHAR sha256
    JSON metadata_json
    VARCHAR storage_key
    DATETIME created_at
    VARCHAR publication_state
  }
  studio_billing_cycles {
    CHAR id PK
    CHAR owner_id FK
    CHAR subscription_id FK
    CHAR plan_version_id FK
    CHAR payment_order_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_invoice_id
    DATETIME period_start
    DATETIME period_end
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_accounts {
    CHAR owner_id PK
    VARCHAR unit
    VARCHAR status
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_credit_authorization_allocations {
    CHAR owner_id FK
    CHAR authorization_id PK
    CHAR lot_id PK
    BIGINT_UNSIGNED allocated_credits
    DATETIME created_at
  }
  studio_credit_authorizations {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR profile
    CHAR project_id
    CHAR conversation_id
    CHAR job_id
    CHAR request_id
    CHAR request_sha256
    CHAR quote_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    MEDIUMBLOB reservation_bytes
    CHAR reservation_sha256
    MEDIUMBLOB policy_bytes
    CHAR policy_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_ledger_entries {
    CHAR id PK
    CHAR owner_id FK
    CHAR lot_id FK
    CHAR authorization_id FK
    VARCHAR operation_key
    VARCHAR kind
    BIGINT grant_delta
    BIGINT held_delta
    BIGINT captured_delta
    MEDIUMBLOB facts_bytes
    CHAR facts_sha256
    DATETIME created_at
  }
  studio_credit_lots {
    CHAR id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED grant_credits
    BIGINT_UNSIGNED held_credits
    BIGINT_UNSIGNED captured_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    CHAR source_order_id FK
    CHAR source_cycle_id FK
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_plan_versions {
    CHAR id PK
    VARCHAR plan_key
    VARCHAR version_key
    VARCHAR kind
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    INT_UNSIGNED credit_validity_seconds
    INT_UNSIGNED billing_interval_months
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    DATETIME published_at
    DATETIME retired_at
  }
  studio_media_model_versions {
    CHAR id PK
    CHAR model_id FK
    VARCHAR version_key
    MEDIUMBLOB config_bytes
    CHAR config_sha256
    DATETIME published_at
  }
  studio_media_models {
    CHAR id PK
    VARCHAR model_key
    VARCHAR kind
    VARCHAR display_name
    VARCHAR status
    CHAR published_version_id FK
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_payment_events {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_event_id
    CHAR order_id FK
    CHAR subscription_id FK
    MEDIUMBLOB body_bytes
    CHAR body_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_payment_orders {
    CHAR id PK
    CHAR owner_id FK
    CHAR client_action_id
    CHAR plan_version_id FK
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_idempotency_key
    VARCHAR provider_order_id
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_sessions {
    CHAR token_hash PK
    CHAR user_id FK
    DATETIME created_at
    DATETIME expires_at
    DATETIME revoked_at
  }
  studio_subscriptions {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_subscription_id
    CHAR plan_version_id FK
    VARCHAR state
    DATETIME period_start
    DATETIME period_end
    TINYINT cancel_at_period_end
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_users {
    CHAR id PK
    VARCHAR email
    VARBINARY password_hash
    TINYINT enabled
    DATETIME created_at
  }
  generation_deliveries {
    CHAR job_id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR asset_id FK
    CHAR request_sha256
    CHAR maa_source_sha256
    CHAR provider_source_sha256
    VARCHAR model
    VARCHAR tool_name
    CHAR input_sha256
    JSON metadata_json
    DATETIME created_at
  }
  generation_development_budgets {
    CHAR id PK
    CHAR owner_id
    VARCHAR policy_version
    BIGINT_UNSIGNED credits_limit
    BIGINT_UNSIGNED credits_reserved
    INT_UNSIGNED calls_limit
    INT_UNSIGNED calls_reserved
    DATETIME expires_at
  }
  generation_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR native_conversation_id
    CHAR native_run_id
    CHAR source_sha256
    CHAR budget_id FK
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    VARCHAR status
    INT_UNSIGNED attempts
    MEDIUMBLOB result_bytes
    DATETIME updated_at
  }
  generation_reservations {
    CHAR job_id PK
    CHAR budget_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED credits
    INT_UNSIGNED calls
    VARCHAR status
  }
  maa_bindings {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR provider
    VARCHAR model_name
    VARCHAR status
    VARCHAR native_cid
    DATETIME created_at
    BIGINT_UNSIGNED model_revision
    CHAR pending_model_change_id FK
  }
  maa_interaction_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR interaction_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR client_action_id
    CHAR card_sha256
    CHAR request_sha256
    CHAR content_sha256
    VARCHAR response_action
    JSON values_payload
    VARCHAR status
    DATETIME created_at
  }
  maa_interactions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR card_sha256
    JSON card_payload
    DATETIME created_at
  }
  maa_message_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_action_id
    CHAR request_sha256
    MEDIUMTEXT content
    VARCHAR status
    CHAR public_run_id FK
    CHAR proposed_run_id
    DATETIME created_at
  }
  maa_model_change_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    CHAR client_action_id
    CHAR request_sha256
    CHAR native_request_sha256
    BIGINT_UNSIGNED expected_revision
    VARCHAR from_provider
    VARCHAR from_model_name
    VARCHAR target_provider
    VARCHAR target_model_name
    VARCHAR status
    BIGINT_UNSIGNED dispatch_attempt
    JSON receipt_payload
    DATETIME created_at
    DATETIME updated_at
  }
  maa_run_stop_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    VARCHAR native_rid
    CHAR client_action_id
    CHAR request_sha256
    VARCHAR stop_action
    VARCHAR status
    BIGINT_UNSIGNED attempt
    VARCHAR terminal_run_status
    DATETIME created_at
    DATETIME updated_at
  }
  maa_runs {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_rid
    VARCHAR status
    DATETIME created_at
    CHAR native_assistant_sha256
    CHAR public_assistant_sha256
    BIGINT_UNSIGNED native_assistant_bytes
    BIGINT_UNSIGNED assistant_event_seq
    BIGINT_UNSIGNED last_event_seq
  }
  studio_canvas_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_canvases {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_conversations {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    VARCHAR title
    DATETIME created_at
  }
  studio_credit_recovery_intents {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256
    VARCHAR kind
    CHAR authorization_id FK
    MEDIUMBLOB intent_bytes
    CHAR intent_sha256
    VARCHAR state
    CHAR lease_token_sha256
    DATETIME lease_until
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_export_jobs {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED document_revision
    CHAR input_hash
    VARCHAR status
    JSON input_json
    JSON output_json
    VARCHAR error_code
    CHAR lease_token_hash
    DATETIME lease_until
    INT_UNSIGNED attempts
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR request_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256 FK
    CHAR client_approval_id
    CHAR approval_sha256
    CHAR credit_authorization_id FK
    VARCHAR authorization_mode
    DATETIME authorization_expires_at
    VARCHAR status
    BIGINT_UNSIGNED revision
    CHAR lease_token_sha256
    DATETIME lease_until
    VARCHAR provider_task_id
    CHAR client_cancel_id
    CHAR cancel_request_sha256
    DATETIME cancel_requested_at
    CHAR staged_asset_id
    CHAR staged_asset_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_outputs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED ordinal
    CHAR asset_id FK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    CHAR provider_result_sha256
    DATETIME created_at
  }
  studio_media_requests {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_request_id
    CHAR model_id FK
    CHAR model_version_id FK
    CHAR model_config_sha256
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    MEDIUMBLOB quote_bytes
    CHAR quote_sha256
    BIGINT_UNSIGNED quoted_credits
    DATETIME expires_at
    VARCHAR origin
    CHAR source_binding_id FK
    CHAR source_run_id FK
    CHAR source_assistant_sha256
    MEDIUMBLOB source_snapshot_bytes
    DATETIME created_at
  }
  studio_media_submission_attempts {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED attempt_no
    CHAR request_sha256 FK
    VARCHAR provider_idempotency_key
    VARCHAR stage
    DATETIME dispatch_started_at
    VARCHAR provider_task_id
    CHAR receipt_sha256
    CHAR terminal_receipt_sha256
    CHAR reconciliation_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_project_asset_refs {
    CHAR project_id PK
    CHAR owner_id FK
    CHAR asset_id PK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
    DATETIME detached_at
  }
  studio_projects {
    CHAR id PK
    CHAR owner_id
    VARCHAR title
    DATETIME created_at
  }
  studio_timeline_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_video_documents {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_users ||--o{ studio_assets : "owner_id"
  studio_payment_orders ||--o| studio_billing_cycles : "plan_version_id"
  studio_subscriptions ||--o{ studio_billing_cycles : "provider_account"
  studio_users ||--o| studio_credit_accounts : "owner_id"
  studio_credit_authorizations ||--o{ studio_credit_authorization_allocations : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_authorization_allocations : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_authorizations : "owner_id"
  studio_credit_authorization_allocations |o--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_authorizations |o--o{ studio_credit_ledger_entries : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_lots : "owner_id"
  studio_billing_cycles |o--o| studio_credit_lots : "source_cycle_id"
  studio_payment_orders |o--o| studio_credit_lots : "source_order_id"
  studio_media_models ||--o{ studio_media_model_versions : "model_id"
  studio_media_model_versions |o--o| studio_media_models : "published_version_id"
  studio_payment_orders |o--o{ studio_payment_events : "provider_account"
  studio_subscriptions |o--o{ studio_payment_events : "provider_account"
  studio_users ||--o{ studio_payment_events : "owner_id"
  studio_credit_plan_versions ||--o{ studio_payment_orders : "plan_version_id"
  studio_users ||--o{ studio_payment_orders : "owner_id"
  studio_users ||--o{ studio_sessions : "user_id"
  studio_credit_plan_versions ||--o{ studio_subscriptions : "plan_version_id"
  studio_users ||--o{ studio_subscriptions : "owner_id"
  generation_jobs ||--o| generation_deliveries : "job_id"
  studio_projects ||--o{ generation_deliveries : "project_id"
  studio_conversations ||--o{ generation_deliveries : "session_id"
  generation_development_budgets ||--o{ generation_jobs : "budget_id"
  studio_projects ||--o{ generation_jobs : "project_id"
  studio_conversations ||--o{ generation_jobs : "session_id"
  generation_jobs ||--o| generation_reservations : "budget_id"
  studio_conversations ||--o| maa_bindings : "conversation_id"
  maa_model_change_actions |o--o| maa_bindings : "pending_model_change_id"
  maa_interactions ||--o| maa_interaction_actions : "interaction_id"
  maa_runs ||--o{ maa_interactions : "public_run_id"
  maa_bindings ||--o{ maa_message_actions : "binding_id"
  maa_runs |o--o{ maa_message_actions : "public_run_id"
  maa_bindings ||--o{ maa_model_change_actions : "binding_id"
  maa_runs ||--o| maa_run_stop_actions : "public_run_id"
  maa_bindings ||--o{ maa_runs : "binding_id"
  studio_canvases ||--o{ studio_canvas_revisions : "project_id"
  studio_canvas_revisions |o--o{ studio_canvas_revisions : "restored_from_revision"
  studio_projects ||--o| studio_canvases : "project_id"
  studio_projects ||--o{ studio_conversations : "project_id"
  studio_media_jobs ||--o{ studio_credit_recovery_intents : "job_id"
  studio_projects ||--o{ studio_export_jobs : "project_id"
  studio_media_requests ||--o| studio_media_jobs : "request_id"
  studio_media_jobs ||--o{ studio_media_outputs : "job_id"
  studio_conversations ||--o{ studio_media_requests : "conversation_id"
  maa_runs |o--o{ studio_media_requests : "source_run_id"
  studio_media_jobs ||--o{ studio_media_submission_attempts : "job_id"
  studio_projects ||--o{ studio_project_asset_refs : "project_id"
  studio_video_documents ||--o{ studio_timeline_revisions : "project_id"
  studio_timeline_revisions |o--o{ studio_timeline_revisions : "restored_from_revision"
  studio_projects ||--o| studio_video_documents : "project_id"
  studio_assets ||--o{ studio_project_asset_refs : "asset_id"
  studio_assets ||--o{ studio_media_outputs : "asset_id"
  studio_assets ||--o{ generation_deliveries : "asset_id"
  studio_media_model_versions ||--o{ studio_media_requests : "model_version_id"
  studio_media_models ||--o{ studio_media_requests : "model_id"
  studio_credit_authorizations |o--o{ studio_media_jobs : "credit_authorization_id"
  studio_credit_authorizations |o--o{ studio_credit_recovery_intents : "authorization_id"
自动生成的整体图(按真实表结构,备查)
Flova 品牌库 ER 图

共享身份库 ER 图

共享身份库 ER 图
实测验证与设计原因 七个品牌相同,展开查看

实测验证(本机 MySQL 实库)

对照本机真实运行的数据库 studio_profile_flova 和 studio_platform_identity,逐项核对本页的表、列、关系与说明。18 / 18 项通过。

查看全部核对项
核对项结果
表数 = 发布值 24通过
列数 = 289通过
外键数 = 34通过
检查约束数 = 66通过
触发器数 = 22通过
索引数(含主键) = 85通过
列定义与 pollo 逐列一致通过
外键与 pollo 完全一致通过
索引与 pollo 一致通过
触发器与 pollo 一致通过
外键 CSV 行数 = 实库外键数通过
mmd 实体 = 实库表通过
svg 标题 24 tables / 34 actual foreign keys = 实库通过
参考 SQL 表集合与列顺序 = 实库通过
15 张表通过
外键 CSV 行数 = 实库外键数通过
svg 标题与实库一致通过
参考 SQL 表集合与列顺序 = 实库通过

实库里的关系是这样的

  • 34 个外键里 31 个是复合外键,且都带着 owner_id,其余 3 个是单列外键;22 张子表都带 owner 外键。所以一行数据不可能指向别的用户的项目或会话。
  • 删除与更新规则全部是 RESTRICT / NO ACTION,没有任何级联:被引用的行不能被删除。
  • 品牌库与共享身份库之间的关系(owner_id、asset_id、model_id + model_version_id)没有数据库外键,靠应用层检查;我们逐表核对了现有数据,没有孤儿。

主动破坏测试(在事务里尝试,全部回滚)

尝试数据库的反应
外键:导出任务.project_id 改成不存在的项目被拒绝拒绝
复合外键:项目素材引用.project_id 改成别的用户的项目被拒绝(owner 与 project 必须成对)拒绝
触发器:已提交的消息动作不可改写拒绝
触发器:媒体产出不可改写拒绝
外键 RESTRICT:被会话引用的项目不能删除拒绝
复合外键:被引用的会话不能把 owner 改成别人拒绝
检查约束:Run 状态只能是约定的六个值拒绝
触发器:账本分录不可修改拒绝
触发器:账本分录不可删除拒绝
验证范围的说明:这几个库里的数据量很小(每个品牌库约 70–270 行),孤儿检查能证明现有数据没有违反关系,不能证明大数据量下的表现;主动破坏测试在 Pollo 库上执行,另外六个品牌库的列、外键、索引、触发器已逐项核对与 Pollo 完全一致。
为什么这样设计外键用真实数据库约束表达,复合外键把 owner/project/conversation 一起带上,所以任何一行数据都不可能指向别人的项目。跨库关系不画外键而用应用层检查,是因为不同库之间数据库本身保证不了,画出来反而误导(决策 D12、D17)。

Flova · 表结构

MySQL 8,字符集 utf8mb4,ID 为 char(36) ascii_bin。点击展开 DDL。

按职责分组

项目与会话 2 张
  • studio_projects:用户拥有的创作项目
  • studio_conversations:项目内的对话会话
Agent执行与并发控制 7 张
  • maa_bindings:业务会话与Native Maa会话映射、模型版本与未决操作
  • maa_runs:公开Run映射、状态与原生/公开助手证据摘要
  • maa_message_actions:原消息动作及幂等回执
  • maa_interactions:澄清卡片及Run归属
  • maa_interaction_actions:原卡片回答/取消动作
  • maa_model_change_actions:带预期模型版本的切换动作
  • maa_run_stop_actions:停止原Run的幂等动作与观测终态
画布、时间轴与素材引用 5 张
  • studio_canvases:项目当前画布文档
  • studio_canvas_revisions:历史画布修订
  • studio_video_documents:项目当前时间轴文档
  • studio_timeline_revisions:历史时间轴修订
  • studio_project_asset_refs:项目对用户级素材的引用;不复制素材所有权
媒体生成、结算恢复与导出 6 张
  • studio_media_requests:用户输入/Agent来源、报价、批准摘要与准入确认
  • studio_media_jobs:已批准媒体任务及状态机
  • studio_media_submission_attempts:一次提交的任务证据与不确定结果
  • studio_media_outputs:受验证的输出与素材映射
  • studio_credit_recovery_intents:积分结算恢复意图;不是用户钱包全账本
  • studio_export_jobs:时间轴导出队列和发布输出
既有generation兼容路径 22 张
  • generation_jobs:既有generation任务,不等同新PublicMedia任务
  • generation_reservations:既有任务费用预留
  • generation_development_budgets:既有开发测试预算
  • generation_deliveries:既有生成交付回执
  • owner_id:归属用户;不是允许跨用户查询的参数。
  • model_revision:并发准入版本;数据库默认0不能代替Native权威读回。
  • pending_model_change_id:未决模型切换引用,阻止其他writer绕过。
  • studio_users:用户账号记录;不导出密码哈希或任何业务行
  • studio_sessions:登录会话;这里只公开字段结构,不公开会话值
  • studio_assets:用户级素材元数据与存储键结构,项目仅引用素材ID
  • studio_media_models:公开媒体模型目录结构
  • studio_media_model_versions:媒体模型不可变版本/计价能力结构
  • studio_subscriptions:订阅状态持久化
  • studio_billing_cycles:订阅账期
  • studio_payment_orders:订单记录
  • studio_payment_events:支付事件幂等记录;不代表已有公开支付路由
  • studio_credit_accounts:用户积分账户
  • studio_credit_lots:积分批次
  • studio_credit_authorizations:费用预留与结算授权
  • studio_credit_authorization_allocations:授权对批次的分配
  • studio_credit_ledger_entries:积分流水
  • studio_credit_plan_versions:积分方案不可变版本

品牌库 studio_profile_flova(24 张)

generation_deliveries 既有生成交付回执
CREATE TABLE `generation_deliveries` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `maa_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `tool_name` varchar(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_delivery_owner_project` (`owner_id`,`project_id`,`session_id`,`job_id`),
  KEY `generation_delivery_owned_asset` (`owner_id`,`asset_id`),
  KEY `generation_delivery_session_fk` (`session_id`),
  CONSTRAINT `generation_delivery_job_fk` FOREIGN KEY (`job_id`) REFERENCES `generation_jobs` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`, `job_id`) REFERENCES `generation_jobs` (`owner_id`, `project_id`, `session_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_session_fk` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_model_tool` CHECK ((((`model` = _ascii'seedance-2.0') and (`tool_name` = _ascii'seedance2_video_generator')) or ((`model` in (_ascii'minimax-h3',_ascii'seedance-2.5')) and (`tool_name` = _ascii'insai_video_generate'))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_development_budgets 既有开发测试预算
CREATE TABLE `generation_development_budgets` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_version` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits_limit` bigint unsigned NOT NULL,
  `credits_reserved` bigint unsigned NOT NULL DEFAULT '0',
  `calls_limit` int unsigned NOT NULL,
  `calls_reserved` int unsigned NOT NULL DEFAULT '0',
  `expires_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_budget_owner` (`owner_id`,`id`),
  CONSTRAINT `generation_budget_limits` CHECK (((`credits_limit` between 1 and 1000000000) and (`credits_reserved` <= `credits_limit`) and (`calls_limit` between 1 and 100) and (`calls_reserved` <= `calls_limit`))),
  CONSTRAINT `generation_budget_policy` CHECK ((`policy_version` = _latin1'operator-test/v1'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_jobs 既有generation任务,不等同新PublicMedia任务
CREATE TABLE `generation_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_conversation_id` char(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_run_id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `result_bytes` mediumblob,
  `updated_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_job_budget_owner` (`id`,`budget_id`,`owner_id`),
  UNIQUE KEY `generation_job_owned_scope` (`owner_id`,`project_id`,`session_id`,`id`),
  KEY `generation_job_owner` (`owner_id`,`project_id`,`session_id`),
  KEY `generation_job_session` (`session_id`),
  KEY `generation_job_budget` (`owner_id`,`budget_id`),
  CONSTRAINT `generation_job_budget` FOREIGN KEY (`owner_id`, `budget_id`) REFERENCES `generation_development_budgets` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_project` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_session` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_attempt` CHECK ((((`status` = _ascii'APPROVED') and (`attempts` = 0) and (`result_bytes` is null)) or ((`status` = _ascii'SUBMITTING') and (`attempts` = 1) and (`result_bytes` is null)) or ((`status` in (_ascii'SUCCEEDED',_ascii'FAILED',_ascii'UNKNOWN')) and (`attempts` = 1) and (`result_bytes` is not null)))),
  CONSTRAINT `generation_job_credit` CHECK ((`credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_reservations 既有任务费用预留
CREATE TABLE `generation_reservations` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `calls` int unsigned NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_reservation_job` (`job_id`,`budget_id`,`owner_id`),
  CONSTRAINT `generation_reservation_job` FOREIGN KEY (`job_id`, `budget_id`, `owner_id`) REFERENCES `generation_jobs` (`id`, `budget_id`, `owner_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_reservation_fixed` CHECK (((`credits` between 1 and 1000000000) and (`calls` = 1) and (`status` = _latin1'HELD')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_bindings 业务会话与Native Maa会话映射、模型版本与未决操作
CREATE TABLE `maa_bindings` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `model_revision` bigint unsigned NOT NULL DEFAULT '0',
  `pending_model_change_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_binding_business_scope` (`owner_id`,`project_id`,`conversation_id`),
  UNIQUE KEY `maa_binding_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `maa_binding_native_identity` (`native_cid`),
  KEY `maa_model_change_pending_fk` (`owner_id`,`project_id`,`conversation_id`,`id`,`pending_model_change_id`),
  CONSTRAINT `maa_binding_owned_conversation_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_pending_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `id`, `pending_model_change_id`) REFERENCES `maa_model_change_actions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_binding_receipt` CHECK ((((`status` = _ascii'dispatching') and (`native_cid` is null)) or ((`status` = _ascii'bound') and (`native_cid` is not null)))),
  CONSTRAINT `maa_model_binding_revision` CHECK ((`model_revision` <= 9007199254740991))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interaction_actions 原卡片回答/取消动作
CREATE TABLE `maa_interaction_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `interaction_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `response_action` varchar(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `values_payload` json NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_action_business_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_interaction_action_one_card` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`interaction_id`),
  CONSTRAINT `maa_interaction_action_owned_card_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `interaction_id`) REFERENCES `maa_interactions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_interaction_action_kind` CHECK ((`response_action` in (_latin1'submit',_latin1'cancel'))),
  CONSTRAINT `maa_interaction_action_status` CHECK ((`status` in (_latin1'dispatching',_latin1'accepted')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interactions 澄清卡片及Run归属
CREATE TABLE `maa_interactions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_payload` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_native_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`native_iid`),
  UNIQUE KEY `maa_interaction_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`id`),
  CONSTRAINT `maa_interaction_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_message_actions 原消息动作及幂等回执
CREATE TABLE `maa_message_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `proposed_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_action_business_identity` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  KEY `maa_action_owned_run_fk` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_action_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_receipt` CHECK ((((`status` = _latin1'dispatching') and (`public_run_id` is null)) or ((`status` = _latin1'accepted') and (`public_run_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_model_change_actions 带预期模型版本的切换动作
CREATE TABLE `maa_model_change_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `expected_revision` bigint unsigned NOT NULL,
  `from_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `from_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_attempt` bigint unsigned NOT NULL,
  `receipt_payload` json DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_model_change_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_model_change_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  CONSTRAINT `maa_model_change_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_attempt` CHECK ((`dispatch_attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_model_change_expected` CHECK ((`expected_revision` <= 9007199254740990)),
  CONSTRAINT `maa_model_change_status` CHECK ((((cast(`status` as char charset binary) = _latin1'dispatching') and (`receipt_payload` is null)) or ((cast(`status` as char charset binary) in (_latin1'accepted',_latin1'rejected')) and (`receipt_payload` is not null) and (json_type(`receipt_payload`) = _latin1'OBJECT')))),
  CONSTRAINT `maa_model_change_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_run_stop_actions 停止原Run的幂等动作与观测终态
CREATE TABLE `maa_run_stop_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stop_action` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt` bigint unsigned NOT NULL,
  `terminal_run_status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_stop_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_stop_one_run` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_stop_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_stop_action` CHECK ((`stop_action` = _latin1'interrupt')),
  CONSTRAINT `maa_stop_attempt` CHECK ((`attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_stop_state` CHECK ((((`status` = _latin1'unknown') and (`terminal_run_status` is null)) or ((`status` = _latin1'terminal_observed') and (`terminal_run_status` is not null) and (`terminal_run_status` in (_latin1'completed',_latin1'failed',_latin1'cancelled'))))),
  CONSTRAINT `maa_stop_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_runs 公开Run映射、状态与原生/公开助手证据摘要
CREATE TABLE `maa_runs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `native_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `public_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `native_assistant_bytes` bigint unsigned NOT NULL DEFAULT '0',
  `assistant_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  `last_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_run_native_identity` (`native_rid`),
  UNIQUE KEY `maa_run_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  KEY `maa_run_owned_list` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  CONSTRAINT `maa_run_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_run_source_evidence` CHECK ((((`native_assistant_sha256` is null) and (`public_assistant_sha256` is null) and (`native_assistant_bytes` = 0) and (`assistant_event_seq` = 0)) or ((`native_assistant_sha256` is not null) and (`public_assistant_sha256` is not null) and (`native_assistant_bytes` > 0) and (`assistant_event_seq` > 0) and (`assistant_event_seq` <= `last_event_seq`)))),
  CONSTRAINT `maa_run_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'waiting_interaction',_latin1'completed',_latin1'failed',_latin1'cancelled')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvas_revisions 历史画布修订
CREATE TABLE `studio_canvas_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_canvas_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_canvas_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_canvases` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_canvas_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_canvas_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_canvas_revision_kind` CHECK ((`kind` = _latin1'canvas')),
  CONSTRAINT `studio_canvas_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_canvas_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_canvas_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvases 项目当前画布文档
CREATE TABLE `studio_canvases` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_canvas_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_canvas_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_conversations 项目内的对话会话
CREATE TABLE `studio_conversations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_conversation_owned_scope` (`owner_id`,`project_id`,`id`),
  KEY `studio_conversation_list` (`owner_id`,`project_id`,`created_at`,`id`),
  CONSTRAINT `studio_conversation_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_recovery_intents 积分结算恢复意图;不是用户钱包全账本
CREATE TABLE `studio_credit_recovery_intents` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `intent_bytes` mediumblob NOT NULL,
  `intent_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_intent_job_kind` (`job_id`,`kind`),
  KEY `credit_intent_recovery` (`state`,`updated_at`,`id`),
  KEY `credit_intent_owned_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `credit_intent_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `credit_intent_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_intent_kind` CHECK (((`kind` in (_latin1'RESERVE_BIND',_latin1'CAPTURE',_latin1'RELEASE')) and (((`kind` = _latin1'RESERVE_BIND') and (`authorization_id` is null)) or ((`kind` in (_latin1'CAPTURE',_latin1'RELEASE')) and (`authorization_id` is not null))))),
  CONSTRAINT `credit_intent_lease` CHECK ((((`state` = _latin1'leased') and (`lease_token_sha256` is not null) and regexp_like(`lease_token_sha256`,_latin1'^[a-f0-9]{64}$') and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`state` <> _latin1'leased') and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `credit_intent_payload_sha` CHECK ((`intent_sha256` = lower(sha2(`intent_bytes`,256)))),
  CONSTRAINT `credit_intent_payload_size` CHECK ((length(`intent_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_intent_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_intent_state` CHECK ((`state` in (_latin1'pending',_latin1'leased',_latin1'confirmed',_latin1'unknown'))),
  CONSTRAINT `credit_intent_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_export_jobs 时间轴导出队列和发布输出
CREATE TABLE `studio_export_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `document_revision` bigint unsigned NOT NULL,
  `input_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_json` json NOT NULL,
  `output_json` json DEFAULT NULL,
  `error_code` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
  `lease_token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_export_input` (`project_id`,`document_revision`,`input_hash`),
  KEY `studio_export_claim` (`status`,`created_at`,`id`),
  KEY `studio_export_owner_list` (`owner_id`,`project_id`,`created_at`,`id`),
  KEY `studio_export_expiry` (`status`,`lease_until`),
  CONSTRAINT `studio_export_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_export_lease` CHECK ((((`status` = _latin1'running') and (`lease_token_hash` is not null) and (`lease_until` is not null)) or ((`status` <> _latin1'running') and (`lease_token_hash` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_export_output` CHECK ((((`status` = _latin1'succeeded') and (`output_json` is not null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'queued',_latin1'running')) and (`output_json` is null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'failed',_latin1'unknown')) and (`output_json` is null) and (`error_code` <> _latin1'')))),
  CONSTRAINT `studio_export_revision` CHECK ((`document_revision` between 1 and 2147483647)),
  CONSTRAINT `studio_export_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'succeeded',_latin1'failed',_latin1'unknown')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_jobs 已批准媒体任务及状态机
CREATE TABLE `studio_media_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_approval_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `approval_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credit_authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_mode` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_expires_at` datetime(6) DEFAULT NULL,
  `status` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `client_cancel_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_requested_at` datetime(6) DEFAULT NULL,
  `staged_asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `staged_asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_job_request` (`request_id`),
  UNIQUE KEY `studio_media_job_client` (`owner_id`,`client_approval_id`),
  UNIQUE KEY `studio_media_job_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_job_owned_request` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`),
  KEY `studio_media_job_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_job_queue` (`status`,`updated_at`,`id`),
  KEY `studio_media_job_request_fk` (`owner_id`,`project_id`,`conversation_id`,`request_id`,`request_sha256`,`quote_sha256`),
  CONSTRAINT `studio_media_job_request_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `request_id`, `request_sha256`, `quote_sha256`) REFERENCES `studio_media_requests` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`, `quote_sha256`),
  CONSTRAINT `studio_media_job_authorization` CHECK ((((`credit_authorization_id` is null) and (`authorization_mode` is null) and (`authorization_expires_at` is null) and (`status` in (_latin1'approval_pending',_latin1'cancelled'))) or ((`credit_authorization_id` is not null) and (`authorization_mode` is not null) and (`authorization_mode` in (_latin1'commercial',_latin1'test_allowance')) and (`authorization_expires_at` is not null) and (`authorization_expires_at` > `created_at`) and (`status` <> _latin1'approval_pending')))),
  CONSTRAINT `studio_media_job_cancel` CHECK ((((`client_cancel_id` is null) and (`cancel_request_sha256` is null) and (`cancel_requested_at` is null)) or ((`client_cancel_id` is not null) and (`cancel_request_sha256` is not null) and (`cancel_requested_at` is not null) and (`cancel_requested_at` between `created_at` and `updated_at`)))),
  CONSTRAINT `studio_media_job_lease` CHECK ((((`status` in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is not null) and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`status` not in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_media_job_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_job_staged` CHECK ((((`staged_asset_id` is null) and (`staged_asset_sha256` is null)) or ((`staged_asset_id` is not null) and (`staged_asset_sha256` is not null)))),
  CONSTRAINT `studio_media_job_status` CHECK ((`status` in (_latin1'approval_pending',_latin1'queued',_latin1'leased',_latin1'submitting',_latin1'running',_latin1'submission_unknown',_latin1'succeeded',_latin1'failed',_latin1'cancelled'))),
  CONSTRAINT `studio_media_job_task` CHECK (((`status` not in (_latin1'running',_latin1'succeeded')) or (`provider_task_id` is not null))),
  CONSTRAINT `studio_media_job_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_outputs 受验证的输出与素材映射
CREATE TABLE `studio_media_outputs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `ordinal` int unsigned NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_result_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_output_job` (`job_id`,`ordinal`),
  KEY `studio_media_output_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`),
  CONSTRAINT `studio_media_output_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`),
  CONSTRAINT `studio_media_output_ordinal` CHECK ((`ordinal` = 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_requests 用户输入/Agent来源、报价、批准摘要与准入确认
CREATE TABLE `studio_media_requests` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_bytes` mediumblob NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quoted_credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_snapshot_bytes` mediumblob,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_request_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_request_quoted` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`,`quote_sha256`),
  UNIQUE KEY `studio_media_request_client` (`owner_id`,`project_id`,`conversation_id`,`client_request_id`),
  KEY `studio_media_request_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_request_source_fk` (`owner_id`,`project_id`,`conversation_id`,`source_binding_id`,`source_run_id`),
  CONSTRAINT `studio_media_request_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`),
  CONSTRAINT `studio_media_request_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `source_binding_id`, `source_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`),
  CONSTRAINT `studio_media_quote_credits` CHECK ((`quoted_credits` between 1 and 1000000000)),
  CONSTRAINT `studio_media_quote_sha` CHECK ((`quote_sha256` = lower(sha2(`quote_bytes`,256)))),
  CONSTRAINT `studio_media_quote_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)))),
  CONSTRAINT `studio_media_request_origin` CHECK ((((`origin` in (_latin1'ui',_latin1'user_edit')) and (`source_binding_id` is null) and (`source_run_id` is null) and (`source_assistant_sha256` is null) and (`source_snapshot_bytes` is null)) or ((`origin` = _latin1'agent') and (`source_binding_id` is not null) and (`source_run_id` is not null) and (`source_assistant_sha256` is not null) and (`source_snapshot_bytes` is not null) and (length(`source_snapshot_bytes`) between 1 and 65536) and (`source_assistant_sha256` = lower(sha2(`source_snapshot_bytes`,256)))))),
  CONSTRAINT `studio_media_request_sha` CHECK ((`request_sha256` = lower(sha2(`request_bytes`,256)))),
  CONSTRAINT `studio_media_request_size` CHECK (((length(`request_bytes`) between 1 and 65536) and (length(`quote_bytes`) between 1 and 65536)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_submission_attempts 一次提交的任务证据与不确定结果
CREATE TABLE `studio_media_submission_attempts` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt_no` int unsigned NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stage` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_started_at` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `terminal_receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `reconciliation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_attempt_job` (`job_id`,`attempt_no`),
  UNIQUE KEY `studio_media_attempt_idempotency` (`provider_idempotency_key`),
  KEY `studio_media_attempt_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `studio_media_attempt_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `studio_media_attempt_number` CHECK ((`attempt_no` = 1)),
  CONSTRAINT `studio_media_attempt_state` CHECK ((((`stage` = _latin1'prepared') and (`dispatch_started_at` is null) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'dispatching',_latin1'unknown')) and (`dispatch_started_at` is not null) and (`dispatch_started_at` >= `created_at`) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` = _latin1'acknowledged') and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'completed',_latin1'failed')) and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is not null)) or ((`stage` = _latin1'reconciled_not_sent') and (`dispatch_started_at` is not null) and (`provider_task_id` is null) and (`reconciliation_sha256` is not null)))),
  CONSTRAINT `studio_media_attempt_times` CHECK (((`updated_at` >= `created_at`) and ((`dispatch_started_at` is null) or (`dispatch_started_at` between `created_at` and `updated_at`))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_project_asset_refs 项目对用户级素材的引用;不复制素材所有权
CREATE TABLE `studio_project_asset_refs` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `detached_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`project_id`,`asset_id`),
  KEY `studio_project_asset_owned_page` (`owner_id`,`project_id`,`asset_id`),
  KEY `studio_project_asset_reverse` (`owner_id`,`asset_id`,`project_id`),
  CONSTRAINT `studio_project_asset_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_project_asset_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_project_asset_state` CHECK ((((`state` = _latin1'attached') and (`detached_at` is null)) or ((`state` = _latin1'detached') and (`detached_at` is not null) and (`detached_at` = `updated_at`)))),
  CONSTRAINT `studio_project_asset_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_projects 用户拥有的创作项目
CREATE TABLE `studio_projects` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_project_owner_identity` (`owner_id`,`id`),
  KEY `studio_project_owner_list` (`owner_id`,`created_at`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_timeline_revisions 历史时间轴修订
CREATE TABLE `studio_timeline_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_timeline_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_timeline_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_video_documents` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_timeline_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_timeline_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_timeline_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_timeline_revision_kind` CHECK ((`kind` = _latin1'timeline')),
  CONSTRAINT `studio_timeline_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_timeline_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_timeline_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_video_documents 项目当前时间轴文档
CREATE TABLE `studio_video_documents` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_timeline_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_document_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_document_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
共享身份库(15 张)与积分账本 七个品牌相同,展开查看
studio_assets
CREATE TABLE `studio_assets` (
  `id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `storage_key` varchar(512) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `publication_state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT 'visible',
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_asset_owner_digest` (`owner_id`,`sha256`),
  KEY `studio_asset_owner_list` (`owner_id`,`created_at`,`id`),
  KEY `studio_asset_visible_list` (`owner_id`,`publication_state`,`created_at`,`id`),
  CONSTRAINT `studio_asset_publication_state` CHECK (CAST(publication_state AS BINARY) IN ('pending','visible')),
  CONSTRAINT `studio_asset_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_billing_cycles
CREATE TABLE `studio_billing_cycles` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `payment_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_invoice_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `billing_cycle_owned` (`owner_id`,`id`),
  UNIQUE KEY `billing_cycle_period` (`subscription_id`,`period_start`),
  UNIQUE KEY `billing_cycle_invoice` (`provider_key`,`provider_account`,`provider_invoice_id`),
  UNIQUE KEY `billing_cycle_order` (`payment_order_id`),
  KEY `billing_cycle_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  KEY `billing_cycle_order_fk` (`owner_id`,`payment_order_id`,`plan_version_id`),
  KEY `billing_cycle_order_provider_fk` (`owner_id`,`payment_order_id`,`provider_key`,`provider_account`),
  CONSTRAINT `billing_cycle_order_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `plan_version_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `plan_version_id`),
  CONSTRAINT `billing_cycle_order_provider_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_amount` CHECK ((`grant_credits` between 1 and 1000000000)),
  CONSTRAINT `billing_cycle_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `billing_cycle_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `billing_cycle_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `billing_cycle_state` CHECK (((`state` in (_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `billing_cycle_times` CHECK (((`period_end` > `period_start`) and (`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_accounts
CREATE TABLE `studio_credit_accounts` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `unit` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`),
  CONSTRAINT `credit_account_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `credit_account_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_account_status` CHECK ((`status` in (_latin1'open',_latin1'suspended'))),
  CONSTRAINT `credit_account_unit` CHECK ((`unit` = _latin1'credits'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorization_allocations
CREATE TABLE `studio_credit_authorization_allocations` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `allocated_credits` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`authorization_id`,`lot_id`),
  UNIQUE KEY `credit_allocation_owned` (`owner_id`,`authorization_id`,`lot_id`),
  KEY `credit_allocation_lot` (`owner_id`,`lot_id`,`authorization_id`),
  CONSTRAINT `credit_allocation_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_amount` CHECK ((`allocated_credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorizations
CREATE TABLE `studio_credit_authorizations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `profile` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `reservation_bytes` mediumblob NOT NULL,
  `reservation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_bytes` mediumblob NOT NULL,
  `policy_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_authorization_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_authorization_job` (`owner_id`,`profile`,`job_id`),
  CONSTRAINT `credit_authorization_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_authorization_amount` CHECK ((`credits` between 1 and 1000000000)),
  CONSTRAINT `credit_authorization_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_authorization_policy_sha` CHECK ((`policy_sha256` = lower(sha2(`policy_bytes`,256)))),
  CONSTRAINT `credit_authorization_policy_size` CHECK ((length(`policy_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_profile` CHECK ((`profile` in (_latin1'topview',_latin1'pollo',_latin1'invideo',_latin1'libtv',_latin1'flova',_latin1'creatok',_latin1'creatify'))),
  CONSTRAINT `credit_authorization_reservation_sha` CHECK ((`reservation_sha256` = lower(sha2(`reservation_bytes`,256)))),
  CONSTRAINT `credit_authorization_reservation_size` CHECK ((length(`reservation_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_authorization_state` CHECK ((`state` in (_latin1'HELD',_latin1'CAPTURED',_latin1'RELEASED'))),
  CONSTRAINT `credit_authorization_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_ledger_entries
CREATE TABLE `studio_credit_ledger_entries` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `operation_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_delta` bigint NOT NULL,
  `held_delta` bigint NOT NULL,
  `captured_delta` bigint NOT NULL,
  `facts_bytes` mediumblob NOT NULL,
  `facts_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_entry_operation` (`owner_id`,`operation_key`,`lot_id`),
  KEY `credit_entry_page` (`owner_id`,`created_at`,`id`),
  KEY `credit_entry_lot_fk` (`owner_id`,`lot_id`),
  KEY `credit_entry_allocation_fk` (`owner_id`,`authorization_id`,`lot_id`),
  CONSTRAINT `credit_entry_allocation_fk` FOREIGN KEY (`owner_id`, `authorization_id`, `lot_id`) REFERENCES `studio_credit_authorization_allocations` (`owner_id`, `authorization_id`, `lot_id`),
  CONSTRAINT `credit_entry_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_delta` CHECK ((((`kind` = _latin1'grant') and (`authorization_id` is null) and (`grant_delta` between 1 and 1000000000) and (`held_delta` = 0) and (`captured_delta` = 0)) or ((`kind` = _latin1'hold') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between 1 and 1000000000) and (`captured_delta` = 0)) or ((`kind` = _latin1'capture') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`captured_delta` between 1 and 1000000000) and (`held_delta` = -(`captured_delta`))) or ((`kind` = _latin1'release') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between -(1000000000) and -(1)) and (`captured_delta` = 0)))),
  CONSTRAINT `credit_entry_facts_sha` CHECK ((`facts_sha256` = lower(sha2(`facts_bytes`,256)))),
  CONSTRAINT `credit_entry_facts_size` CHECK ((length(`facts_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_entry_operation_key` CHECK ((((`kind` = _latin1'grant') and (`operation_key` = concat(`lot_id`,_latin1':grant'))) or ((`kind` <> _latin1'grant') and (`operation_key` = concat(`authorization_id`,_latin1':',`kind`)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_lots
CREATE TABLE `studio_credit_lots` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `held_credits` bigint unsigned NOT NULL,
  `captured_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_cycle_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_lot_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_lot_order` (`source_order_id`),
  UNIQUE KEY `credit_lot_cycle` (`source_cycle_id`),
  KEY `credit_lot_eligible` (`owner_id`,`expires_at`,`id`),
  KEY `credit_lot_order_fk` (`owner_id`,`source_order_id`),
  KEY `credit_lot_cycle_fk` (`owner_id`,`source_cycle_id`),
  CONSTRAINT `credit_lot_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_lot_cycle_fk` FOREIGN KEY (`owner_id`, `source_cycle_id`) REFERENCES `studio_billing_cycles` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_order_fk` FOREIGN KEY (`owner_id`, `source_order_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_counters` CHECK (((`grant_credits` between 1 and 1000000000) and (`held_credits` <= 1000000000) and (`captured_credits` <= 1000000000) and ((`held_credits` + `captured_credits`) <= `grant_credits`))),
  CONSTRAINT `credit_lot_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_lot_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_lot_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_lot_source` CHECK ((((`source_order_id` is not null) and (`source_cycle_id` is null)) or ((`source_order_id` is null) and (`source_cycle_id` is not null)))),
  CONSTRAINT `credit_lot_times` CHECK (((`expires_at` > `valid_from`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_plan_versions
CREATE TABLE `studio_credit_plan_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `credit_validity_seconds` int unsigned NOT NULL,
  `billing_interval_months` int unsigned NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  `retired_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_plan_version_key` (`plan_key`,`version_key`),
  CONSTRAINT `credit_plan_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `credit_plan_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `credit_plan_duration` CHECK (((`credit_validity_seconds` between 1 and 31622400) and ((`retired_at` is null) or (`retired_at` >= `published_at`)))),
  CONSTRAINT `credit_plan_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_plan_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_plan_kind` CHECK (((`kind` in (_latin1'subscription',_latin1'topup')) and (((`kind` = _latin1'subscription') and (`billing_interval_months` in (1,3,12))) or ((`kind` = _latin1'topup') and (`billing_interval_months` = 0)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_model_versions
CREATE TABLE `studio_media_model_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `config_bytes` mediumblob NOT NULL,
  `config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_version_key` (`model_id`,`version_key`),
  UNIQUE KEY `studio_media_version_owned_model` (`model_id`,`id`),
  CONSTRAINT `studio_media_version_model_fk` FOREIGN KEY (`model_id`) REFERENCES `studio_media_models` (`id`),
  CONSTRAINT `studio_media_version_sha` CHECK ((`config_sha256` = lower(sha2(`config_bytes`,256)))),
  CONSTRAINT `studio_media_version_size` CHECK ((length(`config_bytes`) between 1 and 65536))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_models
CREATE TABLE `studio_media_models` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `display_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_model_key` (`model_key`),
  KEY `studio_media_model_published_fk` (`id`,`published_version_id`),
  CONSTRAINT `studio_media_model_published_fk` FOREIGN KEY (`id`, `published_version_id`) REFERENCES `studio_media_model_versions` (`model_id`, `id`),
  CONSTRAINT `studio_media_model_kind` CHECK ((`kind` = _ascii'video')),
  CONSTRAINT `studio_media_model_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_model_status` CHECK (((`status` in (_ascii'draft',_ascii'active',_ascii'disabled')) and ((`status` <> _ascii'active') or (`published_version_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_events
CREATE TABLE `studio_payment_events` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_event_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `body_bytes` mediumblob NOT NULL,
  `body_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_event_provider` (`provider_key`,`provider_account`,`provider_event_id`),
  KEY `payment_event_order_fk` (`owner_id`,`order_id`,`provider_key`,`provider_account`),
  KEY `payment_event_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  CONSTRAINT `payment_event_order_fk` FOREIGN KEY (`owner_id`, `order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_event_body_sha` CHECK ((`body_sha256` = lower(sha2(`body_bytes`,256)))),
  CONSTRAINT `payment_event_body_size` CHECK ((length(`body_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_event_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_event_state` CHECK ((`state` in (_latin1'VERIFIED',_latin1'PROCESSED'))),
  CONSTRAINT `payment_event_subject` CHECK (((`order_id` is not null) or (`subscription_id` is not null))),
  CONSTRAINT `payment_event_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_orders
CREATE TABLE `studio_payment_orders` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_order_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_order_owned` (`owner_id`,`id`),
  UNIQUE KEY `payment_order_owned_plan` (`owner_id`,`id`,`plan_version_id`),
  UNIQUE KEY `payment_order_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `payment_order_client` (`owner_id`,`client_action_id`),
  UNIQUE KEY `payment_order_provider_key` (`provider_key`,`provider_account`,`provider_idempotency_key`),
  UNIQUE KEY `payment_order_provider_id` (`provider_key`,`provider_account`,`provider_order_id`),
  KEY `payment_order_plan_fk` (`plan_version_id`),
  CONSTRAINT `payment_order_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `payment_order_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_order_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `payment_order_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `payment_order_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `payment_order_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_order_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_order_state` CHECK (((`state` in (_latin1'CREATING',_latin1'CREATE_UNKNOWN',_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`provider_order_id` is not null) and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `payment_order_times` CHECK (((`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_sessions
CREATE TABLE `studio_sessions` (
  `token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `user_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `revoked_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`token_hash`),
  KEY `studio_session_expiry` (`expires_at`),
  KEY `studio_session_user` (`user_id`,`expires_at`),
  CONSTRAINT `studio_session_user_fk` FOREIGN KEY (`user_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_session_times` CHECK ((`expires_at` > `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_subscriptions
CREATE TABLE `studio_subscriptions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_subscription_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `cancel_at_period_end` tinyint(1) NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `subscription_owned` (`owner_id`,`id`),
  UNIQUE KEY `subscription_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `subscription_provider` (`provider_key`,`provider_account`,`provider_subscription_id`),
  KEY `subscription_plan_fk` (`plan_version_id`),
  CONSTRAINT `subscription_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `subscription_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `subscription_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `subscription_state` CHECK (((`state` in (_latin1'PENDING',_latin1'ACTIVE',_latin1'PAST_DUE',_latin1'CANCELLED',_latin1'ENDED')) and (`cancel_at_period_end` in (0,1)))),
  CONSTRAINT `subscription_times` CHECK (((`period_end` > `period_start`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_users
CREATE TABLE `studio_users` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `email` varchar(254) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `password_hash` varbinary(72) NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT '1',
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_user_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

积分账本链路

plan_versions → payment_orders(CREATING→PAID)→ payment_events(VERIFIED)→ credit_lots(带适用模型与品牌)→ ledger_entries(grant / hold / capture / release,只追加)→ authorizations / allocations。批准生成时按批次分配冻结额度,扣费与释放都有幂等键。
为什么这样设计账本只追加、用触发器禁止修改,才有可信的对账;批准时按批次分配冻结额度,才能在失败时准确释放。约束写在数据库里而不是应用里,是因为应用会有 bug,而约束不会(决策 D3、D17)。

Flova · 接口清单

共 55 个公开接口,取自源码路由盘点。路径都以 /v1 开头;写操作需要会话 Cookie、可信 Host/Origin 与 CSRF;带品牌的路径用 ?profile=flova;共享的登录、素材、模型目录路径不接受 profile 参数。

Agent/统一动作 7 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactionsrunInteractions200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactions/{interaction_id}/responsesrespondInteractionclient_action_id, card_sha256, action, values202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/interaction-actions/{client_action_id}interactionAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changeschangeConversationModelclient_action_id, expected_revision, agent_model, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changes/{client_action_id}modelChangeAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stopstopRunclient_action_id, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/stop-actions/{client_action_id}stopAction200
Asset 4 个
方法路径operationId请求字段成功
GET/v1/assetslistAssets200
POST/v1/assetsuploadAssetfile201
GET/v1/assets/{asset_id}getAsset200
GET/v1/assets/{asset_id}/contentassetContent200/206
LoginSession 1 个
方法路径operationId请求字段成功
POST/v1/auth/logoutlogout200
MaaBinding 5 个
方法路径operationId请求字段成功
GET/v1/maa/model-optionsmaaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/messagessendMaaMessageclient_action_id, content202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/actions/{client_action_id}getMaaAction200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stategetMaaState200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/eventsmaaEvents200
Media 11 个
方法路径operationId请求字段成功
GET/v1/media/modelspublicMediaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requestscreateMediaRequestclient_request_id, model_id, model_version_id, prompt, duration_seconds, ratio, resolution, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requestsmediaRequestByClient200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}getMediaRequest200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}getMediaJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}/outputgetMediaOutput200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/jobmediaJobByRequest200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/approveapproveMediaRequestclient_approval_id, request_sha256, quote_sha256200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/from-maacreateMediaRequestFromMaainput, source_run_id, source_assistant_sha256, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobslistMediaJobs200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/intentslistMediaIntents200
Other 9 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/canvas/revisionslistCanvasRevisions200
GET/v1/projects/{project_id}/canvas/revisions/{revision}getCanvasRevision200
POST/v1/projects/{project_id}/canvas/revisions/{revision}/restorerestoreCanvasRevisionexpected_revision200
GET/v1/projects/{project_id}/document/revisionslistTimelineRevisions200
GET/v1/projects/{project_id}/document/revisions/{revision}getTimelineRevision200
POST/v1/projects/{project_id}/document/revisions/{revision}/restorerestoreTimelineRevisionexpected_revision200
GET/v1/projects/{project_id}/assetslistProjectAssets200
POST/v1/projects/{project_id}/assetsattachProjectAssetasset_id, expected_revision200
POST/v1/projects/{project_id}/assets/{asset_id}/detachdetachProjectAssetexpected_revision200
Project 15 个
方法路径operationId请求字段成功
GET/v1/projectslistProjects200
POST/v1/projectscreateProjecttitle201
GET/v1/projects/{project_id}getProject200
GET/v1/projects/{project_id}/conversationslistConversations200
POST/v1/projects/{project_id}/conversationscreateConversationtitle201
GET/v1/projects/{project_id}/conversations/{conversation_id}getConversation200
GET/v1/projects/{project_id}/canvasgetCanvas200
POST/v1/projects/{project_id}/canvassaveCanvasexpected_revision, state200
GET/v1/projects/{project_id}/documentgetDocument200
POST/v1/projects/{project_id}/documentsaveDocumentexpected_revision, state200
GET/v1/projects/{project_id}/export-jobslistExportJobs200
POST/v1/projects/{project_id}/export-jobsqueueExportdocument_revision, output?202
GET/v1/projects/{project_id}/export-jobs/{job_id}getExportJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindinggetMaaBinding200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindingbindMaaagent_model XOR use_default:true200
User 3 个
方法路径operationId请求字段成功
POST/v1/auth/registerregisteremail, password201
POST/v1/auth/loginloginemail, password200
GET/v1/auth/sessionsession200

Flova 自己公开的接口调用流程(来自其官方资料)

步骤调用关系操作证据
连接账号创作者 → 接入端从 Web 或外部 CLI 进入账号;CLI 官方指南使用浏览器账号授权。W
建立项目上下文接入端 → 项目上下文历史 CLI:project create --name --description 返回 project_id/project_url;已有项目按 ID 读取上下文。A
附上参考素材接入端 → 项目上下文历史 CLI 上传先获得文件对象;随后通过 run 的 --file-from 附给本次输入。A
启动创作Run接入端 → Agent执行历史 CLI:run PROJECT_ID --content;结果含 project_id、stream_chat_id、terminal、assistant_messages、pending_actions。A
报告进度与待决项Agent执行 → 接入端返回消息与 pending_actions;Action 字段有 action_id、resume_message_id、blocking、options[].id/effect。A
形成可用资源Agent执行 → 资源与编辑历史 Resource 含 resource_id、media_type、artifact_role、status、resource_url、error、state_version;官方 Web 把结果展示在相应面板。A
检查并细调工程创作者 → 资源与编辑检查 Media Space/Canvas 与分镜,手改 Timeline;需要时从消息恢复或在新 Project 分叉。W
提交并检查导出接入端 → 导出任务历史 CLI 导出/打包另有 task_id 与 terminal,视频交付含 export_url;官方 Web 导出当前 Timeline。A
为什么这样设计所有写动作都带客户端幂等键(client_action_id / client_request_id / client_approval_id),重复提交返回原回执;报价和批准分成两个接口;停止、切换模型、澄清卡回答都是「动作」而不是「状态修改」,所以每一步都能查回执。这样前端可以放心地重试,后端也不会因为重试产生第二次副作用(决策 D2、D8、D14)。

Flova · 代码与下载

flova-replica.zip
1.2 MB · 53 个文件 · SHA-256 19a1f4b8d12e909d188666e4a346dfe0484d7c4d17e544129f43a764a91e4d67
下载:flova-replica.zip · 后端与合同 · 全部代码
在线试用:http://119.23.106.60:8105/。测试环境,整套独立部署(前端、BFF、Maa 智能体、数据库),可以注册、对话、生成视频(Seedance,测试积分)。页面右下角有“内部复刻研究”标识。
目录内容
frontend/Flova 前端:<code>candidate-8875-unified-v22.html</code> 及它引用的全部脚本与样式
contracts/本品牌的表结构 SQL、ER(svg / mmd)、外键清单;接口目录(json / csv);共享身份库表结构
README.md怎么运行、需要自备什么

运行前端

python3 -m http.server -d frontend 8791
# 打开 http://127.0.0.1:8791/agent-integration/apps-production-brand-v14/flova/candidate-8875-unified-v22.html
后端和全部品牌:Go 后端、Python 媒体桥、全部测试和 7 个品牌的合同在 backend-and-contracts.zip;all-brands-code.zip 是全部。包里没有任何凭据、数据库备份或媒体文件,已做敏感串扫描。
需自备:MySQL 与账号(按 contracts 建库)、Maa 智能体服务(MAA_AGENT_ROOT)、模型网关与对象存储配置。静态前端单独打开不需要后端,但登录、对话、生成、导出需要整套服务。

复刻完成度(审计 2026-10-19)

结论:不是完美复刻。官方资料里的 63 项能力:实测跑通 2、只有通用对话 0、后端有但没跑过 18、没有后端 43。

真正跑通的:从需求形成创作方案;显示进度并停止调整。

界面对照(首页):真实页面采集到 17 个控件 / 标题文字,复刻页找到 11 个;位置偏差中位数 45 px(5/11 个在 24 px 内)。缺的有:✌️、新、城市英雄大片、探索名画背后的故事(360°环绕视角)、万物皆战场、查看全部。

逐个点击(已登录首页):36 个可点控件,点了有效 7 个,只提示“此功能正在接入” 21 个,没反应或只变样式 1 个,禁用 3 个,未点(发送 / 删除 / 退出等)4 个。

只提示“正在接入”的控件(21 次点击,20 种)

FlovaSkill创作人招募中、Skill ×2、FlovaTV、教程、Flova CLI、添加文件、流程 默认、语音输入、热门玩法、剧情短片、商业广告、音乐/MV、更多 Skill ›、剧本生视频(需上传剧本)、故事驱动型视频、多宫格生视频、3D国漫古装精品短剧、玄幻漫剧武打片段、古风甜宠短剧、剧本杀故事视频

点了没反应或只变样式的控件(1 次点击,1 种)

首页

复刻页自报的绑定状态(页面内嵌的绑定清单):原生工作区控件(新建项目、会话、登录等)8 / 10 个没有绑定到原页面的控件,由统一的工作区条代替;路由 assets 缺失或只做了一部分;

首屏截图对照(左:真实产品 flova/reference-desktop.jpg;右:复刻页)· 粗粒度差异 5%

真实截图是测试账号登录后的画面,内容(作品、项目、横幅)本来就不同,差异百分比只作参考,重点看布局。

Flova:左为真实产品截图,右为复刻页

CreaTok · 总览

重点CreaTok 看这三行就够
  • 它是什么:缺信息时弹结构化表单;方案是 3 镜头表格;Agent 交互:只改第 2 镜头,其余不动。
  • 我们借鉴:澄清卡(必填/单选)和镜头局部修改(决策 D5 D6)。
  • 复刻现状:4 项已验证,在线试用 http://119.23.106.60:8106/;后端与其余六个品牌共用一套(24 张表、55 个接口),所以 ER / 表结构 / 接口页的通用部分默认折叠。

围绕商品内容建立发现、Agent/Canvas 创作、TikTok 发布和效果复盘闭环;Web、Chrome 扩展及 CLI/API 分开标注。

证据:W 官方说明,A 官方接口合同,C 客户端代码,O 实测。“未实测”= 只核对了官方页面,没有上传素材或生成。

能力目录
45 项,分 12 类
入口
首页(浅色)+ Agent 任务页
复刻后端
24 张表 · 34 个外键 · 85 个索引

能力目录

智能 Agent 与 Skill 3 项
能力说明证据限制
多步骤创作 Agent 与审批对话提出创作计划,用户批准后再生成。W未确认审批事件协议、取消和失败恢复
销售脚本 Agent面向商品销售生成脚本与提示词。W未核实脚本版本与会话保存规则
脚本转分镜把脚本或商品构想组织成分镜图。W分镜与最终视频字段映射未知
项目与会话 2 项
能力说明证据限制
Canvas 项目管理公开页显示个人、团队项目及 New Flow。O仅确认入口,项目 CRUD 与权限未测试
节点画布和局部重跑连接图像、视频、声音步骤并调整单步。W未核实连接类型、节点状态与版本恢复
素材与商品 3 项
能力说明证据限制
工作区素材与商品复用集中使用素材及商品信息辅助创作。W上传、删除和素材归属未实测
外部内容收藏及商品采集扩展保存帖子、商品媒体和卖点到工作区。C帖子内部 UID 与外部 postId 分离;只读包证据,未调用写接口
TikTok 视频与照片下载下载公开视频或照片组供后续参考。W可访问性及原内容权限仍依来源
发现与分析 4 项
能力说明证据限制
热门内容和竞品发现浏览带货内容、账号及品类趋势。W数据覆盖地区、更新频率未知
TikTok 创作者分析查看创作者及视频表现信息。W具体指标完整性和限额未测试
视频文案提取及结构分析从 TikTok 链接提取文案并拆解内容。W未提交生成,效果、耗时和当前费用未验证
视频反推提示词从参考视频得到创作提示词。W未提交生成,效果、耗时和当前费用未验证
视频生成 4 项
能力说明证据限制
参考视频复刻以自己的商品重构参考视频。W没有提交参考媒体,保真度与结果未测
商品链接转视频导入商品链接或素材制作电商短片。W商品抓取支持范围及脚本确认步骤未测
多模型视频生成入口按任务选择视频模型及输入方式。W首尾帧、参考音频、编辑等能力按模型不同;不认定全部通用
参考动作迁移把参考视频动作迁移到新角色。W未提交生成,效果、耗时和当前费用未验证
声音与人物 4 项
能力说明证据限制
视频换人用目标人物替换原视频主体。W未提交生成,效果、耗时和当前费用未验证
音频驱动口型将音频与视频人物口型对齐。W未提交生成,效果、耗时和当前费用未验证
文字转语音把脚本文字转成声音。W语种、音色与长度限制未实测
声音克隆从声音样本建立目标音色。W样本要求、授权记录及存续规则未知
编辑与导出 9 项
能力说明证据限制
视频配音与字幕翻译进行视频语言转换及配音处理。W语种、字幕及口型能力以所选任务为准
图片文字翻译翻译图中文字并保留排版。W未提交生成,效果、耗时和当前费用未验证
背景移除移除图片背景并输出透明素材。W未提交生成,效果、耗时和当前费用未验证
图像分层拆分图片前景层与背景用于编辑。W未核实分层文件格式和下载组织
图像超分提升图片输出分辨率。W未提交生成,效果、耗时和当前费用未验证
视频字幕擦除清除视频中的已烧录字幕。W未提交生成,效果、耗时和当前费用未验证
视频水印擦除对选定视频内容进行水印清理。W未提交生成,效果、耗时和当前费用未验证
视频超分提高视频分辨率与视觉质量。W分辨率档位视任务,不保证所有输入均可达最高档
Amazon AI 人物元数据标注本地写入标记,读回验证后下载副本。W素材库批量需登录;声明本地处理,未独立审计实现
图片生成 6 项
能力说明证据限制
图像生成与编辑用文本或参考图制作、修改图片。W每个模型输入格式和参数不同
商品图、详情页与 A+ 内容生成商品主图、长详情图及 A+ 图。W三个专用工具归为一组;不认定为自动发布商品页
带货图文轮播制作 TikTok Shop 风格的商品图组。W图组数量与发布约束未测试
商品多角度图由单张商品图生成不同视角。W角度一致性及批量上限未测试
图片构图批量参考参考布局批量制作商品图片。W批量任务状态及部分失败处理未知
虚拟试穿输入模特图与衣服图生成试穿结果。W未提交生成,效果、耗时和当前费用未验证
发布与运营 4 项
能力说明证据限制
TikTok Shop 授权与商品预检连接账号、同步商品、挂商品并可预检。W权限随账号类型、市场及 TikTok 规则;预检不是发布批准
多账号批量排期及状态按时区安排任务,逐条跟踪发布状态。W失败重试和任务状态协议未测试
普通 TikTok 视频与照片发布支持无商品锚点的普通内容发布。W和 Shop 共用账号额度,具体设置按平台支持
带货视频及商品效果复盘从已发布任务查看观看、点击和销售表现。W数据周期、地区和授权影响覆盖;示例数据不是实测结果
协作与权益 2 项
能力说明证据限制
团队工作区、角色与成员额度邀请成员,共用素材、商品和账号并控制额度。W只确认 Owner/Admin/Member 展示,完整权限表未知
订阅、生成积分与发布账号额度生成受方案和积分限制,发布采用账号额度。W官方发布页称发布不扣积分;额度归成员并跨工作区计数,未用账号验证
模板与社区 1 项
能力说明证据限制
社区流程模板和作品案例按场景搜索模板或浏览公开案例。O入口存在不证明已有所有分类模板,复用未执行
开放接入与客户端 3 项
能力说明证据限制
CLI、Open API 与 Agent Skills用 API Key 和 Skills 接入本地 AI Agent。W未安装 CLI;公开 Skills 不是完整服务端源代码
官方 Chrome 侧栏扩展在内容网站提取、收藏、分析并辅助创作。C扩展流式接口不能当主 Web Agent 协议;密钥值不保留
Lark 中的 Agent从 Lark 对话提交想法并审批创作。W没有授权 Lark;机器人事件、租户与会话对应未知

尚不清楚的部分

竞品技术栈观察

公开Web取包受网络超时限制;官方扩展可确认React18.3.1和TSX源路径,并出现Plasmo/Parcel标记。扩展技术不能外推业务Web或后端。

项目观察到的内容状态置信度
Nginx 与代理版本Web入口本轮无响应banner;Nginx产品与准确版本均未知。unknownlow
前端运行语言扩展交付JavaScript并运行于浏览器;Web页面本轮未取得可执行构件。observedhigh
前端源码语言扩展包含popup.tsx、sidepanel.tsx源路径;Web源码语言及TypeScript版本未知。observedhigh
前端框架与版本扩展React18.3.1;出现Plasmo构件标记。Web框架和版本未知。observedhigh
构建与服务端渲染扩展有Parcel运行时与Plasmo构建标记;Web打包器、SSR模式和版本未知。inferredmedium
业务后端语言业务后端语言未知;CLI、扩展JavaScript和同域API都不能证明服务端语言。unknownlow
业务后端框架业务后端框架及准确版本未知。unknownlow
分发层与源站线索媒体CNAME为volcgslb;历史政策披露Vercel托管/分析。应用源站、数据库和生成节点未知。observedmedium
代码仓库与目录关系扩展包有src/popup.tsx等路径;Web与后端同仓、同目录、同进程关系未知。unknownlow
竞品服务调用图

扩展的面板→后台→Cookie API→SSE中继可由静态代码解释;Web Agent、Canvas与TikTok发布仅按官方流程表示,实际执行协议仍未知。

节点层职责证据
扩展面板client读取外部页面选定材料并展示分析事件。C
扩展后台clientManifest V3 background中继HTTP及流消息。C
Web创作界面clientAgent批准生成与Canvas节点入口。W
Web与API入口entrywww.creatok.ai为扩展Web/API共同请求基址。C
账号上下文domain处理Cookie会话与扩展me读取的逻辑职责。I
素材分析处理runtime解释分析/翻译端点及SSE结果的处理职责。I
收藏资料domain内部uid与外部platformPostId的收藏接口资源。C
商品资料domain来源身份、卖点与用户选择媒体的接口资源。C
创作编排职责runtime从脚本建议、批准到后续生成的可能协调职责。I
生成执行职责runtime批准方案后执行生成或转发生成请求的逻辑职责。I
发布与表现domain账号、商品锚点、排期、预检和表现回看的产品职责。W
TikTok平台external发布授权与视频/商品表现的外部业务平台。W
公开媒体入口externalcontent.creatok.com媒体地址及分发CNAME线索。O

增长与运营机制

免费小工具接高意图需求,实案例 Prompt 页面与 CLI / 扩展把分析转为创作。(官方正文;源元数据未知)

复刻状态

能力状态依据
浅色令牌、登录框、新建项目框已验证弹窗白底、主按钮 #18181b;与该品牌浅色界面一致
Agent 对话:新建项目与会话、自动选默认模型、发送收回复已验证真实 Maa,收到回复;输入框在页面下方,窄视口需滚动到可见
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
首页与 Agent 任务页布局已加载沿用早期静态页(文件 12 MB,含内嵌素材)
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8106 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
前端入口:agent-integration/apps-production-brand-v14/creatok/candidate-8875-unified-v22.html
复刻的架构 七个品牌相同,展开查看
flowchart LR
  U[浏览器] -->|静态页| FE[品牌前端<br/>令牌 + 通用层 + 运行时脚本]
  FE -->|/v1/* Cookie + CSRF| BFF[BFF 业务服务 Go]
  BFF --> MY[(MySQL<br/>共享身份库 + 品牌库)]
  BFF -->|会话 / 消息 / 事件| MAA[Maa Agent]
  BFF -->|任务| WK[媒体 worker]
  WK -->|submit_video| GW[模型网关]
  GW --> MOD[视频模型]
  MAA --> GW
  BFF --> OBJ[(素材 / 导出存储)]
所有七个复刻共用同一套后端:24 张表的品牌库 + 15 张表的共享身份库,55 个公开接口(路径里用 ?profile=creatok 区分品牌)。品牌差异在前端:令牌(颜色、圆角、字体)、入口与页面结构。
为什么这样设计七个竞品的后端结构在我们的实测里几乎一致,差别只在入口、模板和视觉。所以复刻把差异收敛到前端令牌,后端只维护一份;这也是最终设计里「共享内核 + 品牌配置」的来源(决策 D17)。

CreaTok · Agent 交互过程

竞品里观察到的

COMPLETED_TWO_TEXT_TURNS 本轮真实完成两轮套餐内文字对话:4题澄清→3镜头文字方案→只改第2镜头→刷新恢复;积分6→6,日额度0%→10%,额外用量关闭。未执行媒体生成;下载按钮未确认文件。

编号操作结果
CK11-D01打开独立新 Agent 任务已登录,任务页支持 / 技能和 @ 素材。未读取或操作既有任务。
CK11-D02打开每日 Agent 额度已用0%;额外用量OFF,说明额度用完后继续使用按用量扣积分;余额6。
CK11-D03Send 统一文字需求进入运行中/思考中,约10s UI变为需要输入,出现4题澄清卡;未出现媒体生成报价。
CK11-D04补齐四题并提交问商品/卖点/目标/场景,语言,可选人物,可选场景。多题选择中文后自动进入下一题,可回上一题。
CK11-D05读取第一轮结果明确无需再补充,输出3镜头表格和复制Markdown/下载表格入口;UI已处理12s。
CK11-D06Send 只改第2镜头的追问约6s UI完成;1/3画面、时间、字幕、镜头转场一致,仅旁白引号排版变化;第2改为咖啡粉落空杯。
CK11-D07复核每日额度与余额已用10%,额外用量仍OFF,余额6;只证明当前页面统计,未对账流水。
CK11-D08点击 Download table已实际点击;download事件6s内没有可读取文件路径,文件下载成功未证实。
CK11-D09重载独立研究标签两个表格与两轮消息恢复;第二个表格逐单元格与重载前一致。

对话维度

维度观察
clarificationO:四题结构化用户输入卡,不是普通聊天段落。
planO:15秒3镜头消息表格,时间/画面/旁白/字幕/镜头方式。
action_confirmation没有媒体任务或费用批准卡;无批准任何付费动作。
targeted_revisionO:自然语言只改第2镜头,结果对比通过场景/时间/字幕/转场保留检查。
tool_callsUI说加载短视频脚本流程;具体tool ID、网络调用、Skill版本和所有隐藏工具未抓取。
artifactsO:两轮各一个消息内表格,复制/下载控件可见;下载只尝试未取得成功证据。
history_resumeO:自身标签reload后两表格及消息恢复,不宣称跨设备。

费用与准入

项值
before_visible_credits6
after_visible_credits6
before_daily_usage_percent0
after_daily_usage_percent10
extra_usage_beforefalse
extra_usage_afterfalse
media_generatedfalse
media_generation_approval_givenfalse
ledger_verifiedfalse
scope当前账号套餐内Agent额度;不推断其他用户或套餐的文字计费。

对我们设计的启示

结论内容做法 / 原因
adopt缺信息时使用有ID、必填/可选、单选/文本、可回退的澄清卡,回答后继续原Run。一张pending交互卡汇总必要信息,用户补齐并提交,服务端验状态/摘要后恢复Run。
adopt将文字方案呈现为可比对的镜头表格,保留上一版结果。PLAN输出用镜头稳定键、时段/画面/对白/字幕/转场渲染;复制/下载为用户明确动作。
adapt区分套餐内Agent额度与额外扣分开关,展示媒体费用单独授权。
adapt局部修改保持目标范围,同时提示前后镜头的连续性影响。
reject不把消息表格的完整重发当成对现有时间轴无条件覆盖,也不从Download按钮可见推出媒体导出成功。本轮没有编辑器绑定/媒体导出实测;需用已有Output/Selection/CAS协议明确采纳。

竞品公开的调用流程

步骤调用关系操作证据
面板提交请求扩展面板 → 扩展后台chrome.runtime消息API_REQUEST携带path/method/body/query/headers;流请求API_STREAM_REQUEST另带streamId。C
读取账号上下文扩展后台 → Web入口GET https://www.creatok.ai/api/extension/me;fetch credentials:include;客户端处理code===0/data与ApiError。C
发起视觉分析扩展后台 → Web入口POST /api/extension/tiktok-transcript-generator/decompose-video-script;body含video_id、locale、analysis_contract:visual_v1;Accept:text/event-stream。C
中继分析事件扩展后台 → 扩展面板后台读取response.body并发送API_STREAM_EVENT;payload含streamId、type:chunk|done|error;UI按空行解析data JSON的partial/final/error/done。C
保存收藏身份扩展后台 → Web入口POST /api/extension/collections/posts;提交submittedUrl、contentType、postSnapshot、locale等;返回post.uid与platformPostId。C
导入商品资料扩展后台 → Web入口POST /api/extension/products/analyze-selling-points与/api/extension/products;来源platform/externalProductId、卖点与已选media[]分别提交。C
批准脚本并生成创作界面 → Web入口官方Web Agent先给脚本和生成方案,用户批准后进入生成;对应HTTP方法、URL、body与事件格式未取得。W
画布局部重跑创作界面 → Web入口官方Canvas支持节点组合与局部重跑;节点保存、连线编辑、版本、运行接口均未核实。W
排期发布与回看Web入口 → 发布平台官方流程为TikTok授权、商品锚点/预检、立即或排期发布、状态与表现回收;具体TikTok调用与内部发布API合同未知。W

在本品牌复刻上的测试结果

能力状态依据
Agent 对话:新建项目与会话、自动选默认模型、发送收回复已验证真实 Maa,收到回复;输入框在页面下方,窄视口需滚动到可见
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
首页与 Agent 任务页布局已加载沿用早期静态页(文件 12 MB,含内嵌素材)
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8106 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
复刻里的 Agent 交互设计(时序图、状态机、规则) 七个品牌相同,展开查看
sequenceDiagram
  autonumber
  participant B as 浏览器
  participant S as BFF
  participant DB as MySQL
  participant M as Maa Agent
  participant W as 媒体 worker
  participant G as 模型网关
  B->>S: POST /projects, /conversations
  B->>S: POST maa/binding
  S->>DB: maa_bindings dispatching
  S->>M: 创建原生会话
  M-->>S: native_cid
  S->>DB: maa_bindings bound
  B->>S: POST maa/messages {client_action_id, content}
  S->>DB: maa_message_actions 幂等回执
  S-->>B: 202 + run_id
  S->>M: 提交消息
  S->>DB: maa_runs queued 到 running
  loop 事件游标
    B->>S: GET runs/{id}/events
    S-->>B: assistant.delta / assistant.message
  end
  M-->>S: run.completed
  S->>DB: 记录助手内容 SHA-256
  B->>S: POST media/requests/from-maa {source_run_id, sha256}
  S-->>B: 报价 + 有效期 + quote_sha256
  B->>S: POST approve {client_approval_id, request_sha256, quote_sha256}
  S->>DB: 冻结积分 + 建任务
  W->>G: submit_video
  G-->>W: 视频
  W->>DB: 产出入库 + 扣费
  B->>S: GET media/jobs/{id}

Run 状态机

stateDiagram-v2
  [*] --> queued: POST maa/messages 202
  queued --> running
  running --> waiting_interaction: 需要澄清卡
  waiting_interaction --> running: respondInteraction
  running --> completed
  running --> failed
  running --> cancelled: stopRun
  waiting_interaction --> cancelled
  completed --> [*]
  failed --> [*]
  cancelled --> [*]

媒体任务状态机

stateDiagram-v2
  [*] --> quoted
  quoted --> expired: 约 5 分钟
  quoted --> approved: approve
  approved --> held: 冻结积分
  held --> queued
  queued --> running: worker 领取
  running --> succeeded: 产出入库
  succeeded --> captured: 扣费确认
  running --> failed
  failed --> 待核对: 供应商已受理后失败,冻结保留,界面写「费用待核对」

交互规则

环节规则落库
绑定会话bindMaa 传 agent_model 或 use_default;先 dispatching,拿到原生会话号后才 bound(约束保证二者一致)maa_bindings
发消息client_action_id 幂等;返回 202 + run_id;同一个动作 ID 内容不同返回 409maa_message_actions, maa_runs
事件按 seq 游标读取;服务端先清洗路径、内部地址和令牌再公开maa_runs.last_event_seq
完成证据完成时记录助手内容的两份 SHA-256 与字节数maa_runs
澄清卡(HITL)Run 进入 waiting_interaction;respondInteraction 带 card_sha256;不能恢复两次maa_interactions, maa_interaction_actions
停止stopRun 幂等,回执只说「已观测到终态」;对话里保留「已停止回复」maa_run_stop_actions
切换模型必须带 expected_revision;有未决切换时拒绝发新消息maa_model_change_actions
生成视频from-maa → 报价 → 批准 → 冻结 → worker → 入库 → 扣费;批准带请求哈希与报价哈希studio_media_*, 积分账本
报价过期批准被拒,不建任务不扣费;界面提示重新报价studio_media_requests.expires_at
失败后的积分供应商已受理后失败:冻结保留,界面写「费用待核对」,不自动释放;本部署的账本里还没出现过释放分录(12 笔冻结、8 笔扣费、其余 4 笔仍冻结)studio_credit_ledger_entries
会话过期每 8 秒检查登录状态;失效时横幅提示,登录后恢复—
为什么这样设计把事件做成带序号的流、把动作做成带幂等键的回执,是因为长回复一定会遇到断线、刷新和重复点击。序号让续读不丢不重;幂等让重复点击安全;诚实的回执(只说「已观测到终态」)避免把「已停止」说成「不收费」。我们还在复刻里修掉了一个停止按钮从不发请求的缺陷,这说明每条交互路径都需要端到端测试(决策 D8)。

CreaTok · ER 图

品牌库 studio_profile_creatok:24 张表、34 个真实外键、66 个检查约束、22 个触发器。图由实际表结构生成,箭头从父表指向子表。品牌库与共享身份库之间的 owner_id、asset_id 靠应用层归属检查,不画成外键。

共 61 条关系:一对多 37 条,一对一 10 条,一对多(父可选) 10 条,一对一(父可选) 4 条;多对多 2 组(经关联表)。

用户与登录 素材 积分账本 支付与订阅 模型目录 项目与会话 画布 · 时间轴 · 导出 Agent 交互 生成 · 媒体 · 交付

基数标注(线上的文字,箭头从父表指向子表):1:N 一对多,子表外键必填;0..1:N 一对多,但外键可空,所以父是可选的;1:1 一对一,子表外键上有唯一键;0..1:1 一对一且父可选。红色粗双向线 = N:M 多对多,经带 ⇄ 的关联表(它的主键就是两个外键)。实线 = 数据库真实外键;灰色虚线 = 跨库关系(两个库之间数据库无法建外键,由应用层检查);橙色粗框与粗线 = Agent 交互相关。每张表都带 owner_id(指向用户),为了图清楚没有逐条画线。

全景图:全部实体与关系

默认缩放到页面宽度,先看整体结构;点一下图切换到原始大小(看清表名,在框内上下左右滚动),再点一下缩回。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":26,"rankSpacing":80}}}%%
flowchart LR
  subgraph sg_i_user["用户与登录"]
    direction TB
    t_studio_users["users"]
    t_studio_sessions["sessions"]
  end
  subgraph sg_i_asset["素材"]
    direction TB
    t_studio_assets["assets"]
  end
  subgraph sg_i_credit["积分账本"]
    direction TB
    t_studio_credit_accounts["credit_accounts"]
    t_studio_credit_lots["credit_lots"]
    t_studio_credit_authorizations["credit_authorizations"]
    t_studio_credit_authorization_allocations["⇄ credit_authorization_allocations"]
    t_studio_credit_ledger_entries["credit_ledger_entries"]
    t_studio_credit_plan_versions["credit_plan_versions"]
    t_studio_billing_cycles["billing_cycles"]
  end
  subgraph sg_i_pay["支付与订阅"]
    direction TB
    t_studio_payment_orders["payment_orders"]
    t_studio_payment_events["payment_events"]
    t_studio_subscriptions["subscriptions"]
  end
  subgraph sg_i_model["模型目录"]
    direction TB
    t_studio_media_models["media_models"]
    t_studio_media_model_versions["media_model_versions"]
  end
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  subgraph sg_media["生成 · 媒体 · 交付"]
    direction TB
    t_studio_media_requests["media_requests"]
    t_studio_media_jobs["media_jobs"]
    t_studio_media_submission_attempts["media_submission_attempts"]
    t_studio_media_outputs["media_outputs"]
    t_generation_jobs["gen_jobs"]
    t_generation_reservations["gen_reservations"]
    t_generation_deliveries["gen_deliveries"]
    t_generation_development_budgets["gen_development_budgets"]
    t_studio_credit_recovery_intents["credit_recovery_intents"]
  end
  t_studio_users -->|"1:N"| t_studio_assets
  t_studio_payment_orders -->|"1:1"| t_studio_billing_cycles
  t_studio_subscriptions -->|"1:N"| t_studio_billing_cycles
  t_studio_users -->|"1:1"| t_studio_credit_accounts
  t_studio_credit_authorizations -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_lots -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_authorizations
  t_studio_credit_authorization_allocations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_authorizations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_lots -->|"1:N"| t_studio_credit_ledger_entries
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_lots
  t_studio_billing_cycles -->|"0..1:1"| t_studio_credit_lots
  t_studio_payment_orders -->|"0..1:1"| t_studio_credit_lots
  t_studio_media_models -->|"1:N"| t_studio_media_model_versions
  t_studio_media_model_versions -->|"0..1:1"| t_studio_media_models
  t_studio_payment_orders -->|"0..1:N"| t_studio_payment_events
  t_studio_subscriptions -->|"0..1:N"| t_studio_payment_events
  t_studio_users -->|"1:N"| t_studio_payment_events
  t_studio_credit_plan_versions -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_sessions
  t_studio_credit_plan_versions -->|"1:N"| t_studio_subscriptions
  t_studio_users -->|"1:N"| t_studio_subscriptions
  t_generation_jobs -->|"1:1"| t_generation_deliveries
  t_studio_projects -->|"1:N"| t_generation_deliveries
  t_studio_conversations -->|"1:N"| t_generation_deliveries
  t_generation_development_budgets -->|"1:N"| t_generation_jobs
  t_studio_projects -->|"1:N"| t_generation_jobs
  t_studio_conversations -->|"1:N"| t_generation_jobs
  t_generation_jobs -->|"1:1"| t_generation_reservations
  t_studio_conversations -->|"1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"0..1:1"| t_maa_bindings
  t_maa_interactions -->|"1:1"| t_maa_interaction_actions
  t_maa_runs -->|"1:N"| t_maa_interactions
  t_maa_bindings -->|"1:N"| t_maa_message_actions
  t_maa_runs -->|"0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"1:N"| t_maa_model_change_actions
  t_maa_runs -->|"1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"1:N"| t_maa_runs
  t_studio_canvases -->|"1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"1:1"| t_studio_canvases
  t_studio_projects -->|"1:N"| t_studio_conversations
  t_studio_media_jobs -->|"1:N"| t_studio_credit_recovery_intents
  t_studio_projects -->|"1:N"| t_studio_export_jobs
  t_studio_media_requests -->|"1:1"| t_studio_media_jobs
  t_studio_media_jobs -->|"1:N"| t_studio_media_outputs
  t_studio_conversations -->|"1:N"| t_studio_media_requests
  t_maa_runs -->|"0..1:N"| t_studio_media_requests
  t_studio_media_jobs -->|"1:N"| t_studio_media_submission_attempts
  t_studio_projects -->|"1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"1:1"| t_studio_video_documents
  t_studio_assets -.->|"1:N"| t_studio_project_asset_refs
  t_studio_assets -.->|"1:N"| t_studio_media_outputs
  t_studio_assets -.->|"1:N"| t_generation_deliveries
  t_studio_media_model_versions -.->|"1:N"| t_studio_media_requests
  t_studio_media_models -.->|"1:N"| t_studio_media_requests
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_media_jobs
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_credit_recovery_intents
  t_studio_credit_authorizations <-.->|"N:M · 经 credit_authorization_allocations"| t_studio_credit_lots
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_users,t_studio_sessions user
class t_studio_assets asset
class t_studio_credit_accounts,t_studio_credit_lots,t_studio_credit_authorizations,t_studio_credit_authorization_allocations,t_studio_credit_ledger_entries,t_studio_credit_plan_versions,t_studio_billing_cycles credit
class t_studio_payment_orders,t_studio_payment_events,t_studio_subscriptions pay
class t_studio_media_models,t_studio_media_model_versions model
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
class t_studio_media_requests,t_studio_media_jobs,t_studio_media_submission_attempts,t_studio_media_outputs,t_generation_jobs,t_generation_reservations,t_generation_deliveries,t_generation_development_budgets,t_studio_credit_recovery_intents media
class t_studio_credit_authorization_allocations,t_studio_project_asset_refs junction
  style sg_i_user fill:none,stroke:#6b7280,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_asset fill:none,stroke:#06b6d4,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_credit fill:none,stroke:#eab308,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_pay fill:none,stroke:#ec4899,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_model fill:none,stroke:#6366f1,stroke-width:1px,stroke-dasharray:5 4
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  style sg_media fill:none,stroke:#22c55e,stroke-width:1px
  linkStyle 30,31,32,33,34,35,36,37,38,48 stroke:#f97316,stroke-width:2.5px
  linkStyle 54,55,56,57,58,59,60 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 61,62 stroke:#dc2626,stroke-width:3px

多对多(N:M)

数据库里没有“多对多”这种列,它总是靠一张关联表表达。下面这几张表的主键由两个外键组成,所以它们两端是多对多:

多对多的两端关联表关联表的主键 / 唯一键
studio_credit_authorizations ⇄ studio_credit_lotsstudio_credit_authorization_allocationsowner_id, authorization_id, lot_id
studio_projects ⇄ studio_assetsstudio_project_asset_refsproject_id, asset_id

Agent 交互相关(圈出来)

绑定 → Run → 消息 / 澄清卡 / 切换模型 / 停止,每个动作一张回执表;线上是“外键列 · 基数”。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  t_studio_conversations -->|"conversation_id · 1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"pending_model_change_id · 0..1:1"| t_maa_bindings
  t_maa_interactions -->|"interaction_id · 1:1"| t_maa_interaction_actions
  t_maa_runs -->|"public_run_id · 1:N"| t_maa_interactions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_message_actions
  t_maa_runs -->|"public_run_id · 0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_model_change_actions
  t_maa_runs -->|"public_run_id · 1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_runs
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  linkStyle 0,1,2,3,4,5,6,7,8 stroke:#f97316,stroke-width:2.5px

画布与时间轴

项目下是一份视频文档,画布、画布版本、时间线版本和导出都挂在这份文档上;素材放在共享身份库,项目只记引用,项目与素材是多对多,经 project_asset_refs。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_asset["素材(共享身份库)"]
    direction TB
    t_studio_assets["assets"]
  end
  t_studio_canvases -->|"project_id · 1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"restored_from_revision · 0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_canvases
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
  t_studio_projects -->|"project_id · 1:N"| t_studio_export_jobs
  t_studio_projects -->|"project_id · 1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"project_id · 1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"restored_from_revision · 0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_video_documents
  t_studio_assets -.->|"asset_id · 1:N"| t_studio_project_asset_refs
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_studio_assets asset
class t_studio_project_asset_refs junction
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px
  linkStyle 9 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 10 stroke:#dc2626,stroke-width:3px
关系清单 逐条 61 条,含基数
父表子表外键列(子表里)基数约束域
generation_jobsgeneration_deliveriesjob_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_assetsgeneration_deliveriesasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_conversationsgeneration_deliveriesowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_deliveriesowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_development_budgetsgeneration_jobsowner_id, budget_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_conversationsgeneration_jobsowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_jobsgeneration_reservationsjob_id, budget_id, owner_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_model_change_actionsmaa_bindingsowner_id, project_id, conversation_id, id, pending_model_change_id0..1:1 一对一(父可选)DB 外键Agent 交互 → Agent 交互
studio_conversationsmaa_bindingsowner_id, project_id, conversation_id1:1 一对一DB 外键项目与会话 → Agent 交互
maa_interactionsmaa_interaction_actionsowner_id, project_id, conversation_id, binding_id, public_run_id, interaction_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_runsmaa_interactionsowner_id, project_id, conversation_id, binding_id, public_run_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_message_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_message_actionsowner_id, project_id, conversation_id, binding_id, public_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_model_change_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_run_stop_actionsowner_id, project_id, conversation_id, binding_id, public_run_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_runsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
studio_usersstudio_assetsowner_id1:N 一对多DB 外键用户与登录 → 素材
studio_payment_ordersstudio_billing_cyclesowner_id, payment_order_id, plan_version_id1:1 一对一DB 外键支付与订阅 → 积分账本
studio_subscriptionsstudio_billing_cyclesowner_id, subscription_id, provider_key, provider_account1:N 一对多DB 外键支付与订阅 → 积分账本
studio_canvas_revisionsstudio_canvas_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_canvasesstudio_canvas_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_canvasesowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_projectsstudio_conversationsowner_id, project_id1:N 一对多DB 外键项目与会话 → 项目与会话
studio_usersstudio_credit_accountsowner_id1:1 一对一DB 外键用户与登录 → 积分账本
studio_credit_authorizationsstudio_credit_authorization_allocationsowner_id, authorization_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_authorization_allocationsowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_authorizationsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_authorization_allocationsstudio_credit_ledger_entriesowner_id, authorization_id, lot_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_authorizationsstudio_credit_ledger_entriesowner_id, authorization_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_ledger_entriesowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_billing_cyclesstudio_credit_lotsowner_id, source_cycle_id0..1:1 一对一(父可选)DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_lotsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_payment_ordersstudio_credit_lotsowner_id, source_order_id0..1:1 一对一(父可选)DB 外键支付与订阅 → 积分账本
studio_credit_authorizationsstudio_credit_recovery_intentsauthorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_jobsstudio_credit_recovery_intentsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_projectsstudio_export_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_credit_authorizationsstudio_media_jobscredit_authorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_requestsstudio_media_jobsowner_id, project_id, conversation_id, request_id, request_sha256, quote_sha2561:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_model_versionsmodel_id1:N 一对多DB 外键模型目录 → 模型目录
studio_media_model_versionsstudio_media_modelsid, published_version_id0..1:1 一对一(父可选)DB 外键模型目录 → 模型目录
studio_assetsstudio_media_outputsasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_outputsowner_id, project_id, conversation_id, job_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_runsstudio_media_requestsowner_id, project_id, conversation_id, source_binding_id, source_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → 生成 · 媒体 · 交付
studio_conversationsstudio_media_requestsowner_id, project_id, conversation_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_media_model_versionsstudio_media_requestsmodel_version_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_requestsmodel_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_submission_attemptsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_payment_ordersstudio_payment_eventsowner_id, order_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_subscriptionsstudio_payment_eventsowner_id, subscription_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_usersstudio_payment_eventsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_credit_plan_versionsstudio_payment_ordersplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_payment_ordersowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_assetsstudio_project_asset_refsasset_id1:N 一对多跨库(应用层检查)素材 → 画布 · 时间轴 · 导出
studio_projectsstudio_project_asset_refsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_usersstudio_sessionsuser_id1:N 一对多DB 外键用户与登录 → 用户与登录
studio_credit_plan_versionsstudio_subscriptionsplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_subscriptionsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_timeline_revisionsstudio_timeline_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_video_documentsstudio_timeline_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_video_documentsowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
完整 ER:39 张表的全部列,带主键 / 外键与基数符号(鸦脚)
erDiagram
  studio_assets {
    CHAR id PK
    CHAR owner_id FK
    CHAR sha256
    JSON metadata_json
    VARCHAR storage_key
    DATETIME created_at
    VARCHAR publication_state
  }
  studio_billing_cycles {
    CHAR id PK
    CHAR owner_id FK
    CHAR subscription_id FK
    CHAR plan_version_id FK
    CHAR payment_order_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_invoice_id
    DATETIME period_start
    DATETIME period_end
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_accounts {
    CHAR owner_id PK
    VARCHAR unit
    VARCHAR status
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_credit_authorization_allocations {
    CHAR owner_id FK
    CHAR authorization_id PK
    CHAR lot_id PK
    BIGINT_UNSIGNED allocated_credits
    DATETIME created_at
  }
  studio_credit_authorizations {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR profile
    CHAR project_id
    CHAR conversation_id
    CHAR job_id
    CHAR request_id
    CHAR request_sha256
    CHAR quote_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    MEDIUMBLOB reservation_bytes
    CHAR reservation_sha256
    MEDIUMBLOB policy_bytes
    CHAR policy_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_ledger_entries {
    CHAR id PK
    CHAR owner_id FK
    CHAR lot_id FK
    CHAR authorization_id FK
    VARCHAR operation_key
    VARCHAR kind
    BIGINT grant_delta
    BIGINT held_delta
    BIGINT captured_delta
    MEDIUMBLOB facts_bytes
    CHAR facts_sha256
    DATETIME created_at
  }
  studio_credit_lots {
    CHAR id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED grant_credits
    BIGINT_UNSIGNED held_credits
    BIGINT_UNSIGNED captured_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    CHAR source_order_id FK
    CHAR source_cycle_id FK
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_plan_versions {
    CHAR id PK
    VARCHAR plan_key
    VARCHAR version_key
    VARCHAR kind
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    INT_UNSIGNED credit_validity_seconds
    INT_UNSIGNED billing_interval_months
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    DATETIME published_at
    DATETIME retired_at
  }
  studio_media_model_versions {
    CHAR id PK
    CHAR model_id FK
    VARCHAR version_key
    MEDIUMBLOB config_bytes
    CHAR config_sha256
    DATETIME published_at
  }
  studio_media_models {
    CHAR id PK
    VARCHAR model_key
    VARCHAR kind
    VARCHAR display_name
    VARCHAR status
    CHAR published_version_id FK
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_payment_events {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_event_id
    CHAR order_id FK
    CHAR subscription_id FK
    MEDIUMBLOB body_bytes
    CHAR body_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_payment_orders {
    CHAR id PK
    CHAR owner_id FK
    CHAR client_action_id
    CHAR plan_version_id FK
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_idempotency_key
    VARCHAR provider_order_id
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_sessions {
    CHAR token_hash PK
    CHAR user_id FK
    DATETIME created_at
    DATETIME expires_at
    DATETIME revoked_at
  }
  studio_subscriptions {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_subscription_id
    CHAR plan_version_id FK
    VARCHAR state
    DATETIME period_start
    DATETIME period_end
    TINYINT cancel_at_period_end
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_users {
    CHAR id PK
    VARCHAR email
    VARBINARY password_hash
    TINYINT enabled
    DATETIME created_at
  }
  generation_deliveries {
    CHAR job_id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR asset_id FK
    CHAR request_sha256
    CHAR maa_source_sha256
    CHAR provider_source_sha256
    VARCHAR model
    VARCHAR tool_name
    CHAR input_sha256
    JSON metadata_json
    DATETIME created_at
  }
  generation_development_budgets {
    CHAR id PK
    CHAR owner_id
    VARCHAR policy_version
    BIGINT_UNSIGNED credits_limit
    BIGINT_UNSIGNED credits_reserved
    INT_UNSIGNED calls_limit
    INT_UNSIGNED calls_reserved
    DATETIME expires_at
  }
  generation_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR native_conversation_id
    CHAR native_run_id
    CHAR source_sha256
    CHAR budget_id FK
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    VARCHAR status
    INT_UNSIGNED attempts
    MEDIUMBLOB result_bytes
    DATETIME updated_at
  }
  generation_reservations {
    CHAR job_id PK
    CHAR budget_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED credits
    INT_UNSIGNED calls
    VARCHAR status
  }
  maa_bindings {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR provider
    VARCHAR model_name
    VARCHAR status
    VARCHAR native_cid
    DATETIME created_at
    BIGINT_UNSIGNED model_revision
    CHAR pending_model_change_id FK
  }
  maa_interaction_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR interaction_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR client_action_id
    CHAR card_sha256
    CHAR request_sha256
    CHAR content_sha256
    VARCHAR response_action
    JSON values_payload
    VARCHAR status
    DATETIME created_at
  }
  maa_interactions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR card_sha256
    JSON card_payload
    DATETIME created_at
  }
  maa_message_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_action_id
    CHAR request_sha256
    MEDIUMTEXT content
    VARCHAR status
    CHAR public_run_id FK
    CHAR proposed_run_id
    DATETIME created_at
  }
  maa_model_change_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    CHAR client_action_id
    CHAR request_sha256
    CHAR native_request_sha256
    BIGINT_UNSIGNED expected_revision
    VARCHAR from_provider
    VARCHAR from_model_name
    VARCHAR target_provider
    VARCHAR target_model_name
    VARCHAR status
    BIGINT_UNSIGNED dispatch_attempt
    JSON receipt_payload
    DATETIME created_at
    DATETIME updated_at
  }
  maa_run_stop_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    VARCHAR native_rid
    CHAR client_action_id
    CHAR request_sha256
    VARCHAR stop_action
    VARCHAR status
    BIGINT_UNSIGNED attempt
    VARCHAR terminal_run_status
    DATETIME created_at
    DATETIME updated_at
  }
  maa_runs {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_rid
    VARCHAR status
    DATETIME created_at
    CHAR native_assistant_sha256
    CHAR public_assistant_sha256
    BIGINT_UNSIGNED native_assistant_bytes
    BIGINT_UNSIGNED assistant_event_seq
    BIGINT_UNSIGNED last_event_seq
  }
  studio_canvas_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_canvases {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_conversations {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    VARCHAR title
    DATETIME created_at
  }
  studio_credit_recovery_intents {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256
    VARCHAR kind
    CHAR authorization_id FK
    MEDIUMBLOB intent_bytes
    CHAR intent_sha256
    VARCHAR state
    CHAR lease_token_sha256
    DATETIME lease_until
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_export_jobs {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED document_revision
    CHAR input_hash
    VARCHAR status
    JSON input_json
    JSON output_json
    VARCHAR error_code
    CHAR lease_token_hash
    DATETIME lease_until
    INT_UNSIGNED attempts
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR request_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256 FK
    CHAR client_approval_id
    CHAR approval_sha256
    CHAR credit_authorization_id FK
    VARCHAR authorization_mode
    DATETIME authorization_expires_at
    VARCHAR status
    BIGINT_UNSIGNED revision
    CHAR lease_token_sha256
    DATETIME lease_until
    VARCHAR provider_task_id
    CHAR client_cancel_id
    CHAR cancel_request_sha256
    DATETIME cancel_requested_at
    CHAR staged_asset_id
    CHAR staged_asset_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_outputs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED ordinal
    CHAR asset_id FK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    CHAR provider_result_sha256
    DATETIME created_at
  }
  studio_media_requests {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_request_id
    CHAR model_id FK
    CHAR model_version_id FK
    CHAR model_config_sha256
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    MEDIUMBLOB quote_bytes
    CHAR quote_sha256
    BIGINT_UNSIGNED quoted_credits
    DATETIME expires_at
    VARCHAR origin
    CHAR source_binding_id FK
    CHAR source_run_id FK
    CHAR source_assistant_sha256
    MEDIUMBLOB source_snapshot_bytes
    DATETIME created_at
  }
  studio_media_submission_attempts {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED attempt_no
    CHAR request_sha256 FK
    VARCHAR provider_idempotency_key
    VARCHAR stage
    DATETIME dispatch_started_at
    VARCHAR provider_task_id
    CHAR receipt_sha256
    CHAR terminal_receipt_sha256
    CHAR reconciliation_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_project_asset_refs {
    CHAR project_id PK
    CHAR owner_id FK
    CHAR asset_id PK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
    DATETIME detached_at
  }
  studio_projects {
    CHAR id PK
    CHAR owner_id
    VARCHAR title
    DATETIME created_at
  }
  studio_timeline_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_video_documents {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_users ||--o{ studio_assets : "owner_id"
  studio_payment_orders ||--o| studio_billing_cycles : "plan_version_id"
  studio_subscriptions ||--o{ studio_billing_cycles : "provider_account"
  studio_users ||--o| studio_credit_accounts : "owner_id"
  studio_credit_authorizations ||--o{ studio_credit_authorization_allocations : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_authorization_allocations : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_authorizations : "owner_id"
  studio_credit_authorization_allocations |o--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_authorizations |o--o{ studio_credit_ledger_entries : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_lots : "owner_id"
  studio_billing_cycles |o--o| studio_credit_lots : "source_cycle_id"
  studio_payment_orders |o--o| studio_credit_lots : "source_order_id"
  studio_media_models ||--o{ studio_media_model_versions : "model_id"
  studio_media_model_versions |o--o| studio_media_models : "published_version_id"
  studio_payment_orders |o--o{ studio_payment_events : "provider_account"
  studio_subscriptions |o--o{ studio_payment_events : "provider_account"
  studio_users ||--o{ studio_payment_events : "owner_id"
  studio_credit_plan_versions ||--o{ studio_payment_orders : "plan_version_id"
  studio_users ||--o{ studio_payment_orders : "owner_id"
  studio_users ||--o{ studio_sessions : "user_id"
  studio_credit_plan_versions ||--o{ studio_subscriptions : "plan_version_id"
  studio_users ||--o{ studio_subscriptions : "owner_id"
  generation_jobs ||--o| generation_deliveries : "job_id"
  studio_projects ||--o{ generation_deliveries : "project_id"
  studio_conversations ||--o{ generation_deliveries : "session_id"
  generation_development_budgets ||--o{ generation_jobs : "budget_id"
  studio_projects ||--o{ generation_jobs : "project_id"
  studio_conversations ||--o{ generation_jobs : "session_id"
  generation_jobs ||--o| generation_reservations : "budget_id"
  studio_conversations ||--o| maa_bindings : "conversation_id"
  maa_model_change_actions |o--o| maa_bindings : "pending_model_change_id"
  maa_interactions ||--o| maa_interaction_actions : "interaction_id"
  maa_runs ||--o{ maa_interactions : "public_run_id"
  maa_bindings ||--o{ maa_message_actions : "binding_id"
  maa_runs |o--o{ maa_message_actions : "public_run_id"
  maa_bindings ||--o{ maa_model_change_actions : "binding_id"
  maa_runs ||--o| maa_run_stop_actions : "public_run_id"
  maa_bindings ||--o{ maa_runs : "binding_id"
  studio_canvases ||--o{ studio_canvas_revisions : "project_id"
  studio_canvas_revisions |o--o{ studio_canvas_revisions : "restored_from_revision"
  studio_projects ||--o| studio_canvases : "project_id"
  studio_projects ||--o{ studio_conversations : "project_id"
  studio_media_jobs ||--o{ studio_credit_recovery_intents : "job_id"
  studio_projects ||--o{ studio_export_jobs : "project_id"
  studio_media_requests ||--o| studio_media_jobs : "request_id"
  studio_media_jobs ||--o{ studio_media_outputs : "job_id"
  studio_conversations ||--o{ studio_media_requests : "conversation_id"
  maa_runs |o--o{ studio_media_requests : "source_run_id"
  studio_media_jobs ||--o{ studio_media_submission_attempts : "job_id"
  studio_projects ||--o{ studio_project_asset_refs : "project_id"
  studio_video_documents ||--o{ studio_timeline_revisions : "project_id"
  studio_timeline_revisions |o--o{ studio_timeline_revisions : "restored_from_revision"
  studio_projects ||--o| studio_video_documents : "project_id"
  studio_assets ||--o{ studio_project_asset_refs : "asset_id"
  studio_assets ||--o{ studio_media_outputs : "asset_id"
  studio_assets ||--o{ generation_deliveries : "asset_id"
  studio_media_model_versions ||--o{ studio_media_requests : "model_version_id"
  studio_media_models ||--o{ studio_media_requests : "model_id"
  studio_credit_authorizations |o--o{ studio_media_jobs : "credit_authorization_id"
  studio_credit_authorizations |o--o{ studio_credit_recovery_intents : "authorization_id"
自动生成的整体图(按真实表结构,备查)
CreaTok 品牌库 ER 图

共享身份库 ER 图

共享身份库 ER 图
实测验证与设计原因 七个品牌相同,展开查看

实测验证(本机 MySQL 实库)

对照本机真实运行的数据库 studio_profile_creatok 和 studio_platform_identity,逐项核对本页的表、列、关系与说明。18 / 18 项通过。

查看全部核对项
核对项结果
表数 = 发布值 24通过
列数 = 289通过
外键数 = 34通过
检查约束数 = 66通过
触发器数 = 22通过
索引数(含主键) = 85通过
列定义与 pollo 逐列一致通过
外键与 pollo 完全一致通过
索引与 pollo 一致通过
触发器与 pollo 一致通过
外键 CSV 行数 = 实库外键数通过
mmd 实体 = 实库表通过
svg 标题 24 tables / 34 actual foreign keys = 实库通过
参考 SQL 表集合与列顺序 = 实库通过
15 张表通过
外键 CSV 行数 = 实库外键数通过
svg 标题与实库一致通过
参考 SQL 表集合与列顺序 = 实库通过

实库里的关系是这样的

  • 34 个外键里 31 个是复合外键,且都带着 owner_id,其余 3 个是单列外键;22 张子表都带 owner 外键。所以一行数据不可能指向别的用户的项目或会话。
  • 删除与更新规则全部是 RESTRICT / NO ACTION,没有任何级联:被引用的行不能被删除。
  • 品牌库与共享身份库之间的关系(owner_id、asset_id、model_id + model_version_id)没有数据库外键,靠应用层检查;我们逐表核对了现有数据,没有孤儿。

主动破坏测试(在事务里尝试,全部回滚)

尝试数据库的反应
外键:导出任务.project_id 改成不存在的项目被拒绝拒绝
复合外键:项目素材引用.project_id 改成别的用户的项目被拒绝(owner 与 project 必须成对)拒绝
触发器:已提交的消息动作不可改写拒绝
触发器:媒体产出不可改写拒绝
外键 RESTRICT:被会话引用的项目不能删除拒绝
复合外键:被引用的会话不能把 owner 改成别人拒绝
检查约束:Run 状态只能是约定的六个值拒绝
触发器:账本分录不可修改拒绝
触发器:账本分录不可删除拒绝
验证范围的说明:这几个库里的数据量很小(每个品牌库约 70–270 行),孤儿检查能证明现有数据没有违反关系,不能证明大数据量下的表现;主动破坏测试在 Pollo 库上执行,另外六个品牌库的列、外键、索引、触发器已逐项核对与 Pollo 完全一致。
为什么这样设计外键用真实数据库约束表达,复合外键把 owner/project/conversation 一起带上,所以任何一行数据都不可能指向别人的项目。跨库关系不画外键而用应用层检查,是因为不同库之间数据库本身保证不了,画出来反而误导(决策 D12、D17)。

CreaTok · 表结构

MySQL 8,字符集 utf8mb4,ID 为 char(36) ascii_bin。点击展开 DDL。

按职责分组

项目与会话 2 张
  • studio_projects:用户拥有的创作项目
  • studio_conversations:项目内的对话会话
Agent执行与并发控制 7 张
  • maa_bindings:业务会话与Native Maa会话映射、模型版本与未决操作
  • maa_runs:公开Run映射、状态与原生/公开助手证据摘要
  • maa_message_actions:原消息动作及幂等回执
  • maa_interactions:澄清卡片及Run归属
  • maa_interaction_actions:原卡片回答/取消动作
  • maa_model_change_actions:带预期模型版本的切换动作
  • maa_run_stop_actions:停止原Run的幂等动作与观测终态
画布、时间轴与素材引用 5 张
  • studio_canvases:项目当前画布文档
  • studio_canvas_revisions:历史画布修订
  • studio_video_documents:项目当前时间轴文档
  • studio_timeline_revisions:历史时间轴修订
  • studio_project_asset_refs:项目对用户级素材的引用;不复制素材所有权
媒体生成、结算恢复与导出 6 张
  • studio_media_requests:用户输入/Agent来源、报价、批准摘要与准入确认
  • studio_media_jobs:已批准媒体任务及状态机
  • studio_media_submission_attempts:一次提交的任务证据与不确定结果
  • studio_media_outputs:受验证的输出与素材映射
  • studio_credit_recovery_intents:积分结算恢复意图;不是用户钱包全账本
  • studio_export_jobs:时间轴导出队列和发布输出
既有generation兼容路径 22 张
  • generation_jobs:既有generation任务,不等同新PublicMedia任务
  • generation_reservations:既有任务费用预留
  • generation_development_budgets:既有开发测试预算
  • generation_deliveries:既有生成交付回执
  • owner_id:归属用户;不是允许跨用户查询的参数。
  • model_revision:并发准入版本;数据库默认0不能代替Native权威读回。
  • pending_model_change_id:未决模型切换引用,阻止其他writer绕过。
  • studio_users:用户账号记录;不导出密码哈希或任何业务行
  • studio_sessions:登录会话;这里只公开字段结构,不公开会话值
  • studio_assets:用户级素材元数据与存储键结构,项目仅引用素材ID
  • studio_media_models:公开媒体模型目录结构
  • studio_media_model_versions:媒体模型不可变版本/计价能力结构
  • studio_subscriptions:订阅状态持久化
  • studio_billing_cycles:订阅账期
  • studio_payment_orders:订单记录
  • studio_payment_events:支付事件幂等记录;不代表已有公开支付路由
  • studio_credit_accounts:用户积分账户
  • studio_credit_lots:积分批次
  • studio_credit_authorizations:费用预留与结算授权
  • studio_credit_authorization_allocations:授权对批次的分配
  • studio_credit_ledger_entries:积分流水
  • studio_credit_plan_versions:积分方案不可变版本

品牌库 studio_profile_creatok(24 张)

generation_deliveries 既有生成交付回执
CREATE TABLE `generation_deliveries` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `maa_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `tool_name` varchar(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_delivery_owner_project` (`owner_id`,`project_id`,`session_id`,`job_id`),
  KEY `generation_delivery_owned_asset` (`owner_id`,`asset_id`),
  KEY `generation_delivery_session_fk` (`session_id`),
  CONSTRAINT `generation_delivery_job_fk` FOREIGN KEY (`job_id`) REFERENCES `generation_jobs` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`, `job_id`) REFERENCES `generation_jobs` (`owner_id`, `project_id`, `session_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_session_fk` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_model_tool` CHECK ((((`model` = _ascii'seedance-2.0') and (`tool_name` = _ascii'seedance2_video_generator')) or ((`model` in (_ascii'minimax-h3',_ascii'seedance-2.5')) and (`tool_name` = _ascii'insai_video_generate'))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_development_budgets 既有开发测试预算
CREATE TABLE `generation_development_budgets` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_version` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits_limit` bigint unsigned NOT NULL,
  `credits_reserved` bigint unsigned NOT NULL DEFAULT '0',
  `calls_limit` int unsigned NOT NULL,
  `calls_reserved` int unsigned NOT NULL DEFAULT '0',
  `expires_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_budget_owner` (`owner_id`,`id`),
  CONSTRAINT `generation_budget_limits` CHECK (((`credits_limit` between 1 and 1000000000) and (`credits_reserved` <= `credits_limit`) and (`calls_limit` between 1 and 100) and (`calls_reserved` <= `calls_limit`))),
  CONSTRAINT `generation_budget_policy` CHECK ((`policy_version` = _latin1'operator-test/v1'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_jobs 既有generation任务,不等同新PublicMedia任务
CREATE TABLE `generation_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_conversation_id` char(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_run_id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `result_bytes` mediumblob,
  `updated_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_job_budget_owner` (`id`,`budget_id`,`owner_id`),
  UNIQUE KEY `generation_job_owned_scope` (`owner_id`,`project_id`,`session_id`,`id`),
  KEY `generation_job_owner` (`owner_id`,`project_id`,`session_id`),
  KEY `generation_job_session` (`session_id`),
  KEY `generation_job_budget` (`owner_id`,`budget_id`),
  CONSTRAINT `generation_job_budget` FOREIGN KEY (`owner_id`, `budget_id`) REFERENCES `generation_development_budgets` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_project` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_session` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_attempt` CHECK ((((`status` = _ascii'APPROVED') and (`attempts` = 0) and (`result_bytes` is null)) or ((`status` = _ascii'SUBMITTING') and (`attempts` = 1) and (`result_bytes` is null)) or ((`status` in (_ascii'SUCCEEDED',_ascii'FAILED',_ascii'UNKNOWN')) and (`attempts` = 1) and (`result_bytes` is not null)))),
  CONSTRAINT `generation_job_credit` CHECK ((`credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_reservations 既有任务费用预留
CREATE TABLE `generation_reservations` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `calls` int unsigned NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_reservation_job` (`job_id`,`budget_id`,`owner_id`),
  CONSTRAINT `generation_reservation_job` FOREIGN KEY (`job_id`, `budget_id`, `owner_id`) REFERENCES `generation_jobs` (`id`, `budget_id`, `owner_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_reservation_fixed` CHECK (((`credits` between 1 and 1000000000) and (`calls` = 1) and (`status` = _latin1'HELD')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_bindings 业务会话与Native Maa会话映射、模型版本与未决操作
CREATE TABLE `maa_bindings` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `model_revision` bigint unsigned NOT NULL DEFAULT '0',
  `pending_model_change_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_binding_business_scope` (`owner_id`,`project_id`,`conversation_id`),
  UNIQUE KEY `maa_binding_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `maa_binding_native_identity` (`native_cid`),
  KEY `maa_model_change_pending_fk` (`owner_id`,`project_id`,`conversation_id`,`id`,`pending_model_change_id`),
  CONSTRAINT `maa_binding_owned_conversation_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_pending_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `id`, `pending_model_change_id`) REFERENCES `maa_model_change_actions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_binding_receipt` CHECK ((((`status` = _ascii'dispatching') and (`native_cid` is null)) or ((`status` = _ascii'bound') and (`native_cid` is not null)))),
  CONSTRAINT `maa_model_binding_revision` CHECK ((`model_revision` <= 9007199254740991))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interaction_actions 原卡片回答/取消动作
CREATE TABLE `maa_interaction_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `interaction_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `response_action` varchar(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `values_payload` json NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_action_business_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_interaction_action_one_card` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`interaction_id`),
  CONSTRAINT `maa_interaction_action_owned_card_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `interaction_id`) REFERENCES `maa_interactions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_interaction_action_kind` CHECK ((`response_action` in (_latin1'submit',_latin1'cancel'))),
  CONSTRAINT `maa_interaction_action_status` CHECK ((`status` in (_latin1'dispatching',_latin1'accepted')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interactions 澄清卡片及Run归属
CREATE TABLE `maa_interactions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_payload` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_native_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`native_iid`),
  UNIQUE KEY `maa_interaction_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`id`),
  CONSTRAINT `maa_interaction_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_message_actions 原消息动作及幂等回执
CREATE TABLE `maa_message_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `proposed_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_action_business_identity` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  KEY `maa_action_owned_run_fk` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_action_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_receipt` CHECK ((((`status` = _latin1'dispatching') and (`public_run_id` is null)) or ((`status` = _latin1'accepted') and (`public_run_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_model_change_actions 带预期模型版本的切换动作
CREATE TABLE `maa_model_change_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `expected_revision` bigint unsigned NOT NULL,
  `from_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `from_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_attempt` bigint unsigned NOT NULL,
  `receipt_payload` json DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_model_change_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_model_change_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  CONSTRAINT `maa_model_change_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_attempt` CHECK ((`dispatch_attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_model_change_expected` CHECK ((`expected_revision` <= 9007199254740990)),
  CONSTRAINT `maa_model_change_status` CHECK ((((cast(`status` as char charset binary) = _latin1'dispatching') and (`receipt_payload` is null)) or ((cast(`status` as char charset binary) in (_latin1'accepted',_latin1'rejected')) and (`receipt_payload` is not null) and (json_type(`receipt_payload`) = _latin1'OBJECT')))),
  CONSTRAINT `maa_model_change_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_run_stop_actions 停止原Run的幂等动作与观测终态
CREATE TABLE `maa_run_stop_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stop_action` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt` bigint unsigned NOT NULL,
  `terminal_run_status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_stop_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_stop_one_run` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_stop_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_stop_action` CHECK ((`stop_action` = _latin1'interrupt')),
  CONSTRAINT `maa_stop_attempt` CHECK ((`attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_stop_state` CHECK ((((`status` = _latin1'unknown') and (`terminal_run_status` is null)) or ((`status` = _latin1'terminal_observed') and (`terminal_run_status` is not null) and (`terminal_run_status` in (_latin1'completed',_latin1'failed',_latin1'cancelled'))))),
  CONSTRAINT `maa_stop_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_runs 公开Run映射、状态与原生/公开助手证据摘要
CREATE TABLE `maa_runs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `native_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `public_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `native_assistant_bytes` bigint unsigned NOT NULL DEFAULT '0',
  `assistant_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  `last_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_run_native_identity` (`native_rid`),
  UNIQUE KEY `maa_run_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  KEY `maa_run_owned_list` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  CONSTRAINT `maa_run_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_run_source_evidence` CHECK ((((`native_assistant_sha256` is null) and (`public_assistant_sha256` is null) and (`native_assistant_bytes` = 0) and (`assistant_event_seq` = 0)) or ((`native_assistant_sha256` is not null) and (`public_assistant_sha256` is not null) and (`native_assistant_bytes` > 0) and (`assistant_event_seq` > 0) and (`assistant_event_seq` <= `last_event_seq`)))),
  CONSTRAINT `maa_run_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'waiting_interaction',_latin1'completed',_latin1'failed',_latin1'cancelled')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvas_revisions 历史画布修订
CREATE TABLE `studio_canvas_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_canvas_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_canvas_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_canvases` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_canvas_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_canvas_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_canvas_revision_kind` CHECK ((`kind` = _latin1'canvas')),
  CONSTRAINT `studio_canvas_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_canvas_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_canvas_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvases 项目当前画布文档
CREATE TABLE `studio_canvases` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_canvas_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_canvas_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_conversations 项目内的对话会话
CREATE TABLE `studio_conversations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_conversation_owned_scope` (`owner_id`,`project_id`,`id`),
  KEY `studio_conversation_list` (`owner_id`,`project_id`,`created_at`,`id`),
  CONSTRAINT `studio_conversation_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_recovery_intents 积分结算恢复意图;不是用户钱包全账本
CREATE TABLE `studio_credit_recovery_intents` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `intent_bytes` mediumblob NOT NULL,
  `intent_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_intent_job_kind` (`job_id`,`kind`),
  KEY `credit_intent_recovery` (`state`,`updated_at`,`id`),
  KEY `credit_intent_owned_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `credit_intent_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `credit_intent_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_intent_kind` CHECK (((`kind` in (_latin1'RESERVE_BIND',_latin1'CAPTURE',_latin1'RELEASE')) and (((`kind` = _latin1'RESERVE_BIND') and (`authorization_id` is null)) or ((`kind` in (_latin1'CAPTURE',_latin1'RELEASE')) and (`authorization_id` is not null))))),
  CONSTRAINT `credit_intent_lease` CHECK ((((`state` = _latin1'leased') and (`lease_token_sha256` is not null) and regexp_like(`lease_token_sha256`,_latin1'^[a-f0-9]{64}$') and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`state` <> _latin1'leased') and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `credit_intent_payload_sha` CHECK ((`intent_sha256` = lower(sha2(`intent_bytes`,256)))),
  CONSTRAINT `credit_intent_payload_size` CHECK ((length(`intent_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_intent_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_intent_state` CHECK ((`state` in (_latin1'pending',_latin1'leased',_latin1'confirmed',_latin1'unknown'))),
  CONSTRAINT `credit_intent_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_export_jobs 时间轴导出队列和发布输出
CREATE TABLE `studio_export_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `document_revision` bigint unsigned NOT NULL,
  `input_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_json` json NOT NULL,
  `output_json` json DEFAULT NULL,
  `error_code` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
  `lease_token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_export_input` (`project_id`,`document_revision`,`input_hash`),
  KEY `studio_export_claim` (`status`,`created_at`,`id`),
  KEY `studio_export_owner_list` (`owner_id`,`project_id`,`created_at`,`id`),
  KEY `studio_export_expiry` (`status`,`lease_until`),
  CONSTRAINT `studio_export_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_export_lease` CHECK ((((`status` = _latin1'running') and (`lease_token_hash` is not null) and (`lease_until` is not null)) or ((`status` <> _latin1'running') and (`lease_token_hash` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_export_output` CHECK ((((`status` = _latin1'succeeded') and (`output_json` is not null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'queued',_latin1'running')) and (`output_json` is null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'failed',_latin1'unknown')) and (`output_json` is null) and (`error_code` <> _latin1'')))),
  CONSTRAINT `studio_export_revision` CHECK ((`document_revision` between 1 and 2147483647)),
  CONSTRAINT `studio_export_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'succeeded',_latin1'failed',_latin1'unknown')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_jobs 已批准媒体任务及状态机
CREATE TABLE `studio_media_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_approval_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `approval_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credit_authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_mode` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_expires_at` datetime(6) DEFAULT NULL,
  `status` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `client_cancel_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_requested_at` datetime(6) DEFAULT NULL,
  `staged_asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `staged_asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_job_request` (`request_id`),
  UNIQUE KEY `studio_media_job_client` (`owner_id`,`client_approval_id`),
  UNIQUE KEY `studio_media_job_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_job_owned_request` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`),
  KEY `studio_media_job_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_job_queue` (`status`,`updated_at`,`id`),
  KEY `studio_media_job_request_fk` (`owner_id`,`project_id`,`conversation_id`,`request_id`,`request_sha256`,`quote_sha256`),
  CONSTRAINT `studio_media_job_request_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `request_id`, `request_sha256`, `quote_sha256`) REFERENCES `studio_media_requests` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`, `quote_sha256`),
  CONSTRAINT `studio_media_job_authorization` CHECK ((((`credit_authorization_id` is null) and (`authorization_mode` is null) and (`authorization_expires_at` is null) and (`status` in (_latin1'approval_pending',_latin1'cancelled'))) or ((`credit_authorization_id` is not null) and (`authorization_mode` is not null) and (`authorization_mode` in (_latin1'commercial',_latin1'test_allowance')) and (`authorization_expires_at` is not null) and (`authorization_expires_at` > `created_at`) and (`status` <> _latin1'approval_pending')))),
  CONSTRAINT `studio_media_job_cancel` CHECK ((((`client_cancel_id` is null) and (`cancel_request_sha256` is null) and (`cancel_requested_at` is null)) or ((`client_cancel_id` is not null) and (`cancel_request_sha256` is not null) and (`cancel_requested_at` is not null) and (`cancel_requested_at` between `created_at` and `updated_at`)))),
  CONSTRAINT `studio_media_job_lease` CHECK ((((`status` in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is not null) and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`status` not in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_media_job_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_job_staged` CHECK ((((`staged_asset_id` is null) and (`staged_asset_sha256` is null)) or ((`staged_asset_id` is not null) and (`staged_asset_sha256` is not null)))),
  CONSTRAINT `studio_media_job_status` CHECK ((`status` in (_latin1'approval_pending',_latin1'queued',_latin1'leased',_latin1'submitting',_latin1'running',_latin1'submission_unknown',_latin1'succeeded',_latin1'failed',_latin1'cancelled'))),
  CONSTRAINT `studio_media_job_task` CHECK (((`status` not in (_latin1'running',_latin1'succeeded')) or (`provider_task_id` is not null))),
  CONSTRAINT `studio_media_job_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_outputs 受验证的输出与素材映射
CREATE TABLE `studio_media_outputs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `ordinal` int unsigned NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_result_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_output_job` (`job_id`,`ordinal`),
  KEY `studio_media_output_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`),
  CONSTRAINT `studio_media_output_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`),
  CONSTRAINT `studio_media_output_ordinal` CHECK ((`ordinal` = 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_requests 用户输入/Agent来源、报价、批准摘要与准入确认
CREATE TABLE `studio_media_requests` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_bytes` mediumblob NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quoted_credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_snapshot_bytes` mediumblob,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_request_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_request_quoted` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`,`quote_sha256`),
  UNIQUE KEY `studio_media_request_client` (`owner_id`,`project_id`,`conversation_id`,`client_request_id`),
  KEY `studio_media_request_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_request_source_fk` (`owner_id`,`project_id`,`conversation_id`,`source_binding_id`,`source_run_id`),
  CONSTRAINT `studio_media_request_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`),
  CONSTRAINT `studio_media_request_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `source_binding_id`, `source_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`),
  CONSTRAINT `studio_media_quote_credits` CHECK ((`quoted_credits` between 1 and 1000000000)),
  CONSTRAINT `studio_media_quote_sha` CHECK ((`quote_sha256` = lower(sha2(`quote_bytes`,256)))),
  CONSTRAINT `studio_media_quote_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)))),
  CONSTRAINT `studio_media_request_origin` CHECK ((((`origin` in (_latin1'ui',_latin1'user_edit')) and (`source_binding_id` is null) and (`source_run_id` is null) and (`source_assistant_sha256` is null) and (`source_snapshot_bytes` is null)) or ((`origin` = _latin1'agent') and (`source_binding_id` is not null) and (`source_run_id` is not null) and (`source_assistant_sha256` is not null) and (`source_snapshot_bytes` is not null) and (length(`source_snapshot_bytes`) between 1 and 65536) and (`source_assistant_sha256` = lower(sha2(`source_snapshot_bytes`,256)))))),
  CONSTRAINT `studio_media_request_sha` CHECK ((`request_sha256` = lower(sha2(`request_bytes`,256)))),
  CONSTRAINT `studio_media_request_size` CHECK (((length(`request_bytes`) between 1 and 65536) and (length(`quote_bytes`) between 1 and 65536)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_submission_attempts 一次提交的任务证据与不确定结果
CREATE TABLE `studio_media_submission_attempts` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt_no` int unsigned NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stage` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_started_at` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `terminal_receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `reconciliation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_attempt_job` (`job_id`,`attempt_no`),
  UNIQUE KEY `studio_media_attempt_idempotency` (`provider_idempotency_key`),
  KEY `studio_media_attempt_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `studio_media_attempt_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `studio_media_attempt_number` CHECK ((`attempt_no` = 1)),
  CONSTRAINT `studio_media_attempt_state` CHECK ((((`stage` = _latin1'prepared') and (`dispatch_started_at` is null) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'dispatching',_latin1'unknown')) and (`dispatch_started_at` is not null) and (`dispatch_started_at` >= `created_at`) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` = _latin1'acknowledged') and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'completed',_latin1'failed')) and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is not null)) or ((`stage` = _latin1'reconciled_not_sent') and (`dispatch_started_at` is not null) and (`provider_task_id` is null) and (`reconciliation_sha256` is not null)))),
  CONSTRAINT `studio_media_attempt_times` CHECK (((`updated_at` >= `created_at`) and ((`dispatch_started_at` is null) or (`dispatch_started_at` between `created_at` and `updated_at`))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_project_asset_refs 项目对用户级素材的引用;不复制素材所有权
CREATE TABLE `studio_project_asset_refs` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `detached_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`project_id`,`asset_id`),
  KEY `studio_project_asset_owned_page` (`owner_id`,`project_id`,`asset_id`),
  KEY `studio_project_asset_reverse` (`owner_id`,`asset_id`,`project_id`),
  CONSTRAINT `studio_project_asset_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_project_asset_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_project_asset_state` CHECK ((((`state` = _latin1'attached') and (`detached_at` is null)) or ((`state` = _latin1'detached') and (`detached_at` is not null) and (`detached_at` = `updated_at`)))),
  CONSTRAINT `studio_project_asset_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_projects 用户拥有的创作项目
CREATE TABLE `studio_projects` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_project_owner_identity` (`owner_id`,`id`),
  KEY `studio_project_owner_list` (`owner_id`,`created_at`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_timeline_revisions 历史时间轴修订
CREATE TABLE `studio_timeline_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_timeline_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_timeline_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_video_documents` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_timeline_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_timeline_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_timeline_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_timeline_revision_kind` CHECK ((`kind` = _latin1'timeline')),
  CONSTRAINT `studio_timeline_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_timeline_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_timeline_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_video_documents 项目当前时间轴文档
CREATE TABLE `studio_video_documents` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_timeline_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_document_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_document_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
共享身份库(15 张)与积分账本 七个品牌相同,展开查看
studio_assets
CREATE TABLE `studio_assets` (
  `id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `storage_key` varchar(512) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `publication_state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT 'visible',
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_asset_owner_digest` (`owner_id`,`sha256`),
  KEY `studio_asset_owner_list` (`owner_id`,`created_at`,`id`),
  KEY `studio_asset_visible_list` (`owner_id`,`publication_state`,`created_at`,`id`),
  CONSTRAINT `studio_asset_publication_state` CHECK (CAST(publication_state AS BINARY) IN ('pending','visible')),
  CONSTRAINT `studio_asset_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_billing_cycles
CREATE TABLE `studio_billing_cycles` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `payment_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_invoice_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `billing_cycle_owned` (`owner_id`,`id`),
  UNIQUE KEY `billing_cycle_period` (`subscription_id`,`period_start`),
  UNIQUE KEY `billing_cycle_invoice` (`provider_key`,`provider_account`,`provider_invoice_id`),
  UNIQUE KEY `billing_cycle_order` (`payment_order_id`),
  KEY `billing_cycle_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  KEY `billing_cycle_order_fk` (`owner_id`,`payment_order_id`,`plan_version_id`),
  KEY `billing_cycle_order_provider_fk` (`owner_id`,`payment_order_id`,`provider_key`,`provider_account`),
  CONSTRAINT `billing_cycle_order_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `plan_version_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `plan_version_id`),
  CONSTRAINT `billing_cycle_order_provider_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_amount` CHECK ((`grant_credits` between 1 and 1000000000)),
  CONSTRAINT `billing_cycle_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `billing_cycle_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `billing_cycle_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `billing_cycle_state` CHECK (((`state` in (_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `billing_cycle_times` CHECK (((`period_end` > `period_start`) and (`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_accounts
CREATE TABLE `studio_credit_accounts` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `unit` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`),
  CONSTRAINT `credit_account_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `credit_account_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_account_status` CHECK ((`status` in (_latin1'open',_latin1'suspended'))),
  CONSTRAINT `credit_account_unit` CHECK ((`unit` = _latin1'credits'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorization_allocations
CREATE TABLE `studio_credit_authorization_allocations` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `allocated_credits` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`authorization_id`,`lot_id`),
  UNIQUE KEY `credit_allocation_owned` (`owner_id`,`authorization_id`,`lot_id`),
  KEY `credit_allocation_lot` (`owner_id`,`lot_id`,`authorization_id`),
  CONSTRAINT `credit_allocation_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_amount` CHECK ((`allocated_credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorizations
CREATE TABLE `studio_credit_authorizations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `profile` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `reservation_bytes` mediumblob NOT NULL,
  `reservation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_bytes` mediumblob NOT NULL,
  `policy_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_authorization_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_authorization_job` (`owner_id`,`profile`,`job_id`),
  CONSTRAINT `credit_authorization_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_authorization_amount` CHECK ((`credits` between 1 and 1000000000)),
  CONSTRAINT `credit_authorization_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_authorization_policy_sha` CHECK ((`policy_sha256` = lower(sha2(`policy_bytes`,256)))),
  CONSTRAINT `credit_authorization_policy_size` CHECK ((length(`policy_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_profile` CHECK ((`profile` in (_latin1'topview',_latin1'pollo',_latin1'invideo',_latin1'libtv',_latin1'flova',_latin1'creatok',_latin1'creatify'))),
  CONSTRAINT `credit_authorization_reservation_sha` CHECK ((`reservation_sha256` = lower(sha2(`reservation_bytes`,256)))),
  CONSTRAINT `credit_authorization_reservation_size` CHECK ((length(`reservation_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_authorization_state` CHECK ((`state` in (_latin1'HELD',_latin1'CAPTURED',_latin1'RELEASED'))),
  CONSTRAINT `credit_authorization_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_ledger_entries
CREATE TABLE `studio_credit_ledger_entries` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `operation_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_delta` bigint NOT NULL,
  `held_delta` bigint NOT NULL,
  `captured_delta` bigint NOT NULL,
  `facts_bytes` mediumblob NOT NULL,
  `facts_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_entry_operation` (`owner_id`,`operation_key`,`lot_id`),
  KEY `credit_entry_page` (`owner_id`,`created_at`,`id`),
  KEY `credit_entry_lot_fk` (`owner_id`,`lot_id`),
  KEY `credit_entry_allocation_fk` (`owner_id`,`authorization_id`,`lot_id`),
  CONSTRAINT `credit_entry_allocation_fk` FOREIGN KEY (`owner_id`, `authorization_id`, `lot_id`) REFERENCES `studio_credit_authorization_allocations` (`owner_id`, `authorization_id`, `lot_id`),
  CONSTRAINT `credit_entry_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_delta` CHECK ((((`kind` = _latin1'grant') and (`authorization_id` is null) and (`grant_delta` between 1 and 1000000000) and (`held_delta` = 0) and (`captured_delta` = 0)) or ((`kind` = _latin1'hold') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between 1 and 1000000000) and (`captured_delta` = 0)) or ((`kind` = _latin1'capture') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`captured_delta` between 1 and 1000000000) and (`held_delta` = -(`captured_delta`))) or ((`kind` = _latin1'release') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between -(1000000000) and -(1)) and (`captured_delta` = 0)))),
  CONSTRAINT `credit_entry_facts_sha` CHECK ((`facts_sha256` = lower(sha2(`facts_bytes`,256)))),
  CONSTRAINT `credit_entry_facts_size` CHECK ((length(`facts_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_entry_operation_key` CHECK ((((`kind` = _latin1'grant') and (`operation_key` = concat(`lot_id`,_latin1':grant'))) or ((`kind` <> _latin1'grant') and (`operation_key` = concat(`authorization_id`,_latin1':',`kind`)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_lots
CREATE TABLE `studio_credit_lots` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `held_credits` bigint unsigned NOT NULL,
  `captured_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_cycle_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_lot_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_lot_order` (`source_order_id`),
  UNIQUE KEY `credit_lot_cycle` (`source_cycle_id`),
  KEY `credit_lot_eligible` (`owner_id`,`expires_at`,`id`),
  KEY `credit_lot_order_fk` (`owner_id`,`source_order_id`),
  KEY `credit_lot_cycle_fk` (`owner_id`,`source_cycle_id`),
  CONSTRAINT `credit_lot_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_lot_cycle_fk` FOREIGN KEY (`owner_id`, `source_cycle_id`) REFERENCES `studio_billing_cycles` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_order_fk` FOREIGN KEY (`owner_id`, `source_order_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_counters` CHECK (((`grant_credits` between 1 and 1000000000) and (`held_credits` <= 1000000000) and (`captured_credits` <= 1000000000) and ((`held_credits` + `captured_credits`) <= `grant_credits`))),
  CONSTRAINT `credit_lot_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_lot_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_lot_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_lot_source` CHECK ((((`source_order_id` is not null) and (`source_cycle_id` is null)) or ((`source_order_id` is null) and (`source_cycle_id` is not null)))),
  CONSTRAINT `credit_lot_times` CHECK (((`expires_at` > `valid_from`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_plan_versions
CREATE TABLE `studio_credit_plan_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `credit_validity_seconds` int unsigned NOT NULL,
  `billing_interval_months` int unsigned NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  `retired_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_plan_version_key` (`plan_key`,`version_key`),
  CONSTRAINT `credit_plan_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `credit_plan_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `credit_plan_duration` CHECK (((`credit_validity_seconds` between 1 and 31622400) and ((`retired_at` is null) or (`retired_at` >= `published_at`)))),
  CONSTRAINT `credit_plan_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_plan_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_plan_kind` CHECK (((`kind` in (_latin1'subscription',_latin1'topup')) and (((`kind` = _latin1'subscription') and (`billing_interval_months` in (1,3,12))) or ((`kind` = _latin1'topup') and (`billing_interval_months` = 0)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_model_versions
CREATE TABLE `studio_media_model_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `config_bytes` mediumblob NOT NULL,
  `config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_version_key` (`model_id`,`version_key`),
  UNIQUE KEY `studio_media_version_owned_model` (`model_id`,`id`),
  CONSTRAINT `studio_media_version_model_fk` FOREIGN KEY (`model_id`) REFERENCES `studio_media_models` (`id`),
  CONSTRAINT `studio_media_version_sha` CHECK ((`config_sha256` = lower(sha2(`config_bytes`,256)))),
  CONSTRAINT `studio_media_version_size` CHECK ((length(`config_bytes`) between 1 and 65536))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_models
CREATE TABLE `studio_media_models` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `display_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_model_key` (`model_key`),
  KEY `studio_media_model_published_fk` (`id`,`published_version_id`),
  CONSTRAINT `studio_media_model_published_fk` FOREIGN KEY (`id`, `published_version_id`) REFERENCES `studio_media_model_versions` (`model_id`, `id`),
  CONSTRAINT `studio_media_model_kind` CHECK ((`kind` = _ascii'video')),
  CONSTRAINT `studio_media_model_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_model_status` CHECK (((`status` in (_ascii'draft',_ascii'active',_ascii'disabled')) and ((`status` <> _ascii'active') or (`published_version_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_events
CREATE TABLE `studio_payment_events` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_event_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `body_bytes` mediumblob NOT NULL,
  `body_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_event_provider` (`provider_key`,`provider_account`,`provider_event_id`),
  KEY `payment_event_order_fk` (`owner_id`,`order_id`,`provider_key`,`provider_account`),
  KEY `payment_event_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  CONSTRAINT `payment_event_order_fk` FOREIGN KEY (`owner_id`, `order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_event_body_sha` CHECK ((`body_sha256` = lower(sha2(`body_bytes`,256)))),
  CONSTRAINT `payment_event_body_size` CHECK ((length(`body_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_event_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_event_state` CHECK ((`state` in (_latin1'VERIFIED',_latin1'PROCESSED'))),
  CONSTRAINT `payment_event_subject` CHECK (((`order_id` is not null) or (`subscription_id` is not null))),
  CONSTRAINT `payment_event_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_orders
CREATE TABLE `studio_payment_orders` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_order_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_order_owned` (`owner_id`,`id`),
  UNIQUE KEY `payment_order_owned_plan` (`owner_id`,`id`,`plan_version_id`),
  UNIQUE KEY `payment_order_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `payment_order_client` (`owner_id`,`client_action_id`),
  UNIQUE KEY `payment_order_provider_key` (`provider_key`,`provider_account`,`provider_idempotency_key`),
  UNIQUE KEY `payment_order_provider_id` (`provider_key`,`provider_account`,`provider_order_id`),
  KEY `payment_order_plan_fk` (`plan_version_id`),
  CONSTRAINT `payment_order_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `payment_order_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_order_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `payment_order_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `payment_order_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `payment_order_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_order_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_order_state` CHECK (((`state` in (_latin1'CREATING',_latin1'CREATE_UNKNOWN',_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`provider_order_id` is not null) and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `payment_order_times` CHECK (((`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_sessions
CREATE TABLE `studio_sessions` (
  `token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `user_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `revoked_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`token_hash`),
  KEY `studio_session_expiry` (`expires_at`),
  KEY `studio_session_user` (`user_id`,`expires_at`),
  CONSTRAINT `studio_session_user_fk` FOREIGN KEY (`user_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_session_times` CHECK ((`expires_at` > `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_subscriptions
CREATE TABLE `studio_subscriptions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_subscription_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `cancel_at_period_end` tinyint(1) NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `subscription_owned` (`owner_id`,`id`),
  UNIQUE KEY `subscription_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `subscription_provider` (`provider_key`,`provider_account`,`provider_subscription_id`),
  KEY `subscription_plan_fk` (`plan_version_id`),
  CONSTRAINT `subscription_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `subscription_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `subscription_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `subscription_state` CHECK (((`state` in (_latin1'PENDING',_latin1'ACTIVE',_latin1'PAST_DUE',_latin1'CANCELLED',_latin1'ENDED')) and (`cancel_at_period_end` in (0,1)))),
  CONSTRAINT `subscription_times` CHECK (((`period_end` > `period_start`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_users
CREATE TABLE `studio_users` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `email` varchar(254) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `password_hash` varbinary(72) NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT '1',
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_user_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

积分账本链路

plan_versions → payment_orders(CREATING→PAID)→ payment_events(VERIFIED)→ credit_lots(带适用模型与品牌)→ ledger_entries(grant / hold / capture / release,只追加)→ authorizations / allocations。批准生成时按批次分配冻结额度,扣费与释放都有幂等键。
为什么这样设计账本只追加、用触发器禁止修改,才有可信的对账;批准时按批次分配冻结额度,才能在失败时准确释放。约束写在数据库里而不是应用里,是因为应用会有 bug,而约束不会(决策 D3、D17)。

CreaTok · 接口清单

共 55 个公开接口,取自源码路由盘点。路径都以 /v1 开头;写操作需要会话 Cookie、可信 Host/Origin 与 CSRF;带品牌的路径用 ?profile=creatok;共享的登录、素材、模型目录路径不接受 profile 参数。

Agent/统一动作 7 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactionsrunInteractions200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactions/{interaction_id}/responsesrespondInteractionclient_action_id, card_sha256, action, values202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/interaction-actions/{client_action_id}interactionAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changeschangeConversationModelclient_action_id, expected_revision, agent_model, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changes/{client_action_id}modelChangeAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stopstopRunclient_action_id, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/stop-actions/{client_action_id}stopAction200
Asset 4 个
方法路径operationId请求字段成功
GET/v1/assetslistAssets200
POST/v1/assetsuploadAssetfile201
GET/v1/assets/{asset_id}getAsset200
GET/v1/assets/{asset_id}/contentassetContent200/206
LoginSession 1 个
方法路径operationId请求字段成功
POST/v1/auth/logoutlogout200
MaaBinding 5 个
方法路径operationId请求字段成功
GET/v1/maa/model-optionsmaaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/messagessendMaaMessageclient_action_id, content202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/actions/{client_action_id}getMaaAction200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stategetMaaState200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/eventsmaaEvents200
Media 11 个
方法路径operationId请求字段成功
GET/v1/media/modelspublicMediaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requestscreateMediaRequestclient_request_id, model_id, model_version_id, prompt, duration_seconds, ratio, resolution, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requestsmediaRequestByClient200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}getMediaRequest200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}getMediaJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}/outputgetMediaOutput200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/jobmediaJobByRequest200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/approveapproveMediaRequestclient_approval_id, request_sha256, quote_sha256200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/from-maacreateMediaRequestFromMaainput, source_run_id, source_assistant_sha256, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobslistMediaJobs200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/intentslistMediaIntents200
Other 9 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/canvas/revisionslistCanvasRevisions200
GET/v1/projects/{project_id}/canvas/revisions/{revision}getCanvasRevision200
POST/v1/projects/{project_id}/canvas/revisions/{revision}/restorerestoreCanvasRevisionexpected_revision200
GET/v1/projects/{project_id}/document/revisionslistTimelineRevisions200
GET/v1/projects/{project_id}/document/revisions/{revision}getTimelineRevision200
POST/v1/projects/{project_id}/document/revisions/{revision}/restorerestoreTimelineRevisionexpected_revision200
GET/v1/projects/{project_id}/assetslistProjectAssets200
POST/v1/projects/{project_id}/assetsattachProjectAssetasset_id, expected_revision200
POST/v1/projects/{project_id}/assets/{asset_id}/detachdetachProjectAssetexpected_revision200
Project 15 个
方法路径operationId请求字段成功
GET/v1/projectslistProjects200
POST/v1/projectscreateProjecttitle201
GET/v1/projects/{project_id}getProject200
GET/v1/projects/{project_id}/conversationslistConversations200
POST/v1/projects/{project_id}/conversationscreateConversationtitle201
GET/v1/projects/{project_id}/conversations/{conversation_id}getConversation200
GET/v1/projects/{project_id}/canvasgetCanvas200
POST/v1/projects/{project_id}/canvassaveCanvasexpected_revision, state200
GET/v1/projects/{project_id}/documentgetDocument200
POST/v1/projects/{project_id}/documentsaveDocumentexpected_revision, state200
GET/v1/projects/{project_id}/export-jobslistExportJobs200
POST/v1/projects/{project_id}/export-jobsqueueExportdocument_revision, output?202
GET/v1/projects/{project_id}/export-jobs/{job_id}getExportJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindinggetMaaBinding200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindingbindMaaagent_model XOR use_default:true200
User 3 个
方法路径operationId请求字段成功
POST/v1/auth/registerregisteremail, password201
POST/v1/auth/loginloginemail, password200
GET/v1/auth/sessionsession200

CreaTok 自己公开的接口调用流程(来自其官方资料)

步骤调用关系操作证据
面板提交请求扩展面板 → 扩展后台chrome.runtime消息API_REQUEST携带path/method/body/query/headers;流请求API_STREAM_REQUEST另带streamId。C
读取账号上下文扩展后台 → Web入口GET https://www.creatok.ai/api/extension/me;fetch credentials:include;客户端处理code===0/data与ApiError。C
发起视觉分析扩展后台 → Web入口POST /api/extension/tiktok-transcript-generator/decompose-video-script;body含video_id、locale、analysis_contract:visual_v1;Accept:text/event-stream。C
中继分析事件扩展后台 → 扩展面板后台读取response.body并发送API_STREAM_EVENT;payload含streamId、type:chunk|done|error;UI按空行解析data JSON的partial/final/error/done。C
保存收藏身份扩展后台 → Web入口POST /api/extension/collections/posts;提交submittedUrl、contentType、postSnapshot、locale等;返回post.uid与platformPostId。C
导入商品资料扩展后台 → Web入口POST /api/extension/products/analyze-selling-points与/api/extension/products;来源platform/externalProductId、卖点与已选media[]分别提交。C
批准脚本并生成创作界面 → Web入口官方Web Agent先给脚本和生成方案,用户批准后进入生成;对应HTTP方法、URL、body与事件格式未取得。W
画布局部重跑创作界面 → Web入口官方Canvas支持节点组合与局部重跑;节点保存、连线编辑、版本、运行接口均未核实。W
排期发布与回看Web入口 → 发布平台官方流程为TikTok授权、商品锚点/预检、立即或排期发布、状态与表现回收;具体TikTok调用与内部发布API合同未知。W
为什么这样设计所有写动作都带客户端幂等键(client_action_id / client_request_id / client_approval_id),重复提交返回原回执;报价和批准分成两个接口;停止、切换模型、澄清卡回答都是「动作」而不是「状态修改」,所以每一步都能查回执。这样前端可以放心地重试,后端也不会因为重试产生第二次副作用(决策 D2、D8、D14)。

CreaTok · 代码与下载

creatok-replica.zip
7.6 MB · 53 个文件 · SHA-256 e44a352895b04785e4e911274fe112e2d69d54e435792cc2d586199273ac9400
下载:creatok-replica.zip · 后端与合同 · 全部代码
在线试用:http://119.23.106.60:8106/。测试环境,整套独立部署(前端、BFF、Maa 智能体、数据库),可以注册、对话、生成视频(Seedance,测试积分)。页面右下角有“内部复刻研究”标识。
目录内容
frontend/CreaTok 前端:<code>candidate-8875-unified-v22.html</code> 及它引用的全部脚本与样式
contracts/本品牌的表结构 SQL、ER(svg / mmd)、外键清单;接口目录(json / csv);共享身份库表结构
README.md怎么运行、需要自备什么

运行前端

python3 -m http.server -d frontend 8791
# 打开 http://127.0.0.1:8791/agent-integration/apps-production-brand-v14/creatok/candidate-8875-unified-v22.html
后端和全部品牌:Go 后端、Python 媒体桥、全部测试和 7 个品牌的合同在 backend-and-contracts.zip;all-brands-code.zip 是全部。包里没有任何凭据、数据库备份或媒体文件,已做敏感串扫描。
需自备:MySQL 与账号(按 contracts 建库)、Maa 智能体服务(MAA_AGENT_ROOT)、模型网关与对象存储配置。静态前端单独打开不需要后端,但登录、对话、生成、导出需要整套服务。

复刻完成度(审计 2026-10-19)

结论:不是完美复刻。官方资料里的 45 项能力:实测跑通 1、只有通用对话 3、后端有但没跑过 2、没有后端 39。

真正跑通的:Canvas 项目管理。

界面对照(工作台首页(已登录)):真实页面采集到 65 个控件 / 标题文字,复刻页找到 55 个。缺的有:爆款趋势 图文、视频/图文发布 CLI、灵感库 New、Skills/CLI、Google Chrome、客服、本地演示账号 个人空间、快速生成 精准编辑、限时优惠 8 折、限时优惠 9 折。

逐个点击(已登录首页):78 个可点控件,点了有效 4 个,只提示“此功能正在接入” 64 个,没反应或只变样式 2 个,禁用 4 个,未点(发送 / 删除 / 退出等)1 个。

只提示“正在接入”的控件(64 次点击,57 种)

视频分析、爆款趋势 ×2、视频 ×3、图片 ×2、音频、工具、视频/图文发布、灵感库 ×2、Toggle Sidebar、反馈、限时优惠、带货图文,从灵感到发布、Nano Banana 2.1、Chrome 扩展、略胜 Nano 2、下一张 ×2、Agent、添加附件、技能、每日 Agent 额度、拆解视频/图文、复刻视频、创作带货视频、视频转提示词、复刻带货图文、创作带货图文、爆款复刻 ×2、提示词反推、去字幕、画质提升、商品套图、A+内容、去水印、图片去背景、文字转语音(TTS)、TikTok 脚本提取、图片翻译、图片复刻、精选、资产库、商品库、图文、超高货车广告、概念与成品、Seedance 提示词、荒诞钩子、H3 提示词、子弹时间广告、一键创作、蛛丝牵引产品、穿搭种草、展开更多、带货图文复刻、图文带货、电商图提示词、商品套图策划、辛普森轮播

点了没反应或只变样式的控件(2 次点击,2 种)

选择项目 / 选择会话、首页

复刻页自报的绑定状态(页面内嵌的绑定清单):原生工作区控件(新建项目、会话、登录等)9 / 10 个没有绑定到原页面的控件,由统一的工作区条代替;路由 projects、timeline 缺失或只做了一部分;

首屏截图对照(左:真实产品 creatok/source-dashboard-safe.png;右:复刻页)· 粗粒度差异 66%

真实截图是测试账号登录后的画面,内容(作品、项目、横幅)本来就不同,差异百分比只作参考,重点看布局。

CreaTok:左为真实产品截图,右为复刻页

Creatify · 总览

重点Creatify 看这三行就够
  • 它是什么:URL → 视频,入口最短;Agent 交互:入口多,内核同一套。
  • 我们借鉴:多个入口汇成同一个“创作意图”(决策 D11)。
  • 复刻现状:5 项已验证,在线试用 http://119.23.106.60:8107/;后端与其余六个品牌共用一套(24 张表、55 个接口),所以 ER / 表结构 / 接口页的通用部分默认折叠。

广告素材生产、AdFlow 节点流程、效果 Agent 与投放集成共同构成广告闭环;同时提供独立的公开生成 API。

证据:W 官方说明,A 官方接口合同,C 客户端代码,O 实测。“未实测”= 只核对了官方页面,没有上传素材或生成。

能力目录
45 项,分 12 类
入口
首页 + 创作工作台
复刻后端
24 张表 · 34 个外键 · 85 个索引

能力目录

智能 Agent 与 Skill 3 项
能力说明证据限制
创作 Co-Pilot 与计划确认对话讨论角度、分镜和理由,确认后搭建画布。W未观察主 Agent 协议与会话持久化
Performance Agent 与预设营销任务对话分析投放,提供审计、本地化和创意评估入口。W操作审核方式、权限和预算保护未实测;预设 prompt 与内部技能标签不是用户 Skill 发布系统
广告脚本候选及自写脚本生成多个文案候选或采用自写脚本。W脚本审核与版本规则未测
项目与会话 4 项
能力说明证据限制
品牌空间把不同品牌的工作上下文分开。W空间内实体与跨空间素材规则未知
团队工作区及权限通过 Admin/Editor/Viewer 管理成员访问。W帮助页较旧;不采用旧席位配额,权限执行未测
AdFlow 节点编排与局部迭代连接制作步骤,并替换单个模型或部件。W未实测节点执行协议与类型约束;受依赖步骤如何失效、是否自动重跑未知
流程分支、复用及团队回放从共同基础建立变体并回看生成过程。W不是数据库不可变快照或 Git 分支证明
素材与商品 4 项
能力说明证据限制
商品 URL 导入提取商品资料和媒体用于广告制作。A外层 Link ID 与嵌套商品 ID 不应混用
模型参数 Schema 查询查询模型输入定义再构造生成参数。A未核实 Web 是否动态使用相同 schema
异步生成、历史查询与回调查询任务历史和进度,或通过回调接收结果。A回调重试/签名与任务超时未实测;创建时间筛选不是状态变化时间;没有用户数据调用
Smart Assets 素材容量工作区具备素材保存容量权益。W文件类型、删除回收和个人/团队归属未知
视频生成 5 项
能力说明证据限制
多风格预览后选成片异步生成预览,选择一个 media_job 渲染。A预览与成片是不同步骤;没有实际调用
商品图片转广告视频从商品图片制作演示或广告片段。W没有生成或投放,成本、效果与运行时数据未知
文图及参考素材生成视频由提示词、图片或参考媒体制作片段。A视频编辑、首尾帧和声音参数依具体模型
批量广告变体组合多个脚本、视觉风格和演员。W最大批次、部分失败与耗费未知
Ad Clone用参考广告链接制作新的广告版本。W输入支持范围、重构步骤未测试
声音与人物 7 项
能力说明证据限制
AI 演员库选择预置人物用于口播广告。W演员数量随套餐,不以宣传总数当账号可用数
自定义人物 Avatar从自己的素材建立自定义人物。W输入素材及制作周期按具体入口;未提交真人素材
品牌 AI Influencer制作可持续使用的品牌虚拟人物。W跨作品一致性只为官方说明,未测
多场景口播与透明人物输出组合人物口播场景,API 可输出透明 WebM。A具体 API 文档已读;Web 对应入口与背景/格式权限未执行
文字配音、音色与语种选择声音、语言及语音表达生成配音。W语种与音色随产品和套餐,未测音频输出
音乐生成按音乐需求生成歌曲或器乐素材。A单个音乐模型页本次工具无法取得;只确认任务类别与官方目录,不推测时长/价格
声音克隆与克隆音色管理从音频样本建立可复用的 voice ID。A提供方决定额度及模型适用,未将接口配额外推 Web 套餐
图片生成 2 项
能力说明证据限制
AI 图片素材生成与编辑制作或编辑广告图,Web Image Ad 支持商品 URL。WWeb URL-to-Image 仅官方文档说明;API 图片适配接收已有图,二者不相同
IAB 多尺寸广告图适配以现有广告图重绘多种标准横幅。A不是像素级缩放;API 不含 Web 商品 URL 转图流程
编辑与导出 3 项
能力说明证据限制
脚本、人物与 B-roll 编辑修改脚本、配音、演员和画面组合。W免费编辑和付费最终生成应分开理解
字幕、音乐、贴纸与 CTA编辑字幕及广告画面装饰和行动引导。W是否有独立多轨时间轴未核实
画幅调整、下载和成片导出调整展示比例并导出可发布媒体。W分辨率、工程导出和各分享路径未知
模板与社区 3 项
能力说明证据限制
Ad Inspiration 模板按广告场景选择创作模板。W模板名称、数量及席位权益随方案,未逐项体验
自定义模板 API以模板和业务数据生成预览及视频。A不能由 API 文档推断 Web 模板编辑入口
官方教学、案例和更新提供使用教程、客户案例及产品更新。W案例是官方展示,未复核宣传结果
发现与分析 4 项
能力说明证据限制
广告发现与筛选查询社交广告内容寻找创意参考。W数据源完整性和覆盖周期未实测
参考广告收藏与分享保存参考广告并与他人分享。W收藏权限及分享链接有效期未知
品牌广告跟踪关注竞争品牌并查看广告变化。W跟踪限额与实时性以账号为准
广告元素分析与评分拆解 hook、人物、卖点和 CTA。W分析分数不是实际投放收益保证
发布与运营 4 项
能力说明证据限制
广告平台账号和数据连接连接投放账号、店铺与分析数据。W连接器覆盖与授权范围按账号及地区;不声明已连接
广告创建与投放建立创意、预算和定向后发布广告。W未执行外部动作;投放审核、消耗上限未知
投放审计和优化建议定位低效消耗及可扩量内容。W没有对真实广告账户执行审计
持续监控与自动化监控表现并回流到下一轮创作。W执行频率、批准方式、预算自动调节未测
协作与权益 3 项
能力说明证据限制
订阅、积分、并发及无水印权益方案区分额度、水印、并发、时长和资源权限。W不抄录固定价格;方案会变化,结账才确认;不把套餐时长当单个视频模型片段上限
失败退款与输出保存API 失败退积分;成品需自行长期保存。A只适用文档所述接口,不外推整个 Web 退款政策
企业定制和服务提供定制额度、模型调优、白标及 SLA。W具体合同、交付和可用 API 权益未取得
开放接入与客户端 3 项
能力说明证据限制
生成 REST API 与 API Key用官方认证接入商品、声音、人物和任务。A未发业务调用;API 可用性不等于 Web 套餐通用
Creatify MCP向 Claude/ChatGPT 提供产品接入能力。W没有取得 MCP server/tool schema,不推测工具名
HTML 互动广告生成入口官方索引列出商品页转 playable HTML 广告。A仅索引及目录证据;未验证执行、参数、包体或广告平台兼容性

尚不清楚的部分

竞品技术栈观察

官网有历史Framer平台证据;官方招聘要求React/Python及Django或Flask,仅能作为团队选型线索。当前App取包超时;API域DNS指向us-west-2命名的AWS ELB入口。

项目观察到的内容状态置信度
Nginx 与代理版本官网及App本轮无响应banner;Nginx产品及准确版本未知。unknownlow
前端运行语言当前官网/App脚本未取到,具体运行构件未知;招聘提React只是辅助线索。unknownlow
前端源码语言营销站和业务App原始源码语言与版本未知;公开SDK、Skill仓库不等于产品源码。unknownlow
前端框架与版本营销站:2025年Framer官方画廊收录;业务App:React为招聘线索,当前框架版本未证实。inferredmedium
构建与服务端渲染官网历史Framer构建线索;App打包器、SSR/SPA模式和准确版本未知。inferredlow
业务后端语言Python为官方全栈岗位要求;当前业务API/生成Worker的运行语言未直接确认。inferredmedium
业务后端框架官方岗位提Django或Flask;具体生产框架、服务归属和版本均未直接确认。inferredlow
分发层与源站线索API域CNAME指向AWS us-west-2命名ELB;只确认入口线索,官网/App与上游源站位置未知。observedhigh
代码仓库与目录关系官网与业务App有不同域名与技术线索;是否同仓、目录关系及后端源码布局未知。unknownlow
竞品服务调用图

公开API链明确区分Link、脚本任务、预览media_job、渲染输出和通用模型生成;异步协调/执行是有来源的逻辑推断。Web Agent/AdFlow实际接口独立未知。

节点层职责证据
API接入程序client持有调用凭据并发起公开REST请求的客户侧程序。A
Web Agent画布client官方Agent/AdFlow产品入口,与公开API合同单列。W
公共API入口entryapi.creatify.ai的公开接口与当前ELB DNS入口线索。A
Link商品资料domainLink外层资源和内层抓取/编辑资料的合同对象。A
脚本候选任务domain脚本任务及生成文本候选的公开资源。A
预览渲染任务domainLinkToVideo、Preview media_job和每个Output的合同职责。A
模型参数合同domain按model_name提供输入Schema的公开目录职责。A
通用生成任务domainmodel_name/input_params提交与生成任务id资源。A
异步编排职责runtime解释异步预览、渲染和通用任务状态变化的可能协调职责。I
模型执行职责runtime产生预览、视频或asset结果的执行/转发职责。I
任务与产物记录dataAPI返回的状态、media_job和Asset身份的逻辑结果模型。A
结果分发示例external文档结果URL示例为CloudFront,接入方按URL读取资源。A
客户回调接收方external配置webhook_url的客户侧HTTPS接收端。A

增长与运营机制

行业与平台细分页、客户案例/ROI 证据、免费创作与企业演示并行转化。(官方正文;源元数据未知)

复刻状态

能力状态依据
令牌与通用层加载,强调色 #6161ff已验证页面加载后令牌生效
新建项目已验证页面自己的「+ New project」按钮原本不连运行时;运行时把「新建项目」绑到了无关的 AdFlow 工具块并隐藏。已让该按钮转发到运行时的新建项目框,首次点击偶尔无反应,已加一次重试,重试效果未再单独复测
Agent 对话:新建会话、自动选默认模型、发送收回复已验证真实 Maa,收到回复
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
首页与创作工作台布局已加载沿用早期静态页
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8107 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
前端入口:agent-integration/apps-production-brand-v14/creatify/candidate-8875-unified-v22.html
复刻的架构 七个品牌相同,展开查看
flowchart LR
  U[浏览器] -->|静态页| FE[品牌前端<br/>令牌 + 通用层 + 运行时脚本]
  FE -->|/v1/* Cookie + CSRF| BFF[BFF 业务服务 Go]
  BFF --> MY[(MySQL<br/>共享身份库 + 品牌库)]
  BFF -->|会话 / 消息 / 事件| MAA[Maa Agent]
  BFF -->|任务| WK[媒体 worker]
  WK -->|submit_video| GW[模型网关]
  GW --> MOD[视频模型]
  MAA --> GW
  BFF --> OBJ[(素材 / 导出存储)]
所有七个复刻共用同一套后端:24 张表的品牌库 + 15 张表的共享身份库,55 个公开接口(路径里用 ?profile=creatify 区分品牌)。品牌差异在前端:令牌(颜色、圆角、字体)、入口与页面结构。
为什么这样设计七个竞品的后端结构在我们的实测里几乎一致,差别只在入口、模板和视觉。所以复刻把差异收敛到前端令牌,后端只维护一份;这也是最终设计里「共享内核 + 品牌配置」的来源(决策 D17)。

Creatify · Agent 交互过程

竞品里观察到的

NOT_RUN_COST_GATE 已确认登录工作台与空测试工程;按用户明确“只做明确免费Agent对话、不能花现有积分”的限制,没有发送任何Agent消息。

费用与准入

项值
selected_model默认Agent模型未公开
observed_priceUpgrade旁显示10;未见文字回合明确价格或免费声明。
available_credits10
exact_per_turn_costunknown
approval用户已明确只做确认零积分费用的Agent对话;本轮不得消耗现有积分,先前10积分预算询问不构成授权。
action_takenno_send
reason官方回复计费说明与有限免费credits均不满足零积分条件;只文字规划也可能产生费用。
free_only_requirementtrue
zero_credit_cost_confirmedfalse
official_policy当前官方Creative Agent帮助明确每次Agent响应或生成动作有对应积分成本。Web Ad Agent本账号单回合价仍未知,不能推定为零。

竞品公开的调用流程

步骤调用关系操作证据
导入商品链接接入程序 → 公共APIPOST https://api.creatify.ai/api/links/,body含url;鉴权字段X-API-ID/X-API-KEY;保留Link.id和link.id。A
修订商品资料接入程序 → 公共APIPUT /api/links/{id}/修订产品资料;编辑字段和路径id规则以官方schema为准。A
生成脚本候选接入程序 → 公共APIPOST /api/ai_scripts/;返回脚本任务id、workspace/product引用、status及generated_scripts[].paragraphs/script_name/script_style/title。A
请求多个预览接入程序 → 公共APIPOST /api/link_to_videos/preview_list_async/;link引用外层Link.id,可传visual_styles[]、video_length、aspect_ratio等;返回任务id与previews[].media_job。A
选中候选渲染接入程序 → 公共APIPOST /api/link_to_videos/{id}/render_single_preview/;path为LinkToVideo.id,body为media_job:UUID。A
查询每个输出接入程序 → 公共APIGET /api/link_to_videos/{id}/;检查outputs[].status、progress、failed_reason和video_output,不仅检查顶层status。A
读取模型参数接入程序 → 公共APIGET /api/asset_generator/schemas/?model_name=...;返回model_name、generation_type与input_params_schema及UI扩展字段。A
提交通用生成接入程序 → 公共APIPOST /api/asset_generator/;body含model_name、input_params、可选webhook_url;返回生成任务id。A
收取产物状态接入程序 → 公共APIGET /api/asset_generator/{id}/按任务id取status与assets[]{id,type,url,thumbnail_url,name};可选由服务向客户webhook_url发送回调。A

在本品牌复刻上的测试结果

能力状态依据
Agent 对话:新建会话、自动选默认模型、发送收回复已验证真实 Maa,收到回复
停止回复已验证maa_run_stop_actions=terminal_observed,run=cancelled
生成视频(报价→批准→成片)已验证测试部署 119.23.106.60:8107 上真实生成:Seedance 2.0 Mini 5 秒,报价 3 积分,批准后任务成功、产出 MP4(接口级验证;素材页展示未逐项核对)
复刻里的 Agent 交互设计(时序图、状态机、规则) 七个品牌相同,展开查看
sequenceDiagram
  autonumber
  participant B as 浏览器
  participant S as BFF
  participant DB as MySQL
  participant M as Maa Agent
  participant W as 媒体 worker
  participant G as 模型网关
  B->>S: POST /projects, /conversations
  B->>S: POST maa/binding
  S->>DB: maa_bindings dispatching
  S->>M: 创建原生会话
  M-->>S: native_cid
  S->>DB: maa_bindings bound
  B->>S: POST maa/messages {client_action_id, content}
  S->>DB: maa_message_actions 幂等回执
  S-->>B: 202 + run_id
  S->>M: 提交消息
  S->>DB: maa_runs queued 到 running
  loop 事件游标
    B->>S: GET runs/{id}/events
    S-->>B: assistant.delta / assistant.message
  end
  M-->>S: run.completed
  S->>DB: 记录助手内容 SHA-256
  B->>S: POST media/requests/from-maa {source_run_id, sha256}
  S-->>B: 报价 + 有效期 + quote_sha256
  B->>S: POST approve {client_approval_id, request_sha256, quote_sha256}
  S->>DB: 冻结积分 + 建任务
  W->>G: submit_video
  G-->>W: 视频
  W->>DB: 产出入库 + 扣费
  B->>S: GET media/jobs/{id}

Run 状态机

stateDiagram-v2
  [*] --> queued: POST maa/messages 202
  queued --> running
  running --> waiting_interaction: 需要澄清卡
  waiting_interaction --> running: respondInteraction
  running --> completed
  running --> failed
  running --> cancelled: stopRun
  waiting_interaction --> cancelled
  completed --> [*]
  failed --> [*]
  cancelled --> [*]

媒体任务状态机

stateDiagram-v2
  [*] --> quoted
  quoted --> expired: 约 5 分钟
  quoted --> approved: approve
  approved --> held: 冻结积分
  held --> queued
  queued --> running: worker 领取
  running --> succeeded: 产出入库
  succeeded --> captured: 扣费确认
  running --> failed
  failed --> 待核对: 供应商已受理后失败,冻结保留,界面写「费用待核对」

交互规则

环节规则落库
绑定会话bindMaa 传 agent_model 或 use_default;先 dispatching,拿到原生会话号后才 bound(约束保证二者一致)maa_bindings
发消息client_action_id 幂等;返回 202 + run_id;同一个动作 ID 内容不同返回 409maa_message_actions, maa_runs
事件按 seq 游标读取;服务端先清洗路径、内部地址和令牌再公开maa_runs.last_event_seq
完成证据完成时记录助手内容的两份 SHA-256 与字节数maa_runs
澄清卡(HITL)Run 进入 waiting_interaction;respondInteraction 带 card_sha256;不能恢复两次maa_interactions, maa_interaction_actions
停止stopRun 幂等,回执只说「已观测到终态」;对话里保留「已停止回复」maa_run_stop_actions
切换模型必须带 expected_revision;有未决切换时拒绝发新消息maa_model_change_actions
生成视频from-maa → 报价 → 批准 → 冻结 → worker → 入库 → 扣费;批准带请求哈希与报价哈希studio_media_*, 积分账本
报价过期批准被拒,不建任务不扣费;界面提示重新报价studio_media_requests.expires_at
失败后的积分供应商已受理后失败:冻结保留,界面写「费用待核对」,不自动释放;本部署的账本里还没出现过释放分录(12 笔冻结、8 笔扣费、其余 4 笔仍冻结)studio_credit_ledger_entries
会话过期每 8 秒检查登录状态;失效时横幅提示,登录后恢复—
为什么这样设计把事件做成带序号的流、把动作做成带幂等键的回执,是因为长回复一定会遇到断线、刷新和重复点击。序号让续读不丢不重;幂等让重复点击安全;诚实的回执(只说「已观测到终态」)避免把「已停止」说成「不收费」。我们还在复刻里修掉了一个停止按钮从不发请求的缺陷,这说明每条交互路径都需要端到端测试(决策 D8)。

Creatify · ER 图

品牌库 studio_profile_creatify:24 张表、34 个真实外键、66 个检查约束、22 个触发器。图由实际表结构生成,箭头从父表指向子表。品牌库与共享身份库之间的 owner_id、asset_id 靠应用层归属检查,不画成外键。

共 61 条关系:一对多 37 条,一对一 10 条,一对多(父可选) 10 条,一对一(父可选) 4 条;多对多 2 组(经关联表)。

用户与登录 素材 积分账本 支付与订阅 模型目录 项目与会话 画布 · 时间轴 · 导出 Agent 交互 生成 · 媒体 · 交付

基数标注(线上的文字,箭头从父表指向子表):1:N 一对多,子表外键必填;0..1:N 一对多,但外键可空,所以父是可选的;1:1 一对一,子表外键上有唯一键;0..1:1 一对一且父可选。红色粗双向线 = N:M 多对多,经带 ⇄ 的关联表(它的主键就是两个外键)。实线 = 数据库真实外键;灰色虚线 = 跨库关系(两个库之间数据库无法建外键,由应用层检查);橙色粗框与粗线 = Agent 交互相关。每张表都带 owner_id(指向用户),为了图清楚没有逐条画线。

全景图:全部实体与关系

默认缩放到页面宽度,先看整体结构;点一下图切换到原始大小(看清表名,在框内上下左右滚动),再点一下缩回。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":26,"rankSpacing":80}}}%%
flowchart LR
  subgraph sg_i_user["用户与登录"]
    direction TB
    t_studio_users["users"]
    t_studio_sessions["sessions"]
  end
  subgraph sg_i_asset["素材"]
    direction TB
    t_studio_assets["assets"]
  end
  subgraph sg_i_credit["积分账本"]
    direction TB
    t_studio_credit_accounts["credit_accounts"]
    t_studio_credit_lots["credit_lots"]
    t_studio_credit_authorizations["credit_authorizations"]
    t_studio_credit_authorization_allocations["⇄ credit_authorization_allocations"]
    t_studio_credit_ledger_entries["credit_ledger_entries"]
    t_studio_credit_plan_versions["credit_plan_versions"]
    t_studio_billing_cycles["billing_cycles"]
  end
  subgraph sg_i_pay["支付与订阅"]
    direction TB
    t_studio_payment_orders["payment_orders"]
    t_studio_payment_events["payment_events"]
    t_studio_subscriptions["subscriptions"]
  end
  subgraph sg_i_model["模型目录"]
    direction TB
    t_studio_media_models["media_models"]
    t_studio_media_model_versions["media_model_versions"]
  end
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  subgraph sg_media["生成 · 媒体 · 交付"]
    direction TB
    t_studio_media_requests["media_requests"]
    t_studio_media_jobs["media_jobs"]
    t_studio_media_submission_attempts["media_submission_attempts"]
    t_studio_media_outputs["media_outputs"]
    t_generation_jobs["gen_jobs"]
    t_generation_reservations["gen_reservations"]
    t_generation_deliveries["gen_deliveries"]
    t_generation_development_budgets["gen_development_budgets"]
    t_studio_credit_recovery_intents["credit_recovery_intents"]
  end
  t_studio_users -->|"1:N"| t_studio_assets
  t_studio_payment_orders -->|"1:1"| t_studio_billing_cycles
  t_studio_subscriptions -->|"1:N"| t_studio_billing_cycles
  t_studio_users -->|"1:1"| t_studio_credit_accounts
  t_studio_credit_authorizations -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_lots -->|"1:N"| t_studio_credit_authorization_allocations
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_authorizations
  t_studio_credit_authorization_allocations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_authorizations -->|"0..1:N"| t_studio_credit_ledger_entries
  t_studio_credit_lots -->|"1:N"| t_studio_credit_ledger_entries
  t_studio_credit_accounts -->|"1:N"| t_studio_credit_lots
  t_studio_billing_cycles -->|"0..1:1"| t_studio_credit_lots
  t_studio_payment_orders -->|"0..1:1"| t_studio_credit_lots
  t_studio_media_models -->|"1:N"| t_studio_media_model_versions
  t_studio_media_model_versions -->|"0..1:1"| t_studio_media_models
  t_studio_payment_orders -->|"0..1:N"| t_studio_payment_events
  t_studio_subscriptions -->|"0..1:N"| t_studio_payment_events
  t_studio_users -->|"1:N"| t_studio_payment_events
  t_studio_credit_plan_versions -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_payment_orders
  t_studio_users -->|"1:N"| t_studio_sessions
  t_studio_credit_plan_versions -->|"1:N"| t_studio_subscriptions
  t_studio_users -->|"1:N"| t_studio_subscriptions
  t_generation_jobs -->|"1:1"| t_generation_deliveries
  t_studio_projects -->|"1:N"| t_generation_deliveries
  t_studio_conversations -->|"1:N"| t_generation_deliveries
  t_generation_development_budgets -->|"1:N"| t_generation_jobs
  t_studio_projects -->|"1:N"| t_generation_jobs
  t_studio_conversations -->|"1:N"| t_generation_jobs
  t_generation_jobs -->|"1:1"| t_generation_reservations
  t_studio_conversations -->|"1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"0..1:1"| t_maa_bindings
  t_maa_interactions -->|"1:1"| t_maa_interaction_actions
  t_maa_runs -->|"1:N"| t_maa_interactions
  t_maa_bindings -->|"1:N"| t_maa_message_actions
  t_maa_runs -->|"0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"1:N"| t_maa_model_change_actions
  t_maa_runs -->|"1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"1:N"| t_maa_runs
  t_studio_canvases -->|"1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"1:1"| t_studio_canvases
  t_studio_projects -->|"1:N"| t_studio_conversations
  t_studio_media_jobs -->|"1:N"| t_studio_credit_recovery_intents
  t_studio_projects -->|"1:N"| t_studio_export_jobs
  t_studio_media_requests -->|"1:1"| t_studio_media_jobs
  t_studio_media_jobs -->|"1:N"| t_studio_media_outputs
  t_studio_conversations -->|"1:N"| t_studio_media_requests
  t_maa_runs -->|"0..1:N"| t_studio_media_requests
  t_studio_media_jobs -->|"1:N"| t_studio_media_submission_attempts
  t_studio_projects -->|"1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"1:1"| t_studio_video_documents
  t_studio_assets -.->|"1:N"| t_studio_project_asset_refs
  t_studio_assets -.->|"1:N"| t_studio_media_outputs
  t_studio_assets -.->|"1:N"| t_generation_deliveries
  t_studio_media_model_versions -.->|"1:N"| t_studio_media_requests
  t_studio_media_models -.->|"1:N"| t_studio_media_requests
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_media_jobs
  t_studio_credit_authorizations -.->|"0..1:N"| t_studio_credit_recovery_intents
  t_studio_credit_authorizations <-.->|"N:M · 经 credit_authorization_allocations"| t_studio_credit_lots
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_users,t_studio_sessions user
class t_studio_assets asset
class t_studio_credit_accounts,t_studio_credit_lots,t_studio_credit_authorizations,t_studio_credit_authorization_allocations,t_studio_credit_ledger_entries,t_studio_credit_plan_versions,t_studio_billing_cycles credit
class t_studio_payment_orders,t_studio_payment_events,t_studio_subscriptions pay
class t_studio_media_models,t_studio_media_model_versions model
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
class t_studio_media_requests,t_studio_media_jobs,t_studio_media_submission_attempts,t_studio_media_outputs,t_generation_jobs,t_generation_reservations,t_generation_deliveries,t_generation_development_budgets,t_studio_credit_recovery_intents media
class t_studio_credit_authorization_allocations,t_studio_project_asset_refs junction
  style sg_i_user fill:none,stroke:#6b7280,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_asset fill:none,stroke:#06b6d4,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_credit fill:none,stroke:#eab308,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_pay fill:none,stroke:#ec4899,stroke-width:1px,stroke-dasharray:5 4
  style sg_i_model fill:none,stroke:#6366f1,stroke-width:1px,stroke-dasharray:5 4
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  style sg_media fill:none,stroke:#22c55e,stroke-width:1px
  linkStyle 30,31,32,33,34,35,36,37,38,48 stroke:#f97316,stroke-width:2.5px
  linkStyle 54,55,56,57,58,59,60 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 61,62 stroke:#dc2626,stroke-width:3px

多对多(N:M)

数据库里没有“多对多”这种列,它总是靠一张关联表表达。下面这几张表的主键由两个外键组成,所以它们两端是多对多:

多对多的两端关联表关联表的主键 / 唯一键
studio_credit_authorizations ⇄ studio_credit_lotsstudio_credit_authorization_allocationsowner_id, authorization_id, lot_id
studio_projects ⇄ studio_assetsstudio_project_asset_refsproject_id, asset_id

Agent 交互相关(圈出来)

绑定 → Run → 消息 / 澄清卡 / 切换模型 / 停止,每个动作一张回执表;线上是“外键列 · 基数”。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_maa_bindings["maa_bindings"]
    t_maa_runs["maa_runs"]
    t_maa_message_actions["maa_message_actions"]
    t_maa_interactions["maa_interactions"]
    t_maa_interaction_actions["maa_interaction_actions"]
    t_maa_model_change_actions["maa_model_change_actions"]
    t_maa_run_stop_actions["maa_run_stop_actions"]
  end
  t_studio_conversations -->|"conversation_id · 1:1"| t_maa_bindings
  t_maa_model_change_actions -->|"pending_model_change_id · 0..1:1"| t_maa_bindings
  t_maa_interactions -->|"interaction_id · 1:1"| t_maa_interaction_actions
  t_maa_runs -->|"public_run_id · 1:N"| t_maa_interactions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_message_actions
  t_maa_runs -->|"public_run_id · 0..1:N"| t_maa_message_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_model_change_actions
  t_maa_runs -->|"public_run_id · 1:1"| t_maa_run_stop_actions
  t_maa_bindings -->|"binding_id · 1:N"| t_maa_runs
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_maa_bindings,t_maa_runs,t_maa_message_actions,t_maa_interactions,t_maa_interaction_actions,t_maa_model_change_actions,t_maa_run_stop_actions agent
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  linkStyle 0,1,2,3,4,5,6,7,8 stroke:#f97316,stroke-width:2.5px

画布与时间轴

项目下是一份视频文档,画布、画布版本、时间线版本和导出都挂在这份文档上;素材放在共享身份库,项目只记引用,项目与素材是多对多,经 project_asset_refs。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":90}}}%%
flowchart LR
  subgraph sg_project["项目与会话"]
    direction TB
    t_studio_projects["projects"]
    t_studio_conversations["conversations"]
  end
  subgraph sg_canvas["画布 · 时间轴 · 导出"]
    direction TB
    t_studio_video_documents["video_documents"]
    t_studio_canvases["canvases"]
    t_studio_canvas_revisions["canvas_revisions"]
    t_studio_timeline_revisions["timeline_revisions"]
    t_studio_export_jobs["export_jobs"]
    t_studio_project_asset_refs["⇄ project_asset_refs"]
  end
  subgraph sg_asset["素材(共享身份库)"]
    direction TB
    t_studio_assets["assets"]
  end
  t_studio_canvases -->|"project_id · 1:N"| t_studio_canvas_revisions
  t_studio_canvas_revisions -->|"restored_from_revision · 0..1:N"| t_studio_canvas_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_canvases
  t_studio_projects -->|"project_id · 1:N"| t_studio_conversations
  t_studio_projects -->|"project_id · 1:N"| t_studio_export_jobs
  t_studio_projects -->|"project_id · 1:N"| t_studio_project_asset_refs
  t_studio_video_documents -->|"project_id · 1:N"| t_studio_timeline_revisions
  t_studio_timeline_revisions -->|"restored_from_revision · 0..1:N"| t_studio_timeline_revisions
  t_studio_projects -->|"project_id · 1:1"| t_studio_video_documents
  t_studio_assets -.->|"asset_id · 1:N"| t_studio_project_asset_refs
  t_studio_projects <-.->|"N:M · 经 project_asset_refs"| t_studio_assets
classDef project fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef canvas fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef media fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef user fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef credit fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef pay fill:#fce7f3,stroke:#ec4899,color:#111,stroke-width:1.5px
classDef model fill:#e0e7ff,stroke:#6366f1,color:#111,stroke-width:1.5px
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_studio_projects,t_studio_conversations project
class t_studio_video_documents,t_studio_canvases,t_studio_canvas_revisions,t_studio_timeline_revisions,t_studio_export_jobs,t_studio_project_asset_refs canvas
class t_studio_assets asset
class t_studio_project_asset_refs junction
  style sg_project fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_canvas fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px
  linkStyle 9 stroke:#64748b,stroke-width:1.5px,stroke-dasharray:4 3
  linkStyle 10 stroke:#dc2626,stroke-width:3px
关系清单 逐条 61 条,含基数
父表子表外键列(子表里)基数约束域
generation_jobsgeneration_deliveriesjob_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_assetsgeneration_deliveriesasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_conversationsgeneration_deliveriesowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_deliveriesowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_development_budgetsgeneration_jobsowner_id, budget_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_conversationsgeneration_jobsowner_id, project_id, session_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_projectsgeneration_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
generation_jobsgeneration_reservationsjob_id, budget_id, owner_id1:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_model_change_actionsmaa_bindingsowner_id, project_id, conversation_id, id, pending_model_change_id0..1:1 一对一(父可选)DB 外键Agent 交互 → Agent 交互
studio_conversationsmaa_bindingsowner_id, project_id, conversation_id1:1 一对一DB 外键项目与会话 → Agent 交互
maa_interactionsmaa_interaction_actionsowner_id, project_id, conversation_id, binding_id, public_run_id, interaction_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_runsmaa_interactionsowner_id, project_id, conversation_id, binding_id, public_run_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_message_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_message_actionsowner_id, project_id, conversation_id, binding_id, public_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_model_change_actionsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
maa_runsmaa_run_stop_actionsowner_id, project_id, conversation_id, binding_id, public_run_id1:1 一对一DB 外键Agent 交互 → Agent 交互
maa_bindingsmaa_runsowner_id, project_id, conversation_id, binding_id1:N 一对多DB 外键Agent 交互 → Agent 交互
studio_usersstudio_assetsowner_id1:N 一对多DB 外键用户与登录 → 素材
studio_payment_ordersstudio_billing_cyclesowner_id, payment_order_id, plan_version_id1:1 一对一DB 外键支付与订阅 → 积分账本
studio_subscriptionsstudio_billing_cyclesowner_id, subscription_id, provider_key, provider_account1:N 一对多DB 外键支付与订阅 → 积分账本
studio_canvas_revisionsstudio_canvas_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_canvasesstudio_canvas_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_canvasesowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_projectsstudio_conversationsowner_id, project_id1:N 一对多DB 外键项目与会话 → 项目与会话
studio_usersstudio_credit_accountsowner_id1:1 一对一DB 外键用户与登录 → 积分账本
studio_credit_authorizationsstudio_credit_authorization_allocationsowner_id, authorization_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_authorization_allocationsowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_authorizationsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_credit_authorization_allocationsstudio_credit_ledger_entriesowner_id, authorization_id, lot_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_authorizationsstudio_credit_ledger_entriesowner_id, authorization_id0..1:N 一对多(父可选)DB 外键积分账本 → 积分账本
studio_credit_lotsstudio_credit_ledger_entriesowner_id, lot_id1:N 一对多DB 外键积分账本 → 积分账本
studio_billing_cyclesstudio_credit_lotsowner_id, source_cycle_id0..1:1 一对一(父可选)DB 外键积分账本 → 积分账本
studio_credit_accountsstudio_credit_lotsowner_id1:N 一对多DB 外键积分账本 → 积分账本
studio_payment_ordersstudio_credit_lotsowner_id, source_order_id0..1:1 一对一(父可选)DB 外键支付与订阅 → 积分账本
studio_credit_authorizationsstudio_credit_recovery_intentsauthorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_jobsstudio_credit_recovery_intentsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_projectsstudio_export_jobsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_credit_authorizationsstudio_media_jobscredit_authorization_id0..1:N 一对多(父可选)跨库(应用层检查)积分账本 → 生成 · 媒体 · 交付
studio_media_requestsstudio_media_jobsowner_id, project_id, conversation_id, request_id, request_sha256, quote_sha2561:1 一对一DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_model_versionsmodel_id1:N 一对多DB 外键模型目录 → 模型目录
studio_media_model_versionsstudio_media_modelsid, published_version_id0..1:1 一对一(父可选)DB 外键模型目录 → 模型目录
studio_assetsstudio_media_outputsasset_id1:N 一对多跨库(应用层检查)素材 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_outputsowner_id, project_id, conversation_id, job_id1:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
maa_runsstudio_media_requestsowner_id, project_id, conversation_id, source_binding_id, source_run_id0..1:N 一对多(父可选)DB 外键Agent 交互 → 生成 · 媒体 · 交付
studio_conversationsstudio_media_requestsowner_id, project_id, conversation_id1:N 一对多DB 外键项目与会话 → 生成 · 媒体 · 交付
studio_media_model_versionsstudio_media_requestsmodel_version_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_modelsstudio_media_requestsmodel_id1:N 一对多跨库(应用层检查)模型目录 → 生成 · 媒体 · 交付
studio_media_jobsstudio_media_submission_attemptsowner_id, project_id, conversation_id, job_id, request_sha2561:N 一对多DB 外键生成 · 媒体 · 交付 → 生成 · 媒体 · 交付
studio_payment_ordersstudio_payment_eventsowner_id, order_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_subscriptionsstudio_payment_eventsowner_id, subscription_id, provider_key, provider_account0..1:N 一对多(父可选)DB 外键支付与订阅 → 支付与订阅
studio_usersstudio_payment_eventsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_credit_plan_versionsstudio_payment_ordersplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_payment_ordersowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_assetsstudio_project_asset_refsasset_id1:N 一对多跨库(应用层检查)素材 → 画布 · 时间轴 · 导出
studio_projectsstudio_project_asset_refsowner_id, project_id1:N 一对多DB 外键项目与会话 → 画布 · 时间轴 · 导出
studio_usersstudio_sessionsuser_id1:N 一对多DB 外键用户与登录 → 用户与登录
studio_credit_plan_versionsstudio_subscriptionsplan_version_id1:N 一对多DB 外键积分账本 → 支付与订阅
studio_usersstudio_subscriptionsowner_id1:N 一对多DB 外键用户与登录 → 支付与订阅
studio_timeline_revisionsstudio_timeline_revisionsowner_id, project_id, kind, restored_from_revision0..1:N 一对多(父可选)DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_video_documentsstudio_timeline_revisionsowner_id, project_id1:N 一对多DB 外键画布 · 时间轴 · 导出 → 画布 · 时间轴 · 导出
studio_projectsstudio_video_documentsowner_id, project_id1:1 一对一DB 外键项目与会话 → 画布 · 时间轴 · 导出
完整 ER:39 张表的全部列,带主键 / 外键与基数符号(鸦脚)
erDiagram
  studio_assets {
    CHAR id PK
    CHAR owner_id FK
    CHAR sha256
    JSON metadata_json
    VARCHAR storage_key
    DATETIME created_at
    VARCHAR publication_state
  }
  studio_billing_cycles {
    CHAR id PK
    CHAR owner_id FK
    CHAR subscription_id FK
    CHAR plan_version_id FK
    CHAR payment_order_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_invoice_id
    DATETIME period_start
    DATETIME period_end
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_accounts {
    CHAR owner_id PK
    VARCHAR unit
    VARCHAR status
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_credit_authorization_allocations {
    CHAR owner_id FK
    CHAR authorization_id PK
    CHAR lot_id PK
    BIGINT_UNSIGNED allocated_credits
    DATETIME created_at
  }
  studio_credit_authorizations {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR profile
    CHAR project_id
    CHAR conversation_id
    CHAR job_id
    CHAR request_id
    CHAR request_sha256
    CHAR quote_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    MEDIUMBLOB reservation_bytes
    CHAR reservation_sha256
    MEDIUMBLOB policy_bytes
    CHAR policy_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_ledger_entries {
    CHAR id PK
    CHAR owner_id FK
    CHAR lot_id FK
    CHAR authorization_id FK
    VARCHAR operation_key
    VARCHAR kind
    BIGINT grant_delta
    BIGINT held_delta
    BIGINT captured_delta
    MEDIUMBLOB facts_bytes
    CHAR facts_sha256
    DATETIME created_at
  }
  studio_credit_lots {
    CHAR id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED grant_credits
    BIGINT_UNSIGNED held_credits
    BIGINT_UNSIGNED captured_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    CHAR source_order_id FK
    CHAR source_cycle_id FK
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_credit_plan_versions {
    CHAR id PK
    VARCHAR plan_key
    VARCHAR version_key
    VARCHAR kind
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    INT_UNSIGNED credit_validity_seconds
    INT_UNSIGNED billing_interval_months
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    DATETIME published_at
    DATETIME retired_at
  }
  studio_media_model_versions {
    CHAR id PK
    CHAR model_id FK
    VARCHAR version_key
    MEDIUMBLOB config_bytes
    CHAR config_sha256
    DATETIME published_at
  }
  studio_media_models {
    CHAR id PK
    VARCHAR model_key
    VARCHAR kind
    VARCHAR display_name
    VARCHAR status
    CHAR published_version_id FK
    BIGINT_UNSIGNED revision
    DATETIME updated_at
  }
  studio_payment_events {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key FK
    VARCHAR provider_account FK
    VARCHAR provider_event_id
    CHAR order_id FK
    CHAR subscription_id FK
    MEDIUMBLOB body_bytes
    CHAR body_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_payment_orders {
    CHAR id PK
    CHAR owner_id FK
    CHAR client_action_id
    CHAR plan_version_id FK
    BIGINT_UNSIGNED price_minor
    CHAR currency
    BIGINT_UNSIGNED grant_credits
    DATETIME valid_from
    DATETIME expires_at
    MEDIUMBLOB eligibility_bytes
    CHAR eligibility_sha256
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_idempotency_key
    VARCHAR provider_order_id
    VARCHAR state
    CHAR paid_evidence_sha256
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_sessions {
    CHAR token_hash PK
    CHAR user_id FK
    DATETIME created_at
    DATETIME expires_at
    DATETIME revoked_at
  }
  studio_subscriptions {
    CHAR id PK
    CHAR owner_id FK
    VARCHAR provider_key
    VARCHAR provider_account
    VARCHAR provider_subscription_id
    CHAR plan_version_id FK
    VARCHAR state
    DATETIME period_start
    DATETIME period_end
    TINYINT cancel_at_period_end
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_users {
    CHAR id PK
    VARCHAR email
    VARBINARY password_hash
    TINYINT enabled
    DATETIME created_at
  }
  generation_deliveries {
    CHAR job_id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR asset_id FK
    CHAR request_sha256
    CHAR maa_source_sha256
    CHAR provider_source_sha256
    VARCHAR model
    VARCHAR tool_name
    CHAR input_sha256
    JSON metadata_json
    DATETIME created_at
  }
  generation_development_budgets {
    CHAR id PK
    CHAR owner_id
    VARCHAR policy_version
    BIGINT_UNSIGNED credits_limit
    BIGINT_UNSIGNED credits_reserved
    INT_UNSIGNED calls_limit
    INT_UNSIGNED calls_reserved
    DATETIME expires_at
  }
  generation_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR session_id FK
    CHAR native_conversation_id
    CHAR native_run_id
    CHAR source_sha256
    CHAR budget_id FK
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    BIGINT_UNSIGNED credits
    DATETIME expires_at
    VARCHAR status
    INT_UNSIGNED attempts
    MEDIUMBLOB result_bytes
    DATETIME updated_at
  }
  generation_reservations {
    CHAR job_id PK
    CHAR budget_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED credits
    INT_UNSIGNED calls
    VARCHAR status
  }
  maa_bindings {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR provider
    VARCHAR model_name
    VARCHAR status
    VARCHAR native_cid
    DATETIME created_at
    BIGINT_UNSIGNED model_revision
    CHAR pending_model_change_id FK
  }
  maa_interaction_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR interaction_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR client_action_id
    CHAR card_sha256
    CHAR request_sha256
    CHAR content_sha256
    VARCHAR response_action
    JSON values_payload
    VARCHAR status
    DATETIME created_at
  }
  maa_interactions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_iid
    CHAR card_sha256
    JSON card_payload
    DATETIME created_at
  }
  maa_message_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_action_id
    CHAR request_sha256
    MEDIUMTEXT content
    VARCHAR status
    CHAR public_run_id FK
    CHAR proposed_run_id
    DATETIME created_at
  }
  maa_model_change_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    CHAR client_action_id
    CHAR request_sha256
    CHAR native_request_sha256
    BIGINT_UNSIGNED expected_revision
    VARCHAR from_provider
    VARCHAR from_model_name
    VARCHAR target_provider
    VARCHAR target_model_name
    VARCHAR status
    BIGINT_UNSIGNED dispatch_attempt
    JSON receipt_payload
    DATETIME created_at
    DATETIME updated_at
  }
  maa_run_stop_actions {
    CHAR id PK
    CHAR binding_id FK
    CHAR public_run_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_cid
    VARCHAR native_rid
    CHAR client_action_id
    CHAR request_sha256
    VARCHAR stop_action
    VARCHAR status
    BIGINT_UNSIGNED attempt
    VARCHAR terminal_run_status
    DATETIME created_at
    DATETIME updated_at
  }
  maa_runs {
    CHAR id PK
    CHAR binding_id FK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    VARCHAR native_rid
    VARCHAR status
    DATETIME created_at
    CHAR native_assistant_sha256
    CHAR public_assistant_sha256
    BIGINT_UNSIGNED native_assistant_bytes
    BIGINT_UNSIGNED assistant_event_seq
    BIGINT_UNSIGNED last_event_seq
  }
  studio_canvas_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_canvases {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_conversations {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    VARCHAR title
    DATETIME created_at
  }
  studio_credit_recovery_intents {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256
    VARCHAR kind
    CHAR authorization_id FK
    MEDIUMBLOB intent_bytes
    CHAR intent_sha256
    VARCHAR state
    CHAR lease_token_sha256
    DATETIME lease_until
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
  }
  studio_export_jobs {
    CHAR id PK
    CHAR project_id FK
    CHAR owner_id FK
    BIGINT_UNSIGNED document_revision
    CHAR input_hash
    VARCHAR status
    JSON input_json
    JSON output_json
    VARCHAR error_code
    CHAR lease_token_hash
    DATETIME lease_until
    INT_UNSIGNED attempts
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_jobs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR request_id FK
    CHAR request_sha256 FK
    CHAR quote_sha256 FK
    CHAR client_approval_id
    CHAR approval_sha256
    CHAR credit_authorization_id FK
    VARCHAR authorization_mode
    DATETIME authorization_expires_at
    VARCHAR status
    BIGINT_UNSIGNED revision
    CHAR lease_token_sha256
    DATETIME lease_until
    VARCHAR provider_task_id
    CHAR client_cancel_id
    CHAR cancel_request_sha256
    DATETIME cancel_requested_at
    CHAR staged_asset_id
    CHAR staged_asset_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_media_outputs {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED ordinal
    CHAR asset_id FK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    CHAR provider_result_sha256
    DATETIME created_at
  }
  studio_media_requests {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR client_request_id
    CHAR model_id FK
    CHAR model_version_id FK
    CHAR model_config_sha256
    MEDIUMBLOB request_bytes
    CHAR request_sha256
    MEDIUMBLOB quote_bytes
    CHAR quote_sha256
    BIGINT_UNSIGNED quoted_credits
    DATETIME expires_at
    VARCHAR origin
    CHAR source_binding_id FK
    CHAR source_run_id FK
    CHAR source_assistant_sha256
    MEDIUMBLOB source_snapshot_bytes
    DATETIME created_at
  }
  studio_media_submission_attempts {
    CHAR id PK
    CHAR owner_id FK
    CHAR project_id FK
    CHAR conversation_id FK
    CHAR job_id FK
    INT_UNSIGNED attempt_no
    CHAR request_sha256 FK
    VARCHAR provider_idempotency_key
    VARCHAR stage
    DATETIME dispatch_started_at
    VARCHAR provider_task_id
    CHAR receipt_sha256
    CHAR terminal_receipt_sha256
    CHAR reconciliation_sha256
    DATETIME created_at
    DATETIME updated_at
  }
  studio_project_asset_refs {
    CHAR project_id PK
    CHAR owner_id FK
    CHAR asset_id PK
    CHAR asset_sha256
    CHAR asset_metadata_sha256
    VARCHAR state
    BIGINT_UNSIGNED revision
    DATETIME created_at
    DATETIME updated_at
    DATETIME detached_at
  }
  studio_projects {
    CHAR id PK
    CHAR owner_id
    VARCHAR title
    DATETIME created_at
  }
  studio_timeline_revisions {
    CHAR owner_id PK
    CHAR project_id PK
    VARCHAR kind PK
    BIGINT_UNSIGNED revision PK
    MEDIUMBLOB state_bytes
    CHAR state_sha256
    VARCHAR origin
    BIGINT_UNSIGNED restored_from_revision FK
    DATETIME recorded_at
  }
  studio_video_documents {
    CHAR project_id PK
    CHAR owner_id FK
    BIGINT_UNSIGNED revision
    JSON state_json
    DATETIME updated_at
  }
  studio_users ||--o{ studio_assets : "owner_id"
  studio_payment_orders ||--o| studio_billing_cycles : "plan_version_id"
  studio_subscriptions ||--o{ studio_billing_cycles : "provider_account"
  studio_users ||--o| studio_credit_accounts : "owner_id"
  studio_credit_authorizations ||--o{ studio_credit_authorization_allocations : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_authorization_allocations : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_authorizations : "owner_id"
  studio_credit_authorization_allocations |o--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_authorizations |o--o{ studio_credit_ledger_entries : "authorization_id"
  studio_credit_lots ||--o{ studio_credit_ledger_entries : "lot_id"
  studio_credit_accounts ||--o{ studio_credit_lots : "owner_id"
  studio_billing_cycles |o--o| studio_credit_lots : "source_cycle_id"
  studio_payment_orders |o--o| studio_credit_lots : "source_order_id"
  studio_media_models ||--o{ studio_media_model_versions : "model_id"
  studio_media_model_versions |o--o| studio_media_models : "published_version_id"
  studio_payment_orders |o--o{ studio_payment_events : "provider_account"
  studio_subscriptions |o--o{ studio_payment_events : "provider_account"
  studio_users ||--o{ studio_payment_events : "owner_id"
  studio_credit_plan_versions ||--o{ studio_payment_orders : "plan_version_id"
  studio_users ||--o{ studio_payment_orders : "owner_id"
  studio_users ||--o{ studio_sessions : "user_id"
  studio_credit_plan_versions ||--o{ studio_subscriptions : "plan_version_id"
  studio_users ||--o{ studio_subscriptions : "owner_id"
  generation_jobs ||--o| generation_deliveries : "job_id"
  studio_projects ||--o{ generation_deliveries : "project_id"
  studio_conversations ||--o{ generation_deliveries : "session_id"
  generation_development_budgets ||--o{ generation_jobs : "budget_id"
  studio_projects ||--o{ generation_jobs : "project_id"
  studio_conversations ||--o{ generation_jobs : "session_id"
  generation_jobs ||--o| generation_reservations : "budget_id"
  studio_conversations ||--o| maa_bindings : "conversation_id"
  maa_model_change_actions |o--o| maa_bindings : "pending_model_change_id"
  maa_interactions ||--o| maa_interaction_actions : "interaction_id"
  maa_runs ||--o{ maa_interactions : "public_run_id"
  maa_bindings ||--o{ maa_message_actions : "binding_id"
  maa_runs |o--o{ maa_message_actions : "public_run_id"
  maa_bindings ||--o{ maa_model_change_actions : "binding_id"
  maa_runs ||--o| maa_run_stop_actions : "public_run_id"
  maa_bindings ||--o{ maa_runs : "binding_id"
  studio_canvases ||--o{ studio_canvas_revisions : "project_id"
  studio_canvas_revisions |o--o{ studio_canvas_revisions : "restored_from_revision"
  studio_projects ||--o| studio_canvases : "project_id"
  studio_projects ||--o{ studio_conversations : "project_id"
  studio_media_jobs ||--o{ studio_credit_recovery_intents : "job_id"
  studio_projects ||--o{ studio_export_jobs : "project_id"
  studio_media_requests ||--o| studio_media_jobs : "request_id"
  studio_media_jobs ||--o{ studio_media_outputs : "job_id"
  studio_conversations ||--o{ studio_media_requests : "conversation_id"
  maa_runs |o--o{ studio_media_requests : "source_run_id"
  studio_media_jobs ||--o{ studio_media_submission_attempts : "job_id"
  studio_projects ||--o{ studio_project_asset_refs : "project_id"
  studio_video_documents ||--o{ studio_timeline_revisions : "project_id"
  studio_timeline_revisions |o--o{ studio_timeline_revisions : "restored_from_revision"
  studio_projects ||--o| studio_video_documents : "project_id"
  studio_assets ||--o{ studio_project_asset_refs : "asset_id"
  studio_assets ||--o{ studio_media_outputs : "asset_id"
  studio_assets ||--o{ generation_deliveries : "asset_id"
  studio_media_model_versions ||--o{ studio_media_requests : "model_version_id"
  studio_media_models ||--o{ studio_media_requests : "model_id"
  studio_credit_authorizations |o--o{ studio_media_jobs : "credit_authorization_id"
  studio_credit_authorizations |o--o{ studio_credit_recovery_intents : "authorization_id"
自动生成的整体图(按真实表结构,备查)
Creatify 品牌库 ER 图

共享身份库 ER 图

共享身份库 ER 图
实测验证与设计原因 七个品牌相同,展开查看

实测验证(本机 MySQL 实库)

对照本机真实运行的数据库 studio_profile_creatify 和 studio_platform_identity,逐项核对本页的表、列、关系与说明。18 / 18 项通过。

查看全部核对项
核对项结果
表数 = 发布值 24通过
列数 = 289通过
外键数 = 34通过
检查约束数 = 66通过
触发器数 = 22通过
索引数(含主键) = 85通过
列定义与 pollo 逐列一致通过
外键与 pollo 完全一致通过
索引与 pollo 一致通过
触发器与 pollo 一致通过
外键 CSV 行数 = 实库外键数通过
mmd 实体 = 实库表通过
svg 标题 24 tables / 34 actual foreign keys = 实库通过
参考 SQL 表集合与列顺序 = 实库通过
15 张表通过
外键 CSV 行数 = 实库外键数通过
svg 标题与实库一致通过
参考 SQL 表集合与列顺序 = 实库通过

实库里的关系是这样的

  • 34 个外键里 31 个是复合外键,且都带着 owner_id,其余 3 个是单列外键;22 张子表都带 owner 外键。所以一行数据不可能指向别的用户的项目或会话。
  • 删除与更新规则全部是 RESTRICT / NO ACTION,没有任何级联:被引用的行不能被删除。
  • 品牌库与共享身份库之间的关系(owner_id、asset_id、model_id + model_version_id)没有数据库外键,靠应用层检查;我们逐表核对了现有数据,没有孤儿。

主动破坏测试(在事务里尝试,全部回滚)

尝试数据库的反应
外键:导出任务.project_id 改成不存在的项目被拒绝拒绝
复合外键:项目素材引用.project_id 改成别的用户的项目被拒绝(owner 与 project 必须成对)拒绝
触发器:已提交的消息动作不可改写拒绝
触发器:媒体产出不可改写拒绝
外键 RESTRICT:被会话引用的项目不能删除拒绝
复合外键:被引用的会话不能把 owner 改成别人拒绝
检查约束:Run 状态只能是约定的六个值拒绝
触发器:账本分录不可修改拒绝
触发器:账本分录不可删除拒绝
验证范围的说明:这几个库里的数据量很小(每个品牌库约 70–270 行),孤儿检查能证明现有数据没有违反关系,不能证明大数据量下的表现;主动破坏测试在 Pollo 库上执行,另外六个品牌库的列、外键、索引、触发器已逐项核对与 Pollo 完全一致。
为什么这样设计外键用真实数据库约束表达,复合外键把 owner/project/conversation 一起带上,所以任何一行数据都不可能指向别人的项目。跨库关系不画外键而用应用层检查,是因为不同库之间数据库本身保证不了,画出来反而误导(决策 D12、D17)。

Creatify · 表结构

MySQL 8,字符集 utf8mb4,ID 为 char(36) ascii_bin。点击展开 DDL。

按职责分组

项目与会话 2 张
  • studio_projects:用户拥有的创作项目
  • studio_conversations:项目内的对话会话
Agent执行与并发控制 7 张
  • maa_bindings:业务会话与Native Maa会话映射、模型版本与未决操作
  • maa_runs:公开Run映射、状态与原生/公开助手证据摘要
  • maa_message_actions:原消息动作及幂等回执
  • maa_interactions:澄清卡片及Run归属
  • maa_interaction_actions:原卡片回答/取消动作
  • maa_model_change_actions:带预期模型版本的切换动作
  • maa_run_stop_actions:停止原Run的幂等动作与观测终态
画布、时间轴与素材引用 5 张
  • studio_canvases:项目当前画布文档
  • studio_canvas_revisions:历史画布修订
  • studio_video_documents:项目当前时间轴文档
  • studio_timeline_revisions:历史时间轴修订
  • studio_project_asset_refs:项目对用户级素材的引用;不复制素材所有权
媒体生成、结算恢复与导出 6 张
  • studio_media_requests:用户输入/Agent来源、报价、批准摘要与准入确认
  • studio_media_jobs:已批准媒体任务及状态机
  • studio_media_submission_attempts:一次提交的任务证据与不确定结果
  • studio_media_outputs:受验证的输出与素材映射
  • studio_credit_recovery_intents:积分结算恢复意图;不是用户钱包全账本
  • studio_export_jobs:时间轴导出队列和发布输出
既有generation兼容路径 22 张
  • generation_jobs:既有generation任务,不等同新PublicMedia任务
  • generation_reservations:既有任务费用预留
  • generation_development_budgets:既有开发测试预算
  • generation_deliveries:既有生成交付回执
  • owner_id:归属用户;不是允许跨用户查询的参数。
  • model_revision:并发准入版本;数据库默认0不能代替Native权威读回。
  • pending_model_change_id:未决模型切换引用,阻止其他writer绕过。
  • studio_users:用户账号记录;不导出密码哈希或任何业务行
  • studio_sessions:登录会话;这里只公开字段结构,不公开会话值
  • studio_assets:用户级素材元数据与存储键结构,项目仅引用素材ID
  • studio_media_models:公开媒体模型目录结构
  • studio_media_model_versions:媒体模型不可变版本/计价能力结构
  • studio_subscriptions:订阅状态持久化
  • studio_billing_cycles:订阅账期
  • studio_payment_orders:订单记录
  • studio_payment_events:支付事件幂等记录;不代表已有公开支付路由
  • studio_credit_accounts:用户积分账户
  • studio_credit_lots:积分批次
  • studio_credit_authorizations:费用预留与结算授权
  • studio_credit_authorization_allocations:授权对批次的分配
  • studio_credit_ledger_entries:积分流水
  • studio_credit_plan_versions:积分方案不可变版本

品牌库 studio_profile_creatify(24 张)

generation_deliveries 既有生成交付回执
CREATE TABLE `generation_deliveries` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `maa_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `tool_name` varchar(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_delivery_owner_project` (`owner_id`,`project_id`,`session_id`,`job_id`),
  KEY `generation_delivery_owned_asset` (`owner_id`,`asset_id`),
  KEY `generation_delivery_session_fk` (`session_id`),
  CONSTRAINT `generation_delivery_job_fk` FOREIGN KEY (`job_id`) REFERENCES `generation_jobs` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`, `job_id`) REFERENCES `generation_jobs` (`owner_id`, `project_id`, `session_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_session_fk` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_delivery_model_tool` CHECK ((((`model` = _ascii'seedance-2.0') and (`tool_name` = _ascii'seedance2_video_generator')) or ((`model` in (_ascii'minimax-h3',_ascii'seedance-2.5')) and (`tool_name` = _ascii'insai_video_generate'))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_development_budgets 既有开发测试预算
CREATE TABLE `generation_development_budgets` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_version` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits_limit` bigint unsigned NOT NULL,
  `credits_reserved` bigint unsigned NOT NULL DEFAULT '0',
  `calls_limit` int unsigned NOT NULL,
  `calls_reserved` int unsigned NOT NULL DEFAULT '0',
  `expires_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_budget_owner` (`owner_id`,`id`),
  CONSTRAINT `generation_budget_limits` CHECK (((`credits_limit` between 1 and 1000000000) and (`credits_reserved` <= `credits_limit`) and (`calls_limit` between 1 and 100) and (`calls_reserved` <= `calls_limit`))),
  CONSTRAINT `generation_budget_policy` CHECK ((`policy_version` = _latin1'operator-test/v1'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_jobs 既有generation任务,不等同新PublicMedia任务
CREATE TABLE `generation_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `session_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_conversation_id` char(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_run_id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `result_bytes` mediumblob,
  `updated_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `generation_job_budget_owner` (`id`,`budget_id`,`owner_id`),
  UNIQUE KEY `generation_job_owned_scope` (`owner_id`,`project_id`,`session_id`,`id`),
  KEY `generation_job_owner` (`owner_id`,`project_id`,`session_id`),
  KEY `generation_job_session` (`session_id`),
  KEY `generation_job_budget` (`owner_id`,`budget_id`),
  CONSTRAINT `generation_job_budget` FOREIGN KEY (`owner_id`, `budget_id`) REFERENCES `generation_development_budgets` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_project` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_owned_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `session_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_session` FOREIGN KEY (`session_id`) REFERENCES `studio_conversations` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_job_attempt` CHECK ((((`status` = _ascii'APPROVED') and (`attempts` = 0) and (`result_bytes` is null)) or ((`status` = _ascii'SUBMITTING') and (`attempts` = 1) and (`result_bytes` is null)) or ((`status` in (_ascii'SUCCEEDED',_ascii'FAILED',_ascii'UNKNOWN')) and (`attempts` = 1) and (`result_bytes` is not null)))),
  CONSTRAINT `generation_job_credit` CHECK ((`credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
generation_reservations 既有任务费用预留
CREATE TABLE `generation_reservations` (
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `budget_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `calls` int unsigned NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  PRIMARY KEY (`job_id`),
  KEY `generation_reservation_job` (`job_id`,`budget_id`,`owner_id`),
  CONSTRAINT `generation_reservation_job` FOREIGN KEY (`job_id`, `budget_id`, `owner_id`) REFERENCES `generation_jobs` (`id`, `budget_id`, `owner_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `generation_reservation_fixed` CHECK (((`credits` between 1 and 1000000000) and (`calls` = 1) and (`status` = _latin1'HELD')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_bindings 业务会话与Native Maa会话映射、模型版本与未决操作
CREATE TABLE `maa_bindings` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `model_revision` bigint unsigned NOT NULL DEFAULT '0',
  `pending_model_change_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_binding_business_scope` (`owner_id`,`project_id`,`conversation_id`),
  UNIQUE KEY `maa_binding_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `maa_binding_native_identity` (`native_cid`),
  KEY `maa_model_change_pending_fk` (`owner_id`,`project_id`,`conversation_id`,`id`,`pending_model_change_id`),
  CONSTRAINT `maa_binding_owned_conversation_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_pending_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `id`, `pending_model_change_id`) REFERENCES `maa_model_change_actions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_binding_receipt` CHECK ((((`status` = _ascii'dispatching') and (`native_cid` is null)) or ((`status` = _ascii'bound') and (`native_cid` is not null)))),
  CONSTRAINT `maa_model_binding_revision` CHECK ((`model_revision` <= 9007199254740991))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interaction_actions 原卡片回答/取消动作
CREATE TABLE `maa_interaction_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `interaction_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `response_action` varchar(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `values_payload` json NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_action_business_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_interaction_action_one_card` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`interaction_id`),
  CONSTRAINT `maa_interaction_action_owned_card_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `interaction_id`) REFERENCES `maa_interactions` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_interaction_action_kind` CHECK ((`response_action` in (_latin1'submit',_latin1'cancel'))),
  CONSTRAINT `maa_interaction_action_status` CHECK ((`status` in (_latin1'dispatching',_latin1'accepted')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_interactions 澄清卡片及Run归属
CREATE TABLE `maa_interactions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_iid` varchar(124) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `card_payload` json NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_interaction_native_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`native_iid`),
  UNIQUE KEY `maa_interaction_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`,`id`),
  CONSTRAINT `maa_interaction_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_message_actions 原消息动作及幂等回执
CREATE TABLE `maa_message_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `proposed_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_action_business_identity` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  KEY `maa_action_owned_run_fk` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_action_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_action_receipt` CHECK ((((`status` = _latin1'dispatching') and (`public_run_id` is null)) or ((`status` = _latin1'accepted') and (`public_run_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_model_change_actions 带预期模型版本的切换动作
CREATE TABLE `maa_model_change_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `expected_revision` bigint unsigned NOT NULL,
  `from_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `from_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_provider` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `target_model_name` varchar(240) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_attempt` bigint unsigned NOT NULL,
  `receipt_payload` json DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_model_change_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_model_change_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  CONSTRAINT `maa_model_change_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_model_change_attempt` CHECK ((`dispatch_attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_model_change_expected` CHECK ((`expected_revision` <= 9007199254740990)),
  CONSTRAINT `maa_model_change_status` CHECK ((((cast(`status` as char charset binary) = _latin1'dispatching') and (`receipt_payload` is null)) or ((cast(`status` as char charset binary) in (_latin1'accepted',_latin1'rejected')) and (`receipt_payload` is not null) and (json_type(`receipt_payload`) = _latin1'OBJECT')))),
  CONSTRAINT `maa_model_change_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_run_stop_actions 停止原Run的幂等动作与观测终态
CREATE TABLE `maa_run_stop_actions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `public_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_cid` varchar(41) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stop_action` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt` bigint unsigned NOT NULL,
  `terminal_run_status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_stop_owned_key` (`owner_id`,`project_id`,`conversation_id`,`client_action_id`),
  UNIQUE KEY `maa_stop_one_run` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`public_run_id`),
  CONSTRAINT `maa_stop_owned_run_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `public_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_stop_action` CHECK ((`stop_action` = _latin1'interrupt')),
  CONSTRAINT `maa_stop_attempt` CHECK ((`attempt` between 1 and 9007199254740991)),
  CONSTRAINT `maa_stop_state` CHECK ((((`status` = _latin1'unknown') and (`terminal_run_status` is null)) or ((`status` = _latin1'terminal_observed') and (`terminal_run_status` is not null) and (`terminal_run_status` in (_latin1'completed',_latin1'failed',_latin1'cancelled'))))),
  CONSTRAINT `maa_stop_time` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
maa_runs 公开Run映射、状态与原生/公开助手证据摘要
CREATE TABLE `maa_runs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `native_rid` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `native_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `public_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `native_assistant_bytes` bigint unsigned NOT NULL DEFAULT '0',
  `assistant_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  `last_event_seq` bigint unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `maa_run_native_identity` (`native_rid`),
  UNIQUE KEY `maa_run_owned_identity` (`owner_id`,`project_id`,`conversation_id`,`binding_id`,`id`),
  KEY `maa_run_owned_list` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  CONSTRAINT `maa_run_owned_binding_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `binding_id`) REFERENCES `maa_bindings` (`owner_id`, `project_id`, `conversation_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `maa_run_source_evidence` CHECK ((((`native_assistant_sha256` is null) and (`public_assistant_sha256` is null) and (`native_assistant_bytes` = 0) and (`assistant_event_seq` = 0)) or ((`native_assistant_sha256` is not null) and (`public_assistant_sha256` is not null) and (`native_assistant_bytes` > 0) and (`assistant_event_seq` > 0) and (`assistant_event_seq` <= `last_event_seq`)))),
  CONSTRAINT `maa_run_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'waiting_interaction',_latin1'completed',_latin1'failed',_latin1'cancelled')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvas_revisions 历史画布修订
CREATE TABLE `studio_canvas_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_canvas_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_canvas_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_canvases` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_canvas_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_canvas_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_canvas_revision_kind` CHECK ((`kind` = _latin1'canvas')),
  CONSTRAINT `studio_canvas_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_canvas_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_canvas_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_canvases 项目当前画布文档
CREATE TABLE `studio_canvases` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_canvas_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_canvas_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_canvas_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_conversations 项目内的对话会话
CREATE TABLE `studio_conversations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_conversation_owned_scope` (`owner_id`,`project_id`,`id`),
  KEY `studio_conversation_list` (`owner_id`,`project_id`,`created_at`,`id`),
  CONSTRAINT `studio_conversation_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_recovery_intents 积分结算恢复意图;不是用户钱包全账本
CREATE TABLE `studio_credit_recovery_intents` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `intent_bytes` mediumblob NOT NULL,
  `intent_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_intent_job_kind` (`job_id`,`kind`),
  KEY `credit_intent_recovery` (`state`,`updated_at`,`id`),
  KEY `credit_intent_owned_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `credit_intent_owned_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `credit_intent_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_intent_kind` CHECK (((`kind` in (_latin1'RESERVE_BIND',_latin1'CAPTURE',_latin1'RELEASE')) and (((`kind` = _latin1'RESERVE_BIND') and (`authorization_id` is null)) or ((`kind` in (_latin1'CAPTURE',_latin1'RELEASE')) and (`authorization_id` is not null))))),
  CONSTRAINT `credit_intent_lease` CHECK ((((`state` = _latin1'leased') and (`lease_token_sha256` is not null) and regexp_like(`lease_token_sha256`,_latin1'^[a-f0-9]{64}$') and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`state` <> _latin1'leased') and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `credit_intent_payload_sha` CHECK ((`intent_sha256` = lower(sha2(`intent_bytes`,256)))),
  CONSTRAINT `credit_intent_payload_size` CHECK ((length(`intent_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_intent_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_intent_state` CHECK ((`state` in (_latin1'pending',_latin1'leased',_latin1'confirmed',_latin1'unknown'))),
  CONSTRAINT `credit_intent_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_export_jobs 时间轴导出队列和发布输出
CREATE TABLE `studio_export_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `document_revision` bigint unsigned NOT NULL,
  `input_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `input_json` json NOT NULL,
  `output_json` json DEFAULT NULL,
  `error_code` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
  `lease_token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `attempts` int unsigned NOT NULL DEFAULT '0',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_export_input` (`project_id`,`document_revision`,`input_hash`),
  KEY `studio_export_claim` (`status`,`created_at`,`id`),
  KEY `studio_export_owner_list` (`owner_id`,`project_id`,`created_at`,`id`),
  KEY `studio_export_expiry` (`status`,`lease_until`),
  CONSTRAINT `studio_export_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_export_lease` CHECK ((((`status` = _latin1'running') and (`lease_token_hash` is not null) and (`lease_until` is not null)) or ((`status` <> _latin1'running') and (`lease_token_hash` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_export_output` CHECK ((((`status` = _latin1'succeeded') and (`output_json` is not null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'queued',_latin1'running')) and (`output_json` is null) and (`error_code` = _latin1'')) or ((`status` in (_latin1'failed',_latin1'unknown')) and (`output_json` is null) and (`error_code` <> _latin1'')))),
  CONSTRAINT `studio_export_revision` CHECK ((`document_revision` between 1 and 2147483647)),
  CONSTRAINT `studio_export_status` CHECK ((`status` in (_latin1'queued',_latin1'running',_latin1'succeeded',_latin1'failed',_latin1'unknown')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_jobs 已批准媒体任务及状态机
CREATE TABLE `studio_media_jobs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_approval_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `approval_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credit_authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_mode` varchar(24) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `authorization_expires_at` datetime(6) DEFAULT NULL,
  `status` varchar(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `lease_token_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `lease_until` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `client_cancel_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `cancel_requested_at` datetime(6) DEFAULT NULL,
  `staged_asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `staged_asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_job_request` (`request_id`),
  UNIQUE KEY `studio_media_job_client` (`owner_id`,`client_approval_id`),
  UNIQUE KEY `studio_media_job_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_job_owned_request` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`),
  KEY `studio_media_job_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_job_queue` (`status`,`updated_at`,`id`),
  KEY `studio_media_job_request_fk` (`owner_id`,`project_id`,`conversation_id`,`request_id`,`request_sha256`,`quote_sha256`),
  CONSTRAINT `studio_media_job_request_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `request_id`, `request_sha256`, `quote_sha256`) REFERENCES `studio_media_requests` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`, `quote_sha256`),
  CONSTRAINT `studio_media_job_authorization` CHECK ((((`credit_authorization_id` is null) and (`authorization_mode` is null) and (`authorization_expires_at` is null) and (`status` in (_latin1'approval_pending',_latin1'cancelled'))) or ((`credit_authorization_id` is not null) and (`authorization_mode` is not null) and (`authorization_mode` in (_latin1'commercial',_latin1'test_allowance')) and (`authorization_expires_at` is not null) and (`authorization_expires_at` > `created_at`) and (`status` <> _latin1'approval_pending')))),
  CONSTRAINT `studio_media_job_cancel` CHECK ((((`client_cancel_id` is null) and (`cancel_request_sha256` is null) and (`cancel_requested_at` is null)) or ((`client_cancel_id` is not null) and (`cancel_request_sha256` is not null) and (`cancel_requested_at` is not null) and (`cancel_requested_at` between `created_at` and `updated_at`)))),
  CONSTRAINT `studio_media_job_lease` CHECK ((((`status` in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is not null) and (`lease_until` is not null) and (`lease_until` > `updated_at`) and (`lease_until` <= (`updated_at` + interval 15 minute))) or ((`status` not in (_latin1'leased',_latin1'submitting')) and (`lease_token_sha256` is null) and (`lease_until` is null)))),
  CONSTRAINT `studio_media_job_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_job_staged` CHECK ((((`staged_asset_id` is null) and (`staged_asset_sha256` is null)) or ((`staged_asset_id` is not null) and (`staged_asset_sha256` is not null)))),
  CONSTRAINT `studio_media_job_status` CHECK ((`status` in (_latin1'approval_pending',_latin1'queued',_latin1'leased',_latin1'submitting',_latin1'running',_latin1'submission_unknown',_latin1'succeeded',_latin1'failed',_latin1'cancelled'))),
  CONSTRAINT `studio_media_job_task` CHECK (((`status` not in (_latin1'running',_latin1'succeeded')) or (`provider_task_id` is not null))),
  CONSTRAINT `studio_media_job_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_outputs 受验证的输出与素材映射
CREATE TABLE `studio_media_outputs` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `ordinal` int unsigned NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_result_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_output_job` (`job_id`,`ordinal`),
  KEY `studio_media_output_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`),
  CONSTRAINT `studio_media_output_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`),
  CONSTRAINT `studio_media_output_ordinal` CHECK ((`ordinal` = 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_requests 用户输入/Agent来源、报价、批准摘要与准入确认
CREATE TABLE `studio_media_requests` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_bytes` mediumblob NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_bytes` mediumblob NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quoted_credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_binding_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_run_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_assistant_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_snapshot_bytes` mediumblob,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_request_owned` (`owner_id`,`project_id`,`conversation_id`,`id`),
  UNIQUE KEY `studio_media_request_quoted` (`owner_id`,`project_id`,`conversation_id`,`id`,`request_sha256`,`quote_sha256`),
  UNIQUE KEY `studio_media_request_client` (`owner_id`,`project_id`,`conversation_id`,`client_request_id`),
  KEY `studio_media_request_page` (`owner_id`,`project_id`,`conversation_id`,`created_at`,`id`),
  KEY `studio_media_request_source_fk` (`owner_id`,`project_id`,`conversation_id`,`source_binding_id`,`source_run_id`),
  CONSTRAINT `studio_media_request_session_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`) REFERENCES `studio_conversations` (`owner_id`, `project_id`, `id`),
  CONSTRAINT `studio_media_request_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `source_binding_id`, `source_run_id`) REFERENCES `maa_runs` (`owner_id`, `project_id`, `conversation_id`, `binding_id`, `id`),
  CONSTRAINT `studio_media_quote_credits` CHECK ((`quoted_credits` between 1 and 1000000000)),
  CONSTRAINT `studio_media_quote_sha` CHECK ((`quote_sha256` = lower(sha2(`quote_bytes`,256)))),
  CONSTRAINT `studio_media_quote_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)))),
  CONSTRAINT `studio_media_request_origin` CHECK ((((`origin` in (_latin1'ui',_latin1'user_edit')) and (`source_binding_id` is null) and (`source_run_id` is null) and (`source_assistant_sha256` is null) and (`source_snapshot_bytes` is null)) or ((`origin` = _latin1'agent') and (`source_binding_id` is not null) and (`source_run_id` is not null) and (`source_assistant_sha256` is not null) and (`source_snapshot_bytes` is not null) and (length(`source_snapshot_bytes`) between 1 and 65536) and (`source_assistant_sha256` = lower(sha2(`source_snapshot_bytes`,256)))))),
  CONSTRAINT `studio_media_request_sha` CHECK ((`request_sha256` = lower(sha2(`request_bytes`,256)))),
  CONSTRAINT `studio_media_request_size` CHECK (((length(`request_bytes`) between 1 and 65536) and (length(`quote_bytes`) between 1 and 65536)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_submission_attempts 一次提交的任务证据与不确定结果
CREATE TABLE `studio_media_submission_attempts` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempt_no` int unsigned NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `stage` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `dispatch_started_at` datetime(6) DEFAULT NULL,
  `provider_task_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `terminal_receipt_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `reconciliation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_attempt_job` (`job_id`,`attempt_no`),
  UNIQUE KEY `studio_media_attempt_idempotency` (`provider_idempotency_key`),
  KEY `studio_media_attempt_job_fk` (`owner_id`,`project_id`,`conversation_id`,`job_id`,`request_sha256`),
  CONSTRAINT `studio_media_attempt_job_fk` FOREIGN KEY (`owner_id`, `project_id`, `conversation_id`, `job_id`, `request_sha256`) REFERENCES `studio_media_jobs` (`owner_id`, `project_id`, `conversation_id`, `id`, `request_sha256`),
  CONSTRAINT `studio_media_attempt_number` CHECK ((`attempt_no` = 1)),
  CONSTRAINT `studio_media_attempt_state` CHECK ((((`stage` = _latin1'prepared') and (`dispatch_started_at` is null) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'dispatching',_latin1'unknown')) and (`dispatch_started_at` is not null) and (`dispatch_started_at` >= `created_at`) and (`provider_task_id` is null) and (`receipt_sha256` is null) and (`reconciliation_sha256` is null) and (`terminal_receipt_sha256` is null)) or ((`stage` = _latin1'acknowledged') and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is null)) or ((`stage` in (_latin1'completed',_latin1'failed')) and (`dispatch_started_at` is not null) and (`provider_task_id` is not null) and (`receipt_sha256` is not null) and (`terminal_receipt_sha256` is not null)) or ((`stage` = _latin1'reconciled_not_sent') and (`dispatch_started_at` is not null) and (`provider_task_id` is null) and (`reconciliation_sha256` is not null)))),
  CONSTRAINT `studio_media_attempt_times` CHECK (((`updated_at` >= `created_at`) and ((`dispatch_started_at` is null) or (`dispatch_started_at` between `created_at` and `updated_at`))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_project_asset_refs 项目对用户级素材的引用;不复制素材所有权
CREATE TABLE `studio_project_asset_refs` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `asset_metadata_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `detached_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`project_id`,`asset_id`),
  KEY `studio_project_asset_owned_page` (`owner_id`,`project_id`,`asset_id`),
  KEY `studio_project_asset_reverse` (`owner_id`,`asset_id`,`project_id`),
  CONSTRAINT `studio_project_asset_owned_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_project_asset_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_project_asset_state` CHECK ((((`state` = _latin1'attached') and (`detached_at` is null)) or ((`state` = _latin1'detached') and (`detached_at` is not null) and (`detached_at` = `updated_at`)))),
  CONSTRAINT `studio_project_asset_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_projects 用户拥有的创作项目
CREATE TABLE `studio_projects` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `title` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_project_owner_identity` (`owner_id`,`id`),
  KEY `studio_project_owner_list` (`owner_id`,`created_at`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_timeline_revisions 历史时间轴修订
CREATE TABLE `studio_timeline_revisions` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_bytes` mediumblob NOT NULL,
  `state_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `origin` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `restored_from_revision` bigint unsigned DEFAULT NULL,
  `recorded_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`,`project_id`,`kind`,`revision`),
  KEY `studio_timeline_revision_source_fk` (`owner_id`,`project_id`,`kind`,`restored_from_revision`),
  CONSTRAINT `studio_timeline_revision_head_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_video_documents` (`owner_id`, `project_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_timeline_revision_source_fk` FOREIGN KEY (`owner_id`, `project_id`, `kind`, `restored_from_revision`) REFERENCES `studio_timeline_revisions` (`owner_id`, `project_id`, `kind`, `revision`),
  CONSTRAINT `studio_timeline_revision_bytes` CHECK ((length(`state_bytes`) between 1 and 262144)),
  CONSTRAINT `studio_timeline_revision_kind` CHECK ((`kind` = _latin1'timeline')),
  CONSTRAINT `studio_timeline_revision_number` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_timeline_revision_origin` CHECK ((((`origin` in (_latin1'saved',_latin1'backfill')) and (`restored_from_revision` is null)) or ((`origin` = _latin1'restored') and (`restored_from_revision` is not null) and (`restored_from_revision` between 1 and (`revision` - 1))))),
  CONSTRAINT `studio_timeline_revision_sha` CHECK ((`state_sha256` = lower(sha2(`state_bytes`,256))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_video_documents 项目当前时间轴文档
CREATE TABLE `studio_video_documents` (
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `state_json` json NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `studio_timeline_owned_head` (`owner_id`,`project_id`),
  CONSTRAINT `studio_document_project_fk` FOREIGN KEY (`owner_id`, `project_id`) REFERENCES `studio_projects` (`owner_id`, `id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_document_revision` CHECK ((`revision` between 1 and 2147483647))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
共享身份库(15 张)与积分账本 七个品牌相同,展开查看
studio_assets
CREATE TABLE `studio_assets` (
  `id` char(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `metadata_json` json NOT NULL,
  `storage_key` varchar(512) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `publication_state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT 'visible',
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_asset_owner_digest` (`owner_id`,`sha256`),
  KEY `studio_asset_owner_list` (`owner_id`,`created_at`,`id`),
  KEY `studio_asset_visible_list` (`owner_id`,`publication_state`,`created_at`,`id`),
  CONSTRAINT `studio_asset_publication_state` CHECK (CAST(publication_state AS BINARY) IN ('pending','visible')),
  CONSTRAINT `studio_asset_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_billing_cycles
CREATE TABLE `studio_billing_cycles` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `payment_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_invoice_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `billing_cycle_owned` (`owner_id`,`id`),
  UNIQUE KEY `billing_cycle_period` (`subscription_id`,`period_start`),
  UNIQUE KEY `billing_cycle_invoice` (`provider_key`,`provider_account`,`provider_invoice_id`),
  UNIQUE KEY `billing_cycle_order` (`payment_order_id`),
  KEY `billing_cycle_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  KEY `billing_cycle_order_fk` (`owner_id`,`payment_order_id`,`plan_version_id`),
  KEY `billing_cycle_order_provider_fk` (`owner_id`,`payment_order_id`,`provider_key`,`provider_account`),
  CONSTRAINT `billing_cycle_order_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `plan_version_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `plan_version_id`),
  CONSTRAINT `billing_cycle_order_provider_fk` FOREIGN KEY (`owner_id`, `payment_order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `billing_cycle_amount` CHECK ((`grant_credits` between 1 and 1000000000)),
  CONSTRAINT `billing_cycle_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `billing_cycle_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `billing_cycle_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `billing_cycle_state` CHECK (((`state` in (_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `billing_cycle_times` CHECK (((`period_end` > `period_start`) and (`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_accounts
CREATE TABLE `studio_credit_accounts` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `unit` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`owner_id`),
  CONSTRAINT `credit_account_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `credit_account_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_account_status` CHECK ((`status` in (_latin1'open',_latin1'suspended'))),
  CONSTRAINT `credit_account_unit` CHECK ((`unit` = _latin1'credits'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorization_allocations
CREATE TABLE `studio_credit_authorization_allocations` (
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `allocated_credits` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`authorization_id`,`lot_id`),
  UNIQUE KEY `credit_allocation_owned` (`owner_id`,`authorization_id`,`lot_id`),
  KEY `credit_allocation_lot` (`owner_id`,`lot_id`,`authorization_id`),
  CONSTRAINT `credit_allocation_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_allocation_amount` CHECK ((`allocated_credits` between 1 and 1000000000))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_authorizations
CREATE TABLE `studio_credit_authorizations` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `profile` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `project_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `conversation_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `job_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `request_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `quote_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `credits` bigint unsigned NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `reservation_bytes` mediumblob NOT NULL,
  `reservation_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `policy_bytes` mediumblob NOT NULL,
  `policy_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_authorization_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_authorization_job` (`owner_id`,`profile`,`job_id`),
  CONSTRAINT `credit_authorization_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_authorization_amount` CHECK ((`credits` between 1 and 1000000000)),
  CONSTRAINT `credit_authorization_hashes` CHECK ((regexp_like(`request_sha256`,_latin1'^[a-f0-9]{64}$') and regexp_like(`quote_sha256`,_latin1'^[a-f0-9]{64}$'))),
  CONSTRAINT `credit_authorization_policy_sha` CHECK ((`policy_sha256` = lower(sha2(`policy_bytes`,256)))),
  CONSTRAINT `credit_authorization_policy_size` CHECK ((length(`policy_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_profile` CHECK ((`profile` in (_latin1'topview',_latin1'pollo',_latin1'invideo',_latin1'libtv',_latin1'flova',_latin1'creatok',_latin1'creatify'))),
  CONSTRAINT `credit_authorization_reservation_sha` CHECK ((`reservation_sha256` = lower(sha2(`reservation_bytes`,256)))),
  CONSTRAINT `credit_authorization_reservation_size` CHECK ((length(`reservation_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_authorization_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_authorization_state` CHECK ((`state` in (_latin1'HELD',_latin1'CAPTURED',_latin1'RELEASED'))),
  CONSTRAINT `credit_authorization_times` CHECK (((`expires_at` > `created_at`) and (`expires_at` <= (`created_at` + interval 15 minute)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_ledger_entries
CREATE TABLE `studio_credit_ledger_entries` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `lot_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `authorization_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `operation_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_delta` bigint NOT NULL,
  `held_delta` bigint NOT NULL,
  `captured_delta` bigint NOT NULL,
  `facts_bytes` mediumblob NOT NULL,
  `facts_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_entry_operation` (`owner_id`,`operation_key`,`lot_id`),
  KEY `credit_entry_page` (`owner_id`,`created_at`,`id`),
  KEY `credit_entry_lot_fk` (`owner_id`,`lot_id`),
  KEY `credit_entry_allocation_fk` (`owner_id`,`authorization_id`,`lot_id`),
  CONSTRAINT `credit_entry_allocation_fk` FOREIGN KEY (`owner_id`, `authorization_id`, `lot_id`) REFERENCES `studio_credit_authorization_allocations` (`owner_id`, `authorization_id`, `lot_id`),
  CONSTRAINT `credit_entry_authorization_fk` FOREIGN KEY (`owner_id`, `authorization_id`) REFERENCES `studio_credit_authorizations` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_lot_fk` FOREIGN KEY (`owner_id`, `lot_id`) REFERENCES `studio_credit_lots` (`owner_id`, `id`),
  CONSTRAINT `credit_entry_delta` CHECK ((((`kind` = _latin1'grant') and (`authorization_id` is null) and (`grant_delta` between 1 and 1000000000) and (`held_delta` = 0) and (`captured_delta` = 0)) or ((`kind` = _latin1'hold') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between 1 and 1000000000) and (`captured_delta` = 0)) or ((`kind` = _latin1'capture') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`captured_delta` between 1 and 1000000000) and (`held_delta` = -(`captured_delta`))) or ((`kind` = _latin1'release') and (`authorization_id` is not null) and (`grant_delta` = 0) and (`held_delta` between -(1000000000) and -(1)) and (`captured_delta` = 0)))),
  CONSTRAINT `credit_entry_facts_sha` CHECK ((`facts_sha256` = lower(sha2(`facts_bytes`,256)))),
  CONSTRAINT `credit_entry_facts_size` CHECK ((length(`facts_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_entry_operation_key` CHECK ((((`kind` = _latin1'grant') and (`operation_key` = concat(`lot_id`,_latin1':grant'))) or ((`kind` <> _latin1'grant') and (`operation_key` = concat(`authorization_id`,_latin1':',`kind`)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_lots
CREATE TABLE `studio_credit_lots` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `held_credits` bigint unsigned NOT NULL,
  `captured_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `source_order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `source_cycle_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_lot_owned` (`owner_id`,`id`),
  UNIQUE KEY `credit_lot_order` (`source_order_id`),
  UNIQUE KEY `credit_lot_cycle` (`source_cycle_id`),
  KEY `credit_lot_eligible` (`owner_id`,`expires_at`,`id`),
  KEY `credit_lot_order_fk` (`owner_id`,`source_order_id`),
  KEY `credit_lot_cycle_fk` (`owner_id`,`source_cycle_id`),
  CONSTRAINT `credit_lot_account_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_credit_accounts` (`owner_id`),
  CONSTRAINT `credit_lot_cycle_fk` FOREIGN KEY (`owner_id`, `source_cycle_id`) REFERENCES `studio_billing_cycles` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_order_fk` FOREIGN KEY (`owner_id`, `source_order_id`) REFERENCES `studio_payment_orders` (`owner_id`, `id`),
  CONSTRAINT `credit_lot_counters` CHECK (((`grant_credits` between 1 and 1000000000) and (`held_credits` <= 1000000000) and (`captured_credits` <= 1000000000) and ((`held_credits` + `captured_credits`) <= `grant_credits`))),
  CONSTRAINT `credit_lot_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_lot_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_lot_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `credit_lot_source` CHECK ((((`source_order_id` is not null) and (`source_cycle_id` is null)) or ((`source_order_id` is null) and (`source_cycle_id` is not null)))),
  CONSTRAINT `credit_lot_times` CHECK (((`expires_at` > `valid_from`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_credit_plan_versions
CREATE TABLE `studio_credit_plan_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `credit_validity_seconds` int unsigned NOT NULL,
  `billing_interval_months` int unsigned NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  `retired_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `credit_plan_version_key` (`plan_key`,`version_key`),
  CONSTRAINT `credit_plan_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `credit_plan_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `credit_plan_duration` CHECK (((`credit_validity_seconds` between 1 and 31622400) and ((`retired_at` is null) or (`retired_at` >= `published_at`)))),
  CONSTRAINT `credit_plan_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `credit_plan_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `credit_plan_kind` CHECK (((`kind` in (_latin1'subscription',_latin1'topup')) and (((`kind` = _latin1'subscription') and (`billing_interval_months` in (1,3,12))) or ((`kind` = _latin1'topup') and (`billing_interval_months` = 0)))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_model_versions
CREATE TABLE `studio_media_model_versions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `version_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `config_bytes` mediumblob NOT NULL,
  `config_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_version_key` (`model_id`,`version_key`),
  UNIQUE KEY `studio_media_version_owned_model` (`model_id`,`id`),
  CONSTRAINT `studio_media_version_model_fk` FOREIGN KEY (`model_id`) REFERENCES `studio_media_models` (`id`),
  CONSTRAINT `studio_media_version_sha` CHECK ((`config_sha256` = lower(sha2(`config_bytes`,256)))),
  CONSTRAINT `studio_media_version_size` CHECK ((length(`config_bytes`) between 1 and 65536))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_media_models
CREATE TABLE `studio_media_models` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `model_key` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `kind` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `display_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `status` varchar(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `published_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_media_model_key` (`model_key`),
  KEY `studio_media_model_published_fk` (`id`,`published_version_id`),
  CONSTRAINT `studio_media_model_published_fk` FOREIGN KEY (`id`, `published_version_id`) REFERENCES `studio_media_model_versions` (`model_id`, `id`),
  CONSTRAINT `studio_media_model_kind` CHECK ((`kind` = _ascii'video')),
  CONSTRAINT `studio_media_model_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `studio_media_model_status` CHECK (((`status` in (_ascii'draft',_ascii'active',_ascii'disabled')) and ((`status` <> _ascii'active') or (`published_version_id` is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_events
CREATE TABLE `studio_payment_events` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_event_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `order_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `subscription_id` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `body_bytes` mediumblob NOT NULL,
  `body_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_event_provider` (`provider_key`,`provider_account`,`provider_event_id`),
  KEY `payment_event_order_fk` (`owner_id`,`order_id`,`provider_key`,`provider_account`),
  KEY `payment_event_subscription_fk` (`owner_id`,`subscription_id`,`provider_key`,`provider_account`),
  CONSTRAINT `payment_event_order_fk` FOREIGN KEY (`owner_id`, `order_id`, `provider_key`, `provider_account`) REFERENCES `studio_payment_orders` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_subscription_fk` FOREIGN KEY (`owner_id`, `subscription_id`, `provider_key`, `provider_account`) REFERENCES `studio_subscriptions` (`owner_id`, `id`, `provider_key`, `provider_account`),
  CONSTRAINT `payment_event_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_event_body_sha` CHECK ((`body_sha256` = lower(sha2(`body_bytes`,256)))),
  CONSTRAINT `payment_event_body_size` CHECK ((length(`body_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_event_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_event_state` CHECK ((`state` in (_latin1'VERIFIED',_latin1'PROCESSED'))),
  CONSTRAINT `payment_event_subject` CHECK (((`order_id` is not null) or (`subscription_id` is not null))),
  CONSTRAINT `payment_event_times` CHECK ((`updated_at` >= `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_payment_orders
CREATE TABLE `studio_payment_orders` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `client_action_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `price_minor` bigint unsigned NOT NULL,
  `currency` char(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `grant_credits` bigint unsigned NOT NULL,
  `valid_from` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `eligibility_bytes` mediumblob NOT NULL,
  `eligibility_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_idempotency_key` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_order_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `paid_evidence_sha256` char(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `payment_order_owned` (`owner_id`,`id`),
  UNIQUE KEY `payment_order_owned_plan` (`owner_id`,`id`,`plan_version_id`),
  UNIQUE KEY `payment_order_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `payment_order_client` (`owner_id`,`client_action_id`),
  UNIQUE KEY `payment_order_provider_key` (`provider_key`,`provider_account`,`provider_idempotency_key`),
  UNIQUE KEY `payment_order_provider_id` (`provider_key`,`provider_account`,`provider_order_id`),
  KEY `payment_order_plan_fk` (`plan_version_id`),
  CONSTRAINT `payment_order_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `payment_order_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `payment_order_amount` CHECK (((`price_minor` between 1 and 1000000000000) and (`grant_credits` between 1 and 1000000000))),
  CONSTRAINT `payment_order_currency` CHECK (regexp_like(`currency`,_latin1'^[A-Z]{3}$')),
  CONSTRAINT `payment_order_eligibility_sha` CHECK ((`eligibility_sha256` = lower(sha2(`eligibility_bytes`,256)))),
  CONSTRAINT `payment_order_eligibility_size` CHECK ((length(`eligibility_bytes`) between 1 and 65536)),
  CONSTRAINT `payment_order_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `payment_order_state` CHECK (((`state` in (_latin1'CREATING',_latin1'CREATE_UNKNOWN',_latin1'PENDING',_latin1'PAID',_latin1'CLOSED')) and (((`state` = _latin1'PAID') and (`provider_order_id` is not null) and (`paid_evidence_sha256` is not null) and regexp_like(`paid_evidence_sha256`,_latin1'^[a-f0-9]{64}$')) or ((`state` <> _latin1'PAID') and (`paid_evidence_sha256` is null))))),
  CONSTRAINT `payment_order_times` CHECK (((`expires_at` > `valid_from`) and (`expires_at` <= (`valid_from` + interval 31622400 second)) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_sessions
CREATE TABLE `studio_sessions` (
  `token_hash` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `user_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `expires_at` datetime(6) NOT NULL,
  `revoked_at` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`token_hash`),
  KEY `studio_session_expiry` (`expires_at`),
  KEY `studio_session_user` (`user_id`,`expires_at`),
  CONSTRAINT `studio_session_user_fk` FOREIGN KEY (`user_id`) REFERENCES `studio_users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `studio_session_times` CHECK ((`expires_at` > `created_at`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_subscriptions
CREATE TABLE `studio_subscriptions` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `owner_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_key` varchar(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_account` varchar(80) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `provider_subscription_id` varchar(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `plan_version_id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `state` varchar(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `period_start` datetime(6) NOT NULL,
  `period_end` datetime(6) NOT NULL,
  `cancel_at_period_end` tinyint(1) NOT NULL,
  `revision` bigint unsigned NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `subscription_owned` (`owner_id`,`id`),
  UNIQUE KEY `subscription_owned_provider` (`owner_id`,`id`,`provider_key`,`provider_account`),
  UNIQUE KEY `subscription_provider` (`provider_key`,`provider_account`,`provider_subscription_id`),
  KEY `subscription_plan_fk` (`plan_version_id`),
  CONSTRAINT `subscription_plan_fk` FOREIGN KEY (`plan_version_id`) REFERENCES `studio_credit_plan_versions` (`id`),
  CONSTRAINT `subscription_user_fk` FOREIGN KEY (`owner_id`) REFERENCES `studio_users` (`id`),
  CONSTRAINT `subscription_revision` CHECK ((`revision` between 1 and 2147483647)),
  CONSTRAINT `subscription_state` CHECK (((`state` in (_latin1'PENDING',_latin1'ACTIVE',_latin1'PAST_DUE',_latin1'CANCELLED',_latin1'ENDED')) and (`cancel_at_period_end` in (0,1)))),
  CONSTRAINT `subscription_times` CHECK (((`period_end` > `period_start`) and (`updated_at` >= `created_at`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
studio_users
CREATE TABLE `studio_users` (
  `id` char(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `email` varchar(254) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `password_hash` varbinary(72) NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT '1',
  `created_at` datetime(6) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `studio_user_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

积分账本链路

plan_versions → payment_orders(CREATING→PAID)→ payment_events(VERIFIED)→ credit_lots(带适用模型与品牌)→ ledger_entries(grant / hold / capture / release,只追加)→ authorizations / allocations。批准生成时按批次分配冻结额度,扣费与释放都有幂等键。
为什么这样设计账本只追加、用触发器禁止修改,才有可信的对账;批准时按批次分配冻结额度,才能在失败时准确释放。约束写在数据库里而不是应用里,是因为应用会有 bug,而约束不会(决策 D3、D17)。

Creatify · 接口清单

共 55 个公开接口,取自源码路由盘点。路径都以 /v1 开头;写操作需要会话 Cookie、可信 Host/Origin 与 CSRF;带品牌的路径用 ?profile=creatify;共享的登录、素材、模型目录路径不接受 profile 参数。

Agent/统一动作 7 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactionsrunInteractions200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/interactions/{interaction_id}/responsesrespondInteractionclient_action_id, card_sha256, action, values202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/interaction-actions/{client_action_id}interactionAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changeschangeConversationModelclient_action_id, expected_revision, agent_model, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/model-changes/{client_action_id}modelChangeAction200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stopstopRunclient_action_id, expected_attempt?202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/stop-actions/{client_action_id}stopAction200
Asset 4 个
方法路径operationId请求字段成功
GET/v1/assetslistAssets200
POST/v1/assetsuploadAssetfile201
GET/v1/assets/{asset_id}getAsset200
GET/v1/assets/{asset_id}/contentassetContent200/206
LoginSession 1 个
方法路径operationId请求字段成功
POST/v1/auth/logoutlogout200
MaaBinding 5 个
方法路径operationId请求字段成功
GET/v1/maa/model-optionsmaaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/messagessendMaaMessageclient_action_id, content202
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/actions/{client_action_id}getMaaAction200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/stategetMaaState200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/runs/{run_id}/eventsmaaEvents200
Media 11 个
方法路径operationId请求字段成功
GET/v1/media/modelspublicMediaModels200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requestscreateMediaRequestclient_request_id, model_id, model_version_id, prompt, duration_seconds, ratio, resolution, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requestsmediaRequestByClient200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}getMediaRequest200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}getMediaJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobs/{job_id}/outputgetMediaOutput200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/jobmediaJobByRequest200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/{request_id}/approveapproveMediaRequestclient_approval_id, request_sha256, quote_sha256200
POST/v1/projects/{project_id}/conversations/{conversation_id}/media/requests/from-maacreateMediaRequestFromMaainput, source_run_id, source_assistant_sha256, admission?200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/jobslistMediaJobs200
GET/v1/projects/{project_id}/conversations/{conversation_id}/media/intentslistMediaIntents200
Other 9 个
方法路径operationId请求字段成功
GET/v1/projects/{project_id}/canvas/revisionslistCanvasRevisions200
GET/v1/projects/{project_id}/canvas/revisions/{revision}getCanvasRevision200
POST/v1/projects/{project_id}/canvas/revisions/{revision}/restorerestoreCanvasRevisionexpected_revision200
GET/v1/projects/{project_id}/document/revisionslistTimelineRevisions200
GET/v1/projects/{project_id}/document/revisions/{revision}getTimelineRevision200
POST/v1/projects/{project_id}/document/revisions/{revision}/restorerestoreTimelineRevisionexpected_revision200
GET/v1/projects/{project_id}/assetslistProjectAssets200
POST/v1/projects/{project_id}/assetsattachProjectAssetasset_id, expected_revision200
POST/v1/projects/{project_id}/assets/{asset_id}/detachdetachProjectAssetexpected_revision200
Project 15 个
方法路径operationId请求字段成功
GET/v1/projectslistProjects200
POST/v1/projectscreateProjecttitle201
GET/v1/projects/{project_id}getProject200
GET/v1/projects/{project_id}/conversationslistConversations200
POST/v1/projects/{project_id}/conversationscreateConversationtitle201
GET/v1/projects/{project_id}/conversations/{conversation_id}getConversation200
GET/v1/projects/{project_id}/canvasgetCanvas200
POST/v1/projects/{project_id}/canvassaveCanvasexpected_revision, state200
GET/v1/projects/{project_id}/documentgetDocument200
POST/v1/projects/{project_id}/documentsaveDocumentexpected_revision, state200
GET/v1/projects/{project_id}/export-jobslistExportJobs200
POST/v1/projects/{project_id}/export-jobsqueueExportdocument_revision, output?202
GET/v1/projects/{project_id}/export-jobs/{job_id}getExportJob200
GET/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindinggetMaaBinding200
POST/v1/projects/{project_id}/conversations/{conversation_id}/maa/bindingbindMaaagent_model XOR use_default:true200
User 3 个
方法路径operationId请求字段成功
POST/v1/auth/registerregisteremail, password201
POST/v1/auth/loginloginemail, password200
GET/v1/auth/sessionsession200

Creatify 自己公开的接口调用流程(来自其官方资料)

步骤调用关系操作证据
导入商品链接接入程序 → 公共APIPOST https://api.creatify.ai/api/links/,body含url;鉴权字段X-API-ID/X-API-KEY;保留Link.id和link.id。A
修订商品资料接入程序 → 公共APIPUT /api/links/{id}/修订产品资料;编辑字段和路径id规则以官方schema为准。A
生成脚本候选接入程序 → 公共APIPOST /api/ai_scripts/;返回脚本任务id、workspace/product引用、status及generated_scripts[].paragraphs/script_name/script_style/title。A
请求多个预览接入程序 → 公共APIPOST /api/link_to_videos/preview_list_async/;link引用外层Link.id,可传visual_styles[]、video_length、aspect_ratio等;返回任务id与previews[].media_job。A
选中候选渲染接入程序 → 公共APIPOST /api/link_to_videos/{id}/render_single_preview/;path为LinkToVideo.id,body为media_job:UUID。A
查询每个输出接入程序 → 公共APIGET /api/link_to_videos/{id}/;检查outputs[].status、progress、failed_reason和video_output,不仅检查顶层status。A
读取模型参数接入程序 → 公共APIGET /api/asset_generator/schemas/?model_name=...;返回model_name、generation_type与input_params_schema及UI扩展字段。A
提交通用生成接入程序 → 公共APIPOST /api/asset_generator/;body含model_name、input_params、可选webhook_url;返回生成任务id。A
收取产物状态接入程序 → 公共APIGET /api/asset_generator/{id}/按任务id取status与assets[]{id,type,url,thumbnail_url,name};可选由服务向客户webhook_url发送回调。A
为什么这样设计所有写动作都带客户端幂等键(client_action_id / client_request_id / client_approval_id),重复提交返回原回执;报价和批准分成两个接口;停止、切换模型、澄清卡回答都是「动作」而不是「状态修改」,所以每一步都能查回执。这样前端可以放心地重试,后端也不会因为重试产生第二次副作用(决策 D2、D8、D14)。

Creatify · 代码与下载

creatify-replica.zip
0.3 MB · 54 个文件 · SHA-256 d74028b901345ea8c6fafda5fcb7c274e6dd59cdd8dd2532ce7931cc1364a07f
下载:creatify-replica.zip · 后端与合同 · 全部代码
在线试用:http://119.23.106.60:8107/。测试环境,整套独立部署(前端、BFF、Maa 智能体、数据库),可以注册、对话、生成视频(Seedance,测试积分)。页面右下角有“内部复刻研究”标识。
目录内容
frontend/Creatify 前端:<code>candidate-8875-unified-v22.html</code> 及它引用的全部脚本与样式
contracts/本品牌的表结构 SQL、ER(svg / mmd)、外键清单;接口目录(json / csv);共享身份库表结构
README.md怎么运行、需要自备什么

运行前端

python3 -m http.server -d frontend 8791
# 打开 http://127.0.0.1:8791/agent-integration/apps-production-brand-v14/creatify/candidate-8875-unified-v22.html
后端和全部品牌:Go 后端、Python 媒体桥、全部测试和 7 个品牌的合同在 backend-and-contracts.zip;all-brands-code.zip 是全部。包里没有任何凭据、数据库备份或媒体文件,已做敏感串扫描。
需自备:MySQL 与账号(按 contracts 建库)、Maa 智能体服务(MAA_AGENT_ROOT)、模型网关与对象存储配置。静态前端单独打开不需要后端,但登录、对话、生成、导出需要整套服务。

复刻完成度(审计 2026-10-19)

结论:不是完美复刻。官方资料里的 45 项能力:实测跑通 0、只有通用对话 1、后端有但没跑过 5、没有后端 39。

界面对照(Agent 首页(已登录)):真实页面采集到 20 个控件 / 标题文字,复刻页找到 3 个;位置偏差中位数 35 px(0/3 个在 24 px 内)。缺的有:Creatify API、Help & Feedback、Open notifications、Resize sidebar、Hide history、More、Create project、Codex参考体验 · 咖啡广告(仅计划)、Settings、Filter、Tips、Create, analyze, and scale your ads, Demo…。

逐个点击(已登录首页):27 个可点控件,点了有效 4 个,只提示“此功能正在接入” 15 个,没反应或只变样式 3 个,禁用 5 个,未点(发送 / 删除 / 退出等)0 个。

只提示“正在接入”的控件(15 次点击,15 种)

Apps、My Brand、Studio、MCP、API、Help、◧、+、☰、(图标)、←、→、Launch Video、Product Demo、UGC Ad

点了没反应或只变样式的控件(3 次点击,3 种)

Creatify home、Ad Agent、(图标)

复刻页自报的绑定状态(页面内嵌的绑定清单):原生工作区控件(新建项目、会话、登录等)6 / 10 个没有绑定到原页面的控件,由统一的工作区条代替;路由都标为本地已确认;输入框 2 个标为“需要修结构”。

最终设计 · 总览与架构

重点本页重点
  • Go 模块化单体 core + 6 个独立服务(Agent 运行时、模型网关、渲染、采集、实时推送、分析);品牌和入口是配置,不是代码分支。
  • 对话、画布、时间线共用一份项目文档,Agent 只改一个镜头不会误伤其他镜头。
  • Agent、API、MCP 同一套工具面:鉴权、审批、计费只做一遍。

我们的最终设计叫 Lumen。它不是七个竞品的拼盘,而是先把七个竞品逐一复刻、逐项测试,再把真实跑过的做法和踩过的坑归纳成一套设计:保留每一家最有效的一件事,去掉重复和不安全的部分。

flowchart TB
  subgraph Client[客户端]
    WEB[Web 工作台<br/>对话 · 画布 · 时间线]
    EXT[外部 Agent / MCP / API]
  end
  subgraph Core[核心服务 Go 模块化单体]
    PRJ[项目 · 文档<br/>doc_version]
    AGT[Agent 编排接入]
    BIL[账务 · 报价 · 冻结]
    AST[素材]
    INT[创作意图 intent<br/>入口适配器]
  end
  subgraph Runtime[运行时]
    RUN[Agent Runtime Python]
    TMP[Temporal 工作流]
    GWY[模型网关<br/>注册表 + 适配器]
    RND[渲染集群]
  end
  WEB --> PRJ
  WEB --> AGT
  EXT --> AGT
  AGT --> RUN
  INT --> AGT
  RUN -->|同一套工具面| PRJ
  RUN --> BIL
  BIL --> TMP
  TMP --> GWY
  TMP --> RND
  GWY --> M[(视频 / 图片 / 声音模型)]
  PRJ --- DB[(PostgreSQL)]
  BIL --- DB
  AST --- DB
为什么好
  • 一个核心,多个入口。七个竞品的后端结构在我们的实测里几乎一致(同样的 24 张表、同样的 55 个接口形状),区别只在入口、模板和视觉。所以内核只做一份,品牌与入口都是配置和适配器。
  • 对话、画布、时间线共用一份文档。这是 TopView、LibTV、Flova、invideo 各自走向的同一个终点,把它做对一次,Agent 才能只改一个镜头。
  • Agent、API、MCP 用同一套工具面。外部 Agent 接入几乎零成本,计费、审批、审计只做一遍。

本页涉及的决策

D1 一个项目、三种视图共用一份文档 · D9 Skill 与模板有版本,会话固定绑定版本 · D10 模型靠注册表和适配器接入 · D11 不同入口汇成同一个「创作意图」 · D14 能力对内外一致:Agent、API、MCP 用同一套工具面 · D15 不展示做不到的功能 · D17 一个共享内核加品牌配置,而不是七套代码 (借鉴来源与原因见“设计取舍与原因”)

原架构细节(已按最终设计改写,展开查看)

2.1 架构原则

  1. 模块化单体 + 少量独立服务:业务域强耦合部分(项目/画布/账务/素材)放在一个 Go 单体内按模块分层;只有"生命周期、伸缩特性、语言栈"明显不同的部分独立:Agent Runtime(Python)、模型网关、渲染集群、采集器,以及容量模型不同的实时推送与分析,共 6 个(见 2.3)。
  2. 异步任务全部走持久化工作流:生成/渲染/采集一律由 Temporal 编排,不再自造租约与轮询。
  3. 单一事实来源:PostgreSQL 为唯一业务库;所有状态机显式建模,状态只前进;副作用经 outbox 外发。
  4. 账务先于调用:先报价、由用户批准,批准时冻结积分,再调用供应商;结果确定后结算或释放,状态不明时保持冻结、人工核对。
  5. 画布即数据:Agent 的每个产物都是画布节点,用户与 Agent 编辑同一份文档(版本化)。
  6. 供应商可替换:业务代码只依赖"能力(capability)",不依赖具体模型名。

2.2 总体分层

flowchart TB
  subgraph Client[客户端]
    W[Web 应用 Next.js
画布 / 时间线 / 对话] O[开放平台
REST · Webhook · MCP · Skill] end subgraph Edge[接入层] GW[API Gateway / BFF
鉴权 · 限流 · WAF] RT[实时通道
SSE / WebSocket] end subgraph Core[核心域服务 Go 模块化单体] ID[Identity & Org] PR[Project & Canvas] AS[Asset & Library] BL[Billing & Ledger] SF[Safety] TK[Task Facade] ADM[Admin & Config] end subgraph Agent[Agent 域 Python] AR[Agent Runtime
LangGraph] SK[Skill Registry] TL[Tool Servers MCP] end subgraph Gen[生成域] MG[Model Gateway] RN[Render Service
FFmpeg · GPU] CR[Crawler / Collector] end subgraph Plat[平台层] TP[Temporal] PG[(PostgreSQL)] RD[(Redis)] KF[(Kafka)] OS[(COS/S3 + CDN)] CH[(ClickHouse)] VS[(pgvector)] end W --> GW O --> GW W --> RT GW --> Core GW --> AR Core --> TP AR --> TP TP --> MG TP --> RN TP --> CR AR --> TL TL --> Core MG --> Vendors[(模型供应商
Seedance · Kling · Wan · Veo · GPT-Image ...)] Core --> PG Core --> RD Core --> KF RN --> OS KF --> CH AS --> VS

2.3 服务清单与边界

服务语言职责伸缩特性独立理由
core(单体)GoIdentity、Org、Project、Canvas、Asset、Billing、Safety、Admin、Task Facade水平扩展,无状态强事务边界,合并部署降低分布式事务成本
agent-runtimePythonLangGraph 执行、Skill 加载、HITL 暂停恢复、事件发布按活跃会话伸缩;CPU/内存型LLM 生态在 Python;崩溃隔离
model-gatewayGo能力路由、供应商适配器、限额、回调/轮询、成本记录按并发生成数伸缩;I/O 型供应商变化快,独立发布;可对外售卖
renderGo + FFmpeg时间线合成、转码、字幕烧录、封面;Worker 池CPU/GPU Pool,Spot 实例资源画像完全不同
collectorPython/Playwright电商链接/社媒视频采集、解析浏览器池,IP 池反爬、合规与隔离网络
realtimeGoSSE/WS 事件推送,基于 Redis Stream 与 Kafka 订阅连接数伸缩长连接与普通 API 容量模型不同
analyticsGo + ClickHouse埋点、用量、成本、漏斗写吞吐型OLAP 与 OLTP 隔离

core 内部模块与依赖规则

core/
├── identity/    用户、组织、工作区、角色、API Key、OAuth
├── project/     项目、画布文档、节点、版本、协作锁
├── asset/       素材、解析、打标、向量检索、角色资产(asset.kind = character_ref)
├── billing/     账本、冻结/结算、订阅、订单、支付、发票
├── safety/      审核策略、违禁词、风控规则
├── taskfacade/  对外统一"生成任务"视图,屏蔽 Temporal/Gateway 细节
├── admin/       模型目录、价格、限额、配置、审计
└── platform/    db、cache、outbox、event、temporal client、telemetry(只被上面引用)
规则:模块间只通过 Go interface(每个模块的 api 包)调用;禁止跨模块直接读表;
      CI 里用 depguard + archunit 风格测试强制,违反即失败。

2.4 领域模型(限界上下文)

下图由最终 DDL 生成,用的是真实表名和真实基数(鸦脚符号)。原设计这里画的 CANVAS 实体在最终版不单独建表:画布 = project + node + edge;原图里“一个工作区一个账本账户”也不对,最终版每个工作区按用途有多个账户(UNIQUE(workspace_id, kind))。完整 46 张表见 ER 图页。

erDiagram
  workspace ||--o{ member : "workspace_id"
  workspace ||--o{ project : "workspace_id"
  asset |o--o{ project : "cover_asset_id"
  project ||--o{ node : "project_id"
  workspace ||--o{ node : "workspace_id"
  node |o--o{ node : "parent_id"
  node ||--o{ node_version : "node_id"
  asset |o--o{ node_version : "output_asset_id"
  generation_task |o--o{ node_version : "task_id"
  project ||--o{ edge : "project_id"
  node ||--o{ edge : "src_node_id"
  node ||--o{ edge : "dst_node_id"
  project ||--o{ timeline : "project_id"
  workspace ||--o{ timeline : "workspace_id"
  workspace ||--o{ export_job : "workspace_id"
  timeline ||--o{ export_job : "timeline_id"
  asset |o--o{ export_job : "output_asset_id"
  hold |o--o| export_job : "hold_id"
  project ||--o{ agent_session : "project_id"
  workspace ||--o{ agent_session : "workspace_id"
  agent_session ||--o{ agent_run : "session_id"
  workspace ||--o{ agent_run : "workspace_id"
  agent_run ||--o{ run_event : "run_id"
  agent_run ||--o{ interaction : "run_id"
  workspace ||--o{ generation_task : "workspace_id"
  project |o--o{ generation_task : "project_id"
  node |o--o{ generation_task : "node_id"
  agent_run |o--o{ generation_task : "run_id"
  hold |o--o| generation_task : "hold_id"
  generation_task ||--o{ task_attempt : "task_id"
  workspace ||--o{ asset : "workspace_id"
  generation_task |o--o{ asset : "origin_task_id"
  workspace |o--o{ ledger_account : "workspace_id"
  ledger_account ||--o{ ledger_entry : "account_id"
  point_lot |o--o{ ledger_entry : "lot_id"
  workspace ||--o{ point_lot : "workspace_id"
  workspace ||--o{ hold : "workspace_id"
  generation_quote ||--o{ generation_approval : "quote_id"
  generation_task |o--o{ generation_approval : "task_id"
  org ||--o{ workspace : "org_id"
  app_user ||--o{ member : "user_id"
  app_user ||--o{ agent_session : "user_id"
  app_user ||--o{ project : "owner_id"
  skill ||--o{ skill_version : "skill_id"
  skill ||--o{ agent_session : "skill_id"
  workspace |o--o{ skill : "owner_workspace_id"
  workspace ||--o{ generation_quote : "workspace_id"
  agent_run |o--o{ generation_quote : "source_run_id"
  project ||--o{ project_asset_ref : "project_id"
  asset ||--o{ project_asset_ref : "asset_id"
  timeline ||--o{ timeline_version : "timeline_id"

2.5 关键架构决策(ADR 摘要)

编号决策备选理由代价/风险
ADR-01模块化单体 + 6 个独立服务(2.3)全微服务;全单体团队规模 20–30 人,事务边界集中于项目/账务单体需严格依赖规则防腐烂
ADR-02Temporal 作为唯一长任务编排自研租约+轮询(旧方案);Celery;Argo旧系统两套任务模型(租约 supervisor、outbox+Redis Streams)维护成本高;Temporal 内建重试、定时、信号、可见性引入新基础设施,需要团队学习;Worker 代码必须确定性
ADR-03画布文档 = 节点/边表 + 版本表(非 CRDT)Yjs CRDT 全文档节点粒度冲突少,服务端校验与计费需要结构化数据;画布协同用节点级乐观锁 + 在线提示多人同节点编辑体验弱于 CRDT,可接受
ADR-04统一能力(capability)抽象而非模型名业务直接选模型供应商频繁换代,业务层不应改代码能力矩阵需要运营持续维护
ADR-05积分账本复式记账 + 冻结余额字段加减旧系统出现过修数据 SQL 与退款处置补丁实现复杂度更高,但换来可审计
ADR-06事件:Kafka(业务事件)+ Redis Stream(会话实时流)统一用 Kafka实时流需低延迟、短保留、按会话订阅两套中间件,用统一事件信封规范抹平
ADR-07PostgreSQL 唯一业务库,禁用数据库外键MySQL;加外键沿用旧系统"无外键、应用层保证"的经验以便分库;PG 提供 JSONB、pgvector、部分唯一索引一致性靠应用与对账任务
ADR-08Agent Runtime 用 LangGraph,产物写回画布自研 Agent 循环旧 maa_agent 已验证 interrupt/checkpoint 价值;可迁移 Skill 资产框架升级风险,通过适配层隔离
ADR-09双 Region(国内 / 海外)数据隔离部署单 Region 全球合规、供应商可用性、延迟运维成本翻倍;两 Region 共享代码、独立数据

2.6 Agent 架构

flowchart LR
  U[用户消息/链接/参考视频] --> O[Orchestrator]
  O -->|plan| P[Plan 节点
可编辑] O --> E1[采集专家] O --> E2[策略/脚本专家] O --> E3[分镜专家] O --> E4[生产专家] O --> E5[后期专家] E1 & E2 & E3 & E4 & E5 -->|MCP tool call| T[(Tool Servers)] T --> C[Canvas API] T --> G[Generation API] T --> A[Asset API] E4 -->|提交| TF[Task Facade] O <-->|interrupt / resume| H[HITL 卡片] O --> CP[(Checkpoint PG)]
  • Skill 包:目录结构 skill.yaml + prompts/ + tools.json + evals/,版本化(skill_version)、可发布到开放平台(POST /skills);会话创建时固定一个版本。灰度发布没有单独建模。旧 ad-video-editing / viral-ad-replication / product-link-collection 等能力包直接作为初始 Skill 迁移。
  • 工具通过 MCP:Core 与 Gateway 各暴露 MCP Server,Agent 与外部 Agent 使用同一套工具,避免"内部一套外部一套"。
  • 产物溯源:每个视频节点的版本(node_version)必须记录 task_id;经 task_attempt 可追到 provider_task_id,经 generation_task.request_hash 可追到输入。Agent 不能凭空写入成片 URL(继承旧系统硬规则)。
  • 预算守卫:Run 持有 budget_centi(预算)与 spent_centi(已花);接近上限发 budget.warning,超限进入 waiting_user 由用户决定。

2.7 部署视图(单 Region)

flowchart TB
  CDN[CDN] --> LB[LB / Ingress]
  LB --> AP[core ×N]
  LB --> RTM[realtime ×N]
  LB --> AG[agent-runtime ×N]
  AP --> PGM[(PG 主)]
  PGM --> PGR[(PG 只读副本 ×2)]
  AP --> RDS[(Redis Cluster)]
  AP --> KFK[(Kafka 3 节点)]
  AP --> TPS[Temporal 集群]
  TPS --> WK1[workflow worker]
  WK1 --> MGW[model-gateway ×N]
  WK1 --> RNW[render worker
CPU Pool + GPU Pool] WK1 --> COL[collector 浏览器池] RNW --> COS[(对象存储)] KFK --> CHS[(ClickHouse)]

最终设计 · Agent 交互过程设计

重点本页重点
  • 一条链:发送前说明准入 → 澄清卡补信息 → 方案落成镜头节点 → 先报价再批准 → 生成。
  • 批准是硬门槛且带哈希:用户看到的价格和提示词就是实际执行的;报价过期就拒绝,不建任务不扣费。
  • 每个动作都有回执、每个事件都有序号:断线、刷新、重复点击都安全。
  • 打开或刷新会话先读快照、再接事件流:进度、停止键、未答的卡片、写到一半的回复都能恢复;停止后的半截回复保存为“已停止”。

下面的过程把竞品里最好的几件事串成一条链:发送前说明准入(Flova)→ 结构化澄清卡(CreaTok)→ 方案落成镜头节点(CreaTok、LibTV)→ 先报价再批准,批准带哈希(我们复刻里跑通;否决 LibTV 的只提醒)→ 生成、停止、恢复都幂等(Pollo 的流式事件 + 我们的修复)。

sequenceDiagram
  autonumber
  participant U as 用户
  participant W as 工作台
  participant C as Core
  participant R as Agent Runtime
  participant B as 账务
  participant T as Temporal
  participant G as 模型网关
  U->>W: 输入需求(可 / 选 Skill,@ 引用素材)
  W->>C: GET admission(额度、积分、原因)
  C-->>W: 可用 / 不足 + 下一步
  W->>C: POST messages {client_action_id}
  C->>R: 创建 Run
  R->>C: 事件(每条带递增 seq)
  C-->>W: 事件流(SSE,经实时通道,断线按 seq 续读)
  R->>C: 需要补充信息 → interaction(card_sha256)
  C-->>W: 澄清卡(必填 / 可选)
  U->>W: 提交答案
  W->>C: respond {card_sha256, values}
  C->>R: 恢复同一个 Run
  R->>C: 写入 scene / shot 节点(node_version)
  C-->>W: 画布与对话同步出现镜头
  U->>W: 只改第 2 个镜头
  R->>C: 新 node_version,其余镜头不变
  U->>W: 生成
  W->>C: POST generations:quote
  C->>B: 计算报价
  B-->>W: quote_id + 两个哈希 + expires_at
  U->>W: 批准
  W->>C: POST generations {quote_id, client_approval_id, 哈希}
  C->>B: 冻结额度
  C->>T: 启动工作流
  T->>G: submit
  G-->>T: 结果
  T->>B: 结算 / 失败释放
  C-->>W: 任务完成,素材入库
为什么好
  • 准入前置(Flova):失败发生在发送之后用户会反复重试;提前说明一次,少一次无效请求。
  • 澄清是对象不是段落(CreaTok 已真实跑通两轮):必填校验、刷新后仍在、过期有明确状态,同一个 Run 恢复不会恢复两次。
  • 方案落成节点:只改第 2 个镜头就只产生一个新版本,其余镜头保持,重生成只为改动的镜头计费。
  • 批准是硬门槛且带哈希:LibTV 的预算只在达到阈值时提醒,不阻止下一笔扣费;哈希绑定保证用户看到的价格和提示词就是实际执行的。我们的真实测试里,报价过期后批准被拒,没有建任务也没有扣费。
  • 动作都有回执:长回复必然遇到断线、刷新和重复点击;序号和幂等让这些情况安全。

对话状态与恢复规则

同一会话里的消息、运行、卡片和事件都以服务端为准;浏览器只保存未提交的卡片草稿。

场景用户看到 / 能做什么前端后端保存接口 / 事件验收
首次发送用户气泡立即出现;发送键变为停止键;回复区显示“处理中 0s”计时先用临时 id 渲染气泡;收到 202 后换成服务端 message_id;URL 改为 /projects/{pid}/agent/{session_id}写用户消息与 run_action;创建 run(queued);返回 run_id、message_idagent_message(user)、agent_run、run_action;agent_session.last_message_atPOST /sessions/{id}/messages → 202;事件 run.started、user.messageAGT-01
执行中展示过程区逐行出现步骤(自然语言标题 + 状态),正文逐字出现;输入框上方“任务进度 n/m”按 (run_id, seq) 去重;只渲染 session_id 等于当前会话的事件;正文以 assistant.message 为最终版本Runtime 为每个工具调用写一句用户可读的 title;回复文字每 ≤2 秒落库一次run_event(工具、计划事件);agent_message.content / content_seq事件 tool.started/completed/failed(含 title)、plan.updated、assistant.delta、assistant.messageAGT-01、AGT-13
运行中再发消息消息显示为用户气泡,标签“已补充到当前任务”;停止键仍在,输入框有字时按钮为发送发送同一接口;收到 mode=guidance 时加标签追加到当前 run 的下一步输入;不新建 runagent_message(delivery=guidance)POST messages → 202 mode=guidanceAGT-02
等待用户补充 / 确认卡片占据输入框位置:一次一题、分页“1 / N”、必填带 *、选项 + 自由输入、取消 / 继续;历史列表该会话标“需要输入”已填答案存浏览器草稿(卡片 id + card_sha256),刷新不丢;提交一次带全部答案run 进入 waiting_user,同一时刻只有一张未关闭的卡;答案按卡片 schema 校验后恢复同一个 runinteraction(pending → resolved)事件 interaction.requested / resolved;POST interactions/{id}:respondAGT-05、06、07、17
积分不足暂停卡片“积分不足,还差 X 积分 · 去充值 / 我已充值,继续”;历史标“需要输入”“去充值”打开购买页;“我已充值,继续”调用 respond付费步骤前做准入检查;不足则发 kind=payment_required 的卡;respond 时再查一次,够则恢复同一个 run,不够返回 402 并保留卡片interaction(payment_required)GET admission;POST respond → 200 / 402 insufficient_pointsAGT-14
停止停止后保留已输出的文字,标“已停止”;刷新后仍是同样内容点击即禁用停止键,等回执;回执 unknown 时提示“正在确认是否已停止”并轮询 run幂等停止;把已输出文字存为 stopped 消息;未关闭的卡片 cancelled;已批准的生成任务不取消agent_message(stopped)、run_action(stop)POST /runs/{id}:stop;事件 assistant.message(status=stopped)、run.cancelledAGT-03、12
失败与超时回复下方显示错误原因和“重试”重试 = 用新的 client_action_id 再发上一条用户消息运行 30 分钟无结束事件判超时:run failed(run_timeout),流式消息 failed,未提交任务的冻结释放agent_run.error_code;agent_message(failed)事件 run.failedAGT-19
重复提交同一条消息只出现一次每次点击发送生成一个 client_action_id;网络重试沿用同一个同 id 同内容返回原回执;同 id 不同内容 409run_action、agent_message UNIQUE(session_id, client_action_id)POST messages → 202 / 409 idempotency_conflictAGT-01
刷新 / 离开后重新进入回到同一会话:历史消息、进行中的进度和停止键、未答的卡片、写到一半的回复都在先 GET /sessions/{id} 渲染,再用 Last-Event-ID = run_id:last_seq 接事件流快照里返回流式消息已保存的文字和 active_run.last_seq;接流时若缺少未落库的增量,先发 assistant.snapshot同上(全部来自库,不依赖浏览器)GET /sessions/{id};GET /sessions/{id}/eventsAGT-11
断线重连顶部提示“连接中断,正在重试”,带“重试”按钮;恢复后提示消失指数退避重连 3 次;仍失败则每 5 秒轮询 GET /runs/{id} 直到连上事件按 seq 补发,恰好一次、有序—GET /sessions/{id}/events(Last-Event-ID)AGT-04、18
多个会话同时运行历史列表里运行中的会话带转圈标记,等待用户的带“需要输入”每个页面只订阅当前会话的事件流;历史列表打开时拉取一次、之后每 30 秒刷新同一会话最多 1 个活跃 run;同一工作区并发上限按套餐 2 / 6 / 20,超出 429 run_busyagent_run(one_active_run_per_session)GET /projects/{id}/sessions(active_run、needs_action)AGT-15
stateDiagram-v2
  [*] --> queued: POST messages(新任务)
  queued --> running: 工作流启动
  running --> waiting_user: 发出卡片(含 payment_required)
  waiting_user --> running: respond(同一个 run)
  waiting_user --> cancelled: 卡片过期 / 用户直接发新消息 / 停止
  running --> succeeded: assistant.message + run.completed
  running --> failed: 出错 / 30 分钟超时
  running --> cancelled: stop(已输出文字存为“已停止”)

消息的组成与呈现

组成部分数据来源呈现规则
用户消息agent_message(role=user)右侧气泡,附件显示缩略图;操作:复制。补充指令带“已补充到当前任务”标签。
过程区tool.* 事件的 title + status;run.started 的时间运行中展开:“处理中 Ns”计时,每步一行“标题 + 状态”(进行中 / 完成 / 失败)。结束后折叠为“已处理 N 步 · 用时 Ns”,可展开。只显示 Runtime 写的 title,不显示原始工具名,也不显示模型原始思考。
任务进度plan.updated输入框上方“任务进度 n/m”,当前项高亮,可收起;run 结束后消失。
正文assistant.delta → assistant.messageMarkdown:标题、列表、表格。流式期间末尾显示光标;收到 assistant.message 后整段替换为最终文本。
卡片interaction显示在输入框位置(见上表“等待用户补充”);一次一题,最多 5 题。
产物node.created / node.updated、task.progress、asset标签形式(镜头、文档、图片、视频),点击定位到画布节点或素材;生成中显示进度百分比。
后续建议assistant.message.suggestions只在最新一条助手消息下方显示,最多 3 条,点击即作为新消息发送。
消息底栏agent_message.status、completed_at时间 + 复制;stopped 显示“已停止”;failed 显示错误和“重试”。
会话用量ledger_entry(ref_type=run,决策 D4)按会话汇总,随快照返回 usage_centi输入框下方“本会话已用 X 积分”;首次发送前提示一次“对话推理按用量扣积分,生成素材另计”。

回复内容规范

竞品对话样式参考 2026-10-20 同一任务实测;invideo 未取得回复
竞品整体执行中后续建议等待 / 确认
Pollo深色;右侧用户气泡;正文 Markdown + 表格“Completed ⌄ · Thinking complete ›”+ 自然语言步骤“What’s next” 3 条积分不足时输入框上方卡片“Purchase completed? / I’ve paid, continue”
CreaTok浅色,Geist 字体,正文 14px / 20px #0a0a0a“处理中 Ns”计时,左边线缩进的叙述 + ✓ 步骤,结束折叠为“已处理 Ns”无卡片替换输入框,一次一题“1 / 7”,必填 *,取消 Esc / 继续 ↵
Creatify深色,正文衬线体 Source Serif 4 / Noto Serif SC 16px / 26px;用户气泡 #131414、1.5px 边框、10px 圆角“Making sense of it… Ns”→“Working ⌄”步骤 →“Writing the reply…”“Suggested follow-ups” 3 条无卡片:先出方案,用 [方括号] 占位,最后列出要补充的信息;输入框下显示本次花费
TopView深色侧栏 Agent“Planning shot sequence…”+ 骨架卡无结果是分镜卡(时长 · 画幅 · 分段),卡上“Use Prompt / Generate Storyboard / Generate Video”,确认前不改时间线
LibTV深色画布侧栏 TV Director“思考中…”直接展开模型原始推理(含内部指令,不应照搬)无首次发送前确认:推理实时扣积分、自动生成默认关闭、预算提醒阈值
Flova深色项目工作区“已处理 N 项”,每步“动作 + 状态 · 对象”,涉及的文件以标签显示;结束后折叠无(回复下方“这个结果怎么样?”)方案写进项目文档,对话里同时给出全文和文档标签

本页涉及的决策

D2 先报价,再批准,批准带哈希 · D4 文字推理成本和媒体成本分开显示 · D5 缺信息时用结构化澄清卡 · D6 方案是结构化实体,不是一大段文字 · D7 发送前就说明准入条件 · D8 事件流带游标,可停止、可恢复 · D16 登录与会话状态要有可见入口 (借鉴来源与原因见“设计取舍与原因”)

本页相关接口 15 条,完整清单在“接口清单”
方法路径说明来源
POST/v1/generations:quote报价,不扣费;返回 quote_id、request_sha256、quote_sha256、expires_at决策 D2
POST/v1/generations批准并创建任务:body 带 quote_id、client_approval_id、两个哈希;重放返回同一任务;报价过期返回 409 quote_expired决策 D2
POST/v1/sessions/{id}/messages请求头 / body 带 client_action_id;返回 202 + run_id;同 ID 不同内容 409决策 D8
POST/v1/runs/{id}:stop幂等停止;回执 status=terminal_observed | unknown;已输出的文字保存为一条 status=stopped 的助手消息;不承诺供应商侧已取消计费决策 D8
GET/v1/projects/{id}/sessions会话列表(历史):按 last_message_at 倒序,q 搜索标题;每项带 active_run {id, status} 与 needs_action(等用户回答卡片或充值时为 true)决策 D8
GET/v1/sessions/{id}打开或刷新会话:返回会话、最近 50 条消息、进行中的 run(状态、last_seq、计划、未关闭的卡片);前端先按它渲染,再从 last_seq 接事件流决策 D8
GET/v1/sessions/{id}/messages向上翻历史:before=<message_id>&limit≤50,按时间倒序决策 D8
POST/v1/interactions/{id}:respond带 card_sha256;答案按卡片 schema 校验;已恢复过的卡再提交返回原回执决策 D5
GET/v1/workspaces/{id}/admission发送前准入:额度/积分是否够用、原因、下一步(购买/升级/打开额外用量)决策 D7
PUT/v1/workspaces/{id}/approval-policyAsk First / Always Allow(带单次与每日上限)决策 D2
POST/v1/intents创建创作意图:adapter + source_ref(URL、商品、脚本、参考视频)决策 D11
GET/v1/capabilities能力注册表:每项能力状态(可用/灰度/未接入),界面只渲染可用项决策 D15
GET/v1/timelines/{id}/versions时间线版本历史(每次保存一条)决策 D13
POST/v1/timelines/{id}/versions/{v}:restore恢复到某个版本:以旧内容新建一个版本,不覆盖历史决策 D13
GET/v1/models模型清单:版本、约束、价格表(来自 model_version)决策 D10
全部业务流程、Temporal 工作流、并发规则 已按“先报价再批准”改写,展开查看

6.1 流程清单

F1 积分冻结 → 生成 → 结算(最核心)

sequenceDiagram
  autonumber
  participant C as 客户端
  participant API as core.taskfacade
  participant B as core.billing
  participant S as core.safety
  participant PG as PostgreSQL
  participant TP as Temporal
  participant W as GenWorkflow
  participant G as model-gateway
  participant V as 供应商
  participant A as core.asset
  C->>API: POST /generations:quote
  API->>S: 输入审核(prompt+参考图)
  S-->>API: pass
  API->>G: Route(capability,params)
  G-->>API: model_version + 价格
  API->>PG: INSERT generation_quote(request_sha256, quote_sha256, expires_at)
  API-->>C: quote_id + 两个哈希 + expires_at(不扣费)
  C->>API: POST /generations {quote_id, client_approval_id, 两个哈希}
  API->>PG: 校验报价未过期、哈希一致(重放同一批准返回同一任务)
  rect rgb(40,60,90)
  note over API,PG: 单个本地事务
  API->>B: Reserve(ref=task_id, amount=quoted)
  B->>PG: 选 lot → hold(active) → 分录
  API->>PG: INSERT generation_approval + generation_task(held→queued) + outbox(start_workflow)
  end
  API-->>C: 201 queued
  PG-->>TP: outbox relay → StartWorkflow(task_id)
  TP->>W: run
  W->>G: Submit(task_id, attempt=1)
  G->>V: 创建视频任务
  V-->>G: provider_task_id
  G-->>W: ACCEPTED
  loop 回调优先,轮询兜底
    V-->>G: webhook / 轮询 Query
    G-->>W: Signal(progress)
  end
  V-->>G: 成功 + 视频URL
  W->>A: 下载转存 COS,生成缩略图,审核
  A-->>W: asset_id(pass)
  W->>B: Settle(hold, actual)
  B->>PG: hold=settled,分录:held→revenue/cogs,差额返还
  W->>PG: task=succeeded + 节点新版本 + 事件
  PG-->>C: SSE task.completed / node.updated

要点:① 积分冻结与任务创建同事务,不会出现"扣了没任务"或"有任务没扣";② Workflow 里每一步是 Activity,失败自动重试(带退避),结算与释放都是幂等的(以 hold 的 UNIQUE(ref_type, ref_id) 保证);③ 视频必须转存到自有存储后才视为成功,供应商链接通常 24h 失效;④ 任务只能由批准创建:先 generations:quote,再带 quote_id 与两个哈希调用 POST /generations,报价过期或哈希不一致都不会建任务、不会扣费。

F2 提交不确定(submit_uncertain)

sequenceDiagram
  participant W as GenWorkflow
  participant G as Gateway
  participant V as 供应商
  W->>G: Submit(task, attempt=1)
  G->>V: POST create (带 client_token=task:attempt)
  Note over G,V: 超时/连接重置
  G-->>W: UNCERTAIN
  W->>W: task=submit_uncertain (保持冻结)
  loop 最多 N 次, 间隔递增
    W->>G: Query(by client_token)
    G->>V: 按 token 查询 / 列表对账
  end
  alt 供应商确认受理
    G-->>W: ACCEPTED(provider_task_id)
    W->>W: → running
  else 确认未受理
    G-->>W: NOT_FOUND
    W->>W: attempt=2 (可换供应商) → queued
  else 无法确认(超过 T_max)
    W->>W: 标记 needs_manual, 保持冻结, 告警
    Note right of W: 运营对账后 release 或 settle
  end
原则:不能确认供应商未受理之前,绝不重新提交(避免双倍成本);供应商不支持幂等 token 的,通过"提交前落库 + 列表对账(按时间窗+参数哈希)"兜底。

F3 URL → 广告视频(Agent 全流程)

sequenceDiagram
  autonumber
  participant U as 用户
  participant O as Orchestrator
  participant CO as 采集专家
  participant ST as 策略/脚本专家
  participant SB as 分镜专家
  participant PD as 生产专家
  participant PP as 后期专家
  participant K as Canvas/Task API
  U->>O: 商品链接 + "3 条 15s TikTok 广告"
  O->>CO: collect_url
  CO->>K: collector job → 商品信息/图片
  CO-->>O: product 节点 (标题/卖点/价格/图)
  O->>ST: 提炼卖点+受众+3 个创意角度
  ST-->>O: script 节点 ×3
  O-->>U: interaction: review_script (HITL)
  U-->>O: 选定/修改
  O->>SB: 分镜(每条 4-6 镜头)+角色+首帧图
  SB->>K: shot 节点 + 首帧 t2i 任务
  SB-->>O: storyboard 节点
  O->>K: POST /generations:quote ×N(逐镜头报价)
  O-->>U: interaction: confirm_cost(Σ报价 = N 积分)
  U-->>O: 批准
  O->>PD: 并发生成所有 shot(i2v) + TTS + 字幕
  PD->>K: POST /generations ×N {quote_id, client_approval_id, 哈希}
  K-->>PD: task 完成事件
  PD-->>O: shot_assets(溯源齐全)
  O->>PP: 合成时间线(BGM,转场,字幕,混音)
  PP->>K: 写 timeline + 创建 export
  PP-->>O: final_video
  O-->>U: 成片 + 可编辑画布

溯源规则(硬约束):final_video 的每个镜头片段必须引用 node_version.task_id 对应的 succeeded 任务;写入工具校验,不通过则拒绝,Agent 无法伪造成片。

F4 HITL 暂停与恢复

sequenceDiagram
  participant AR as agent-runtime
  participant WF as RunWorkflow
  participant PG as PG
  participant RT as realtime
  participant U as 用户
  AR->>WF: interrupt(kind, request)
  WF->>PG: INSERT interaction(pending) + run=waiting_user + event (同事务)
  PG-->>RT: outbox → interaction.requested
  RT-->>U: SSE 卡片
  Note over WF: 等待 Signal (最长 TTL, 无资源占用)
  U->>PG: POST respond (client_action_id)
  PG->>PG: 校验幂等/状态, interaction=resolved
  PG->>WF: Signal(response)
  WF->>AR: resume(thread_id, response)
  AR-->>WF: 继续执行
  alt 超时
    WF->>PG: interaction=expired, run=cancelled, release holds
  end

F5 单镜头重生成

flowchart LR
  A[用户点击重生成<br/>可改 prompt/参考] --> B[quote 报价]
  B --> C{余额足够?}
  C -- 否 --> D[402 引导充值]
  C -- 是 --> B2[用户批准报价<br/>quote_id + 哈希]
  B2 --> E[冻结 + 新建 task<br/>base=旧版本上下文]
  E --> F[读取: 角色参考图<br/>前后镜头尾帧/首帧<br/>风格种子]
  F --> G[提交生成]
  G --> H[成功: node_version+1<br/>旧版本保留]
  H --> I[标记下游 timeline clip 为 outdated<br/>可一键替换]
  H --> J[旧任务费用不退<br/>新任务单独计费]

F6 时间线导出

sequenceDiagram
  participant C as 客户端
  participant API as core
  participant WF as ExportWorkflow
  participant R as render worker
  participant S as COS
  C->>API: POST /timelines/{id}/exports (version=V)
  API->>API: 校验所有 clip 素材可用/审核通过
  API->>API: 冻结导出积分(可为0)
  API->>WF: start (timeline snapshot V 固化)
  WF->>R: 拆分: 分段渲染 (每段 ≤10s)
  par 并行分段
    R->>S: 读源素材(分段区间)
    R->>R: FFmpeg filter_complex 合成
    R->>S: 写分段
  end
  WF->>R: 拼接+混音+字幕烧录+封面
  R->>S: 写成片
  WF->>API: asset 入库 + export=succeeded
  API-->>C: SSE export.succeeded

分段并行把 60s 1080p 的导出压缩到约 40–90s;失败只重试失败的分段;timeline 在提交时固化快照,导出期间用户继续编辑不影响结果。

F7 充值 / 订阅

sequenceDiagram
  participant U as 用户
  participant API as billing
  participant PSP as Stripe/微信
  participant PG as PG
  U->>API: POST /billing/checkout (sku)
  API->>PG: order(created)
  API->>PSP: 创建支付会话
  PSP-->>U: 跳转支付
  PSP->>API: webhook payment.succeeded (event_id)
  API->>PG: INSERT payment_event ON CONFLICT DO NOTHING
  alt 首次处理
    API->>PG: 事务: order=paid, point_lot(purchase), 分录(revenue→wallet_paid)
  else 重复事件
    API-->>PSP: 200(幂等忽略)
  end
  API-->>U: SSE billing.balance_changed
  Note over API,PG: 退款: 先 hold 对应剩余积分,不足则标记欠费并限制使用

F8 爆款复刻

flowchart TB
  A[参考视频 URL/文件] --> B[采集+转存+审核]
  B --> C[镜头切分<br/>PySceneDetect]
  C --> D[逐镜 VLM 理解:<br/>景别/运镜/主体/台词/情绪]
  D --> E[ASR+文案结构化<br/>钩子/卖点/CTA 时间轴]
  E --> F[节奏提取: 镜头时长、BGM 节拍]
  F --> G[生成 blueprint 节点<br/>可编辑]
  G --> H{用户确认}
  H --> I[套入新商品:<br/>替换主体/文案/素材]
  I --> J[进入 F3 生产段]
版权风险:复刻只提取"结构与节奏",不复制原视频画面/人物;输出前做相似度检测(关键帧 pHash + 文案相似度),超过阈值提示并阻断。

F9 批量变体

flowchart LR
  A[母版项目 + 变量矩阵<br/>风格×角色×语种×CTA] --> B[展开为 N 个子项目]
  B --> B2[逐个报价,用户一次批准整批<br/>每个子项目一个 quote_id]
  B2 --> C[预算校验: Σ报价 ≤ 余额<br/>冻结总额]
  C --> D[Child Workflow ×N<br/>限并发 K]
  D --> E{单个失败?}
  E -- 是 --> F[自动重试 ≤2 次<br/>仍失败释放该份冻结]
  E -- 否 --> G[产物回收]
  D --> H[进度聚合: 暂停/继续/取消]

F10 素材上传与解析

sequenceDiagram
  participant C as 客户端
  participant API as asset
  participant S as COS
  participant WF as AssetWorkflow
  C->>API: POST /assets:init-upload (size,mime,sha256)
  API->>API: 配额校验,sha256 命中 → 直接复用(秒传)
  API-->>C: 预签名 URL (分片)
  C->>S: 直传
  C->>API: POST /assets/{id}:complete
  API->>WF: start
  WF->>WF: 病毒/格式校验 → 转码代理 → 抽帧/镜头切分
  WF->>WF: 审核 (图像/视频/ASR 文本)
  WF->>WF: VLM 描述 + 标签 + embedding
  WF->>API: asset 可用 (moderation=pass)

F11 内容审核分层

阶段对象动作延迟预算
L0 报价时(输入)prompt/文案违禁词(Aho-Corasick)+ 文本审核模型<100ms 同步
L1 报价时(参考素材)参考图/视频图像审核(人脸/名人/成人/暴力)<800ms 同步(图),视频异步
L2 供应商侧请求供应商自带的审核结果记在 task_attempt(error_code = content_rejected,原因在 error_message),任务失败并释放积分—
L3 产出后生成视频抽帧审核 + ASR 文本审核;未通过不入库、不可分享异步 <30s
L4 发布前对外发布/直发复审 + 平台政策检查同步
L5 人工中风险队列运营后台复核,SLA 4h—

6.2 Temporal 工作流设计

WorkflowTask Queue关键 Activity超时/重试信号/查询
GenerationWorkflowgenSubmit, PollOrAwaitCallback, Ingest, Settle/Release(路由在报价时完成,冻结在批准时与建任务同事务完成)Submit 3 次(退避 2s→30s);整体 timeout=模型 timeout+10mSignal: cancel, provider_callback;Query: state
AgentRunWorkflowagentRunGraphStep(LangGraph 单步), PersistEvents步级重试 2 次;run 总时长 ≤ 6hSignal: guidance, interaction_response, cancel
ExportWorkflowrenderPlan, RenderSegment×N, Concat, Ingest分段 5m 超时,重试 3 次Signal: cancel
AssetWorkflowassetProbe, Transcode, SceneSplit, Moderate, Embed每步 10m—
BatchWorkflowbatchExpandMatrix, StartChildrenChild 失败策略可配Signal: pause/resume/cancel
HoldSweeper (Schedule)billingReleaseExpiredHolds(跳过 submit_uncertain 与待人工核对的任务,它们保持冻结)每分钟—
Reconcile (Schedule)billingLedgerInvariants, ProviderCostReconcile每日 03:00—
OrphanSweeper (Schedule)gen找出 DB 活跃但无 Workflow 的任务并补启动每 5 分钟—

6.3 并发与一致性规则

  • 每工作区并发上限:按套餐(Free 2 / Pro 6 / Team 20)+ 每模型全局并发(来自 model_catalog.max_concurrency);Redis 令牌桶 + 排队,超出进入 queued 而非拒绝。
  • 公平调度:Gateway 提交队列按 workspace_id 加权轮询,防止大客户批量任务饿死其他租户。
  • 画布并发:graph:patch 使用 base_version 乐观锁;Agent 写入与用户编辑冲突时,Agent 侧收到 409 后 rebase 并重试一次,仍冲突则以用户版本为准。
  • 事件顺序:run 内 seq 由 Workflow 单点分配;同一 run 的事件只由其 Workflow 写入。

最终设计 · ER 图

重点本页重点
  • 46 张表,按 7 个域划分;先看全景图,再看下面圈出的 Agent 交互和画布两块。
  • 按设计不建数据库外键:74 条关系靠应用事务、唯一索引和对账保证;租户边界是 workspace_id。

46 张表 · 405 列 · 76 条关系 = 原 30 张核心表 + 吸收竞品新增 7 张 + 补全 9 张(原设计引用了却没给表)。图由 PostgreSQL 16 上实际执行最终 DDL 后读回的结构生成,与表结构、DDL 一致。

租户与成员 画布 · 时间轴(项目文档) Agent 交互 生成 · 报价批准 · 模型 素材 积分账本 · 订阅 · 额度 平台

基数标注(线上的文字,箭头从父表指向子表):1:N 一对多,子表外键必填(NOT NULL);0..1:N 一对多,但外键可空,所以父是可选的;1:1 一对一,子表外键上有唯一键;0..1:1 一对一且父可选。红色粗双向线 = N:M 多对多,经带 ⇄ 的关联表。★ = 吸收竞品后新增的 7 张表;◆ = 补全的 9 张表(原设计提到或被别的表、接口、决策引用,但没给 DDL);橙色粗线与橙色粗框 = Agent 交互相关。本设计沿用“不建数据库外键”的约定,所以基数取自库里真实存在的约束:外键列是否 NOT NULL、是否有单列唯一键。

租户边界:workspace 是租户,下面 21 张表都带 workspace_id、都属于某一个工作区。为了让图清楚,全景图没有把这 21 条线逐条画出,它们都列在下面的“关系清单”里。

全景图:全部实体与关系

默认缩放到页面宽度,先看整体结构;点一下图切换到原始大小(看清表名,在框内上下左右滚动),再点一下缩回。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":28,"rankSpacing":70}}}%%
flowchart TB
  subgraph sg_tenant["租户与成员"]
    direction TB
    t_org["org ◆"]
    t_app_user["app_user ◆"]
    t_session_token["session_token ◆"]
    t_plan["plan"]
    t_workspace["workspace"]
    t_member["⇄ member"]
    t_api_key["api_key"]
  end
  subgraph sg_canvas["画布 · 时间轴(项目文档)"]
    direction TB
    t_project["project"]
    t_project_asset_ref["⇄ project_asset_ref ◆"]
    t_node["node"]
    t_node_version["node_version"]
    t_edge["⇄ edge"]
    t_timeline["timeline"]
    t_timeline_version["timeline_version ◆"]
    t_export_job["export_job"]
    t_intent["intent ★"]
  end
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_agent_session["agent_session"]
    t_agent_message["agent_message ◆"]
    t_agent_run["agent_run"]
    t_run_event["run_event"]
    t_interaction["interaction"]
    t_run_action["run_action ★"]
    t_skill["skill ◆"]
    t_skill_version["skill_version ◆"]
  end
  subgraph sg_gen["生成 · 报价批准 · 模型"]
    direction TB
    t_model_catalog["model_catalog"]
    t_model_version["model_version ★"]
    t_price_rule["price_rule"]
    t_generation_quote["generation_quote ★"]
    t_generation_approval["generation_approval ★"]
    t_generation_task["generation_task"]
    t_task_attempt["task_attempt"]
  end
  subgraph sg_asset["素材"]
    direction TB
    t_asset["asset"]
    t_asset_embedding["asset_embedding"]
    t_retrieval_seen["retrieval_seen"]
  end
  subgraph sg_billing["积分账本 · 订阅 · 额度"]
    direction TB
    t_ledger_account["ledger_account"]
    t_ledger_entry["ledger_entry"]
    t_point_lot["point_lot"]
    t_hold["hold"]
    t_quota_window["quota_window ★"]
    t_approval_policy["approval_policy ★"]
    t_subscription["subscription"]
    t_purchase_order["purchase_order"]
    t_payment_event["payment_event"]
  end
  subgraph sg_platform["平台"]
    direction TB
    t_outbox["outbox"]
    t_webhook_endpoint["webhook_endpoint ◆"]
    t_audit_log["audit_log"]
  end
  t_plan -->|"1:N"| t_workspace
  t_workspace -->|"1:N"| t_member
  t_workspace -->|"1:N"| t_api_key
  t_asset -->|"0..1:N"| t_project
  t_project -->|"1:N"| t_node
  t_node -->|"0..1:N"| t_node
  t_node -->|"1:N"| t_node_version
  t_asset -->|"0..1:N"| t_node_version
  t_generation_task -->|"0..1:N"| t_node_version
  t_project -->|"1:N"| t_edge
  t_node -->|"1:N"| t_edge
  t_node -->|"1:N"| t_edge
  t_project -->|"1:N"| t_timeline
  t_timeline -->|"1:N"| t_export_job
  t_asset -->|"0..1:N"| t_export_job
  t_hold -->|"0..1:1"| t_export_job
  t_project -->|"1:N"| t_agent_session
  t_agent_session -->|"1:N"| t_agent_run
  t_agent_run -->|"1:N"| t_run_event
  t_agent_run -->|"1:N"| t_interaction
  t_model_catalog -->|"1:N"| t_price_rule
  t_project -->|"0..1:N"| t_generation_task
  t_node -->|"0..1:N"| t_generation_task
  t_agent_run -->|"0..1:N"| t_generation_task
  t_api_key -->|"0..1:N"| t_generation_task
  t_model_catalog -->|"0..1:N"| t_generation_task
  t_hold -->|"0..1:1"| t_generation_task
  t_generation_task -->|"1:N"| t_task_attempt
  t_model_catalog -->|"1:N"| t_task_attempt
  t_generation_task -->|"0..1:N"| t_asset
  t_asset -->|"1:N"| t_asset_embedding
  t_asset -->|"1:N"| t_retrieval_seen
  t_ledger_account -->|"1:N"| t_ledger_entry
  t_point_lot -->|"0..1:N"| t_ledger_entry
  t_plan -->|"1:N"| t_subscription
  t_generation_quote -->|"1:N"| t_generation_approval
  t_generation_task -->|"0..1:N"| t_generation_approval
  t_model_version -->|"1:N"| t_generation_quote
  t_model_catalog -->|"1:N"| t_model_version
  t_project -->|"1:N"| t_intent
  t_agent_run -->|"1:N"| t_run_action
  t_org -->|"1:N"| t_workspace
  t_app_user -->|"1:N"| t_member
  t_app_user -->|"1:N"| t_agent_session
  t_app_user -->|"1:N"| t_project
  t_app_user -->|"1:N"| t_session_token
  t_skill -->|"1:N"| t_skill_version
  t_skill -->|"1:N"| t_agent_session
  t_skill -->|"0..1:N"| t_intent
  t_agent_run -->|"0..1:N"| t_generation_quote
  t_member -->|"0..1:N"| t_approval_policy
  t_project -->|"1:N"| t_project_asset_ref
  t_agent_session -->|"1:N"| t_agent_message
  t_agent_run -->|"0..1:N"| t_agent_message
  t_asset -->|"1:N"| t_project_asset_ref
  t_timeline -->|"1:N"| t_timeline_version
  t_node <-.->|"N:M · 经 edge"| t_node
  t_workspace <-.->|"N:M · 经 member"| t_app_user
  t_project <-.->|"N:M · 经 project_asset_ref"| t_asset
classDef tenant fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef canvas fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef gen fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef billing fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef platform fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
class t_api_key,t_app_user,t_member,t_org,t_plan,t_session_token,t_workspace tenant
class t_edge,t_export_job,t_intent,t_node,t_node_version,t_project,t_project_asset_ref,t_timeline,t_timeline_version canvas
class t_agent_message,t_agent_run,t_agent_session,t_interaction,t_run_action,t_run_event,t_skill,t_skill_version agent
class t_generation_approval,t_generation_quote,t_generation_task,t_model_catalog,t_model_version,t_price_rule,t_task_attempt gen
class t_asset,t_asset_embedding,t_retrieval_seen asset
class t_approval_policy,t_hold,t_ledger_account,t_ledger_entry,t_payment_event,t_point_lot,t_purchase_order,t_quota_window,t_subscription billing
class t_audit_log,t_outbox,t_webhook_endpoint platform
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_edge,t_member,t_project_asset_ref junction
  style sg_tenant fill:none,stroke:#3b82f6,stroke-width:1px,stroke-dasharray:5 4
  style sg_canvas fill:none,stroke:#8b5cf6,stroke-width:1px,stroke-dasharray:5 4
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px,stroke-dasharray:0
  style sg_gen fill:none,stroke:#22c55e,stroke-width:1px,stroke-dasharray:5 4
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px,stroke-dasharray:5 4
  style sg_billing fill:none,stroke:#eab308,stroke-width:1px,stroke-dasharray:5 4
  style sg_platform fill:none,stroke:#6b7280,stroke-width:1px,stroke-dasharray:5 4
  linkStyle 16,17,18,19,23,40,43,46,47,48,49,52,53 stroke:#f97316,stroke-width:2.5px
  linkStyle 56,57,58 stroke:#dc2626,stroke-width:3px

Agent 交互相关(圈出来)

会话 → Run → 事件流 / 澄清卡 / 动作回执;Run 产生生成任务。线上是“外键列 · 基数”。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":80}}}%%
flowchart LR
  subgraph sg_agent["⭕ Agent 交互"]
    direction TB
    t_agent_session["agent_session"]
    t_agent_message["agent_message ◆"]
    t_agent_run["agent_run"]
    t_run_event["run_event"]
    t_interaction["interaction"]
    t_run_action["run_action ★"]
    t_skill["skill ◆"]
    t_skill_version["skill_version ◆"]
  end
  subgraph sg_tenant["租户与成员"]
    direction TB
    t_workspace["workspace"]
  end
  subgraph sg_canvas["画布 · 时间轴(项目文档)"]
    direction TB
    t_project["project"]
  end
  subgraph sg_gen["生成 · 报价批准 · 模型"]
    direction TB
    t_generation_task["generation_task"]
  end
  t_workspace -->|"workspace_id · 1:N"| t_project
  t_project -->|"project_id · 1:N"| t_agent_session
  t_workspace -->|"workspace_id · 1:N"| t_agent_session
  t_agent_session -->|"session_id · 1:N"| t_agent_run
  t_workspace -->|"workspace_id · 1:N"| t_agent_run
  t_agent_run -->|"run_id · 1:N"| t_run_event
  t_agent_run -->|"run_id · 1:N"| t_interaction
  t_workspace -->|"workspace_id · 1:N"| t_generation_task
  t_project -->|"project_id · 0..1:N"| t_generation_task
  t_agent_run -->|"run_id · 0..1:N"| t_generation_task
  t_agent_run -->|"run_id · 1:N"| t_run_action
  t_skill -->|"skill_id · 1:N"| t_skill_version
  t_skill -->|"skill_id · 1:N"| t_agent_session
  t_workspace -->|"owner_workspace_id · 0..1:N"| t_skill
  t_agent_session -->|"session_id · 1:N"| t_agent_message
  t_agent_run -->|"run_id · 0..1:N"| t_agent_message
classDef tenant fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef canvas fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef gen fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef billing fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef platform fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
class t_workspace tenant
class t_project canvas
class t_run_event,t_run_action,t_agent_message,t_agent_run,t_interaction,t_agent_session,t_skill_version,t_skill agent
class t_generation_task gen
classDef junction stroke-dasharray:4 3,stroke-width:3px
  style sg_tenant fill:none,stroke:#3b82f6,stroke-width:1px
  style sg_canvas fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_agent fill:#fff7ed,stroke:#f97316,stroke-width:4px
  style sg_gen fill:none,stroke:#22c55e,stroke-width:1px
  linkStyle 1,2,3,4,5,6,9,10,11,12,13,14,15 stroke:#f97316,stroke-width:2.5px
为什么好
  • agent_session 挂在 project 下:对话不是独立的聊天记录,而是项目的一部分,Agent 的产出才能落到画布。
  • run_action 独立成表:发消息、停止、切换模型、回答卡片共用一套幂等机制。
  • interaction 带 card_sha256 与 schema:澄清卡是有状态对象,答案按原卡校验。
  • run_event 按序号追加:断线后用序号续读,不丢不重。

画布与时间轴

项目是一份文档:node 是画布上的节点(可自引用成层级,parent_id),edge 连接两个节点,node_version 记录每次修改与产出素材;timeline 是同一项目的时间轴,导出走 export_job。

%%{init:{"flowchart":{"useMaxWidth":false,"nodeSpacing":40,"rankSpacing":80}}}%%
flowchart LR
  subgraph sg_canvas["画布 · 时间轴(项目文档)"]
    direction TB
    t_project["project"]
    t_node["node"]
    t_node_version["node_version"]
    t_edge["⇄ edge"]
    t_timeline["timeline"]
    t_export_job["export_job"]
    t_intent["intent ★"]
  end
  subgraph sg_asset["素材"]
    direction TB
    t_asset["asset"]
  end
  subgraph sg_gen["生成 · 报价批准 · 模型"]
    direction TB
    t_generation_task["generation_task"]
  end
  subgraph sg_billing["积分账本 · 订阅 · 额度"]
    direction TB
    t_hold["hold"]
  end
  t_asset -->|"cover_asset_id · 0..1:N"| t_project
  t_project -->|"project_id · 1:N"| t_node
  t_node -->|"parent_id · 0..1:N"| t_node
  t_node -->|"node_id · 1:N"| t_node_version
  t_asset -->|"output_asset_id · 0..1:N"| t_node_version
  t_generation_task -->|"task_id · 0..1:N"| t_node_version
  t_project -->|"project_id · 1:N"| t_edge
  t_node -->|"src_node_id · 1:N"| t_edge
  t_node -->|"dst_node_id · 1:N"| t_edge
  t_project -->|"project_id · 1:N"| t_timeline
  t_timeline -->|"timeline_id · 1:N"| t_export_job
  t_asset -->|"output_asset_id · 0..1:N"| t_export_job
  t_hold -->|"hold_id · 0..1:1"| t_export_job
  t_project -->|"project_id · 0..1:N"| t_generation_task
  t_node -->|"node_id · 0..1:N"| t_generation_task
  t_hold -->|"hold_id · 0..1:1"| t_generation_task
  t_generation_task -->|"origin_task_id · 0..1:N"| t_asset
  t_project -->|"project_id · 1:N"| t_intent
  t_node <-.->|"N:M · 经 edge"| t_node
  t_project <-.->|"N:M · 经 project_asset_ref"| t_asset
classDef tenant fill:#dbeafe,stroke:#3b82f6,color:#111,stroke-width:1.5px
classDef canvas fill:#ede9fe,stroke:#8b5cf6,color:#111,stroke-width:1.5px
classDef agent fill:#ffedd5,stroke:#f97316,color:#111,stroke-width:1.5px
classDef gen fill:#dcfce7,stroke:#22c55e,color:#111,stroke-width:1.5px
classDef asset fill:#cffafe,stroke:#06b6d4,color:#111,stroke-width:1.5px
classDef billing fill:#fef9c3,stroke:#eab308,color:#111,stroke-width:1.5px
classDef platform fill:#e5e7eb,stroke:#6b7280,color:#111,stroke-width:1.5px
class t_node,t_node_version,t_export_job,t_edge,t_intent,t_project,t_timeline canvas
class t_generation_task gen
class t_asset asset
class t_hold billing
classDef junction stroke-dasharray:4 3,stroke-width:3px
class t_edge junction
  style sg_canvas fill:none,stroke:#8b5cf6,stroke-width:1px
  style sg_gen fill:none,stroke:#22c55e,stroke-width:1px
  style sg_asset fill:none,stroke:#06b6d4,stroke-width:1px
  style sg_billing fill:none,stroke:#eab308,stroke-width:1px
  linkStyle 18,19 stroke:#dc2626,stroke-width:3px
为什么好
  • 画布与时间轴共用 project:同一份文档的两种视图,改一处另一处同步。
  • node_version 独立:撤销、对比、只对改动镜头重新计费。
  • edge 用 src / dst 两个节点列:一条边就是一次依赖,批量图变更接口一次提交。

多对多(N:M)

数据库里没有“多对多”这种列,它总是靠一张关联表表达。最终设计里有 3 组多对多,其余关系都是一对多或一对一:

多对多的两端关联表关联表的唯一键含义
node ⇄ nodeedgesrc_node_id, dst_node_id, kind节点与节点:一条连线(src、dst)连起两个节点,一个节点可以有多条入边和出边
workspace ⇄ app_usermemberworkspace_id, user_id工作区与用户:一个用户可以加入多个工作区,一个工作区有多个成员
project ⇄ assetproject_asset_refproject_id, asset_id项目与素材:素材归工作区,一个素材可被多个项目引用,一个项目引用多个素材
关系清单 逐条 76 条,含基数

一对多 54 条,一对多(父可选) 20 条,一对一(父可选) 2 条;多对多 3 组。

父表子表外键列(在子表)基数域
agent_runagent_messagerun_id0..1:N 一对多(父可选)Agent 交互 → Agent 交互
agent_sessionagent_messagesession_id1:N 一对多Agent 交互 → Agent 交互
agent_sessionagent_runsession_id1:N 一对多Agent 交互 → Agent 交互
workspaceagent_runworkspace_id1:N 一对多租户与成员 → Agent 交互
app_useragent_sessionuser_id1:N 一对多租户与成员 → Agent 交互
projectagent_sessionproject_id1:N 一对多画布 · 时间轴(项目文档) → Agent 交互
skillagent_sessionskill_id1:N 一对多Agent 交互 → Agent 交互
workspaceagent_sessionworkspace_id1:N 一对多租户与成员 → Agent 交互
workspaceapi_keyworkspace_id1:N 一对多租户与成员 → 租户与成员
memberapproval_policymember_id0..1:N 一对多(父可选)租户与成员 → 积分账本 · 订阅 · 额度
workspaceapproval_policyworkspace_id1:N 一对多租户与成员 → 积分账本 · 订阅 · 额度
generation_taskassetorigin_task_id0..1:N 一对多(父可选)生成 · 报价批准 · 模型 → 素材
workspaceassetworkspace_id1:N 一对多租户与成员 → 素材
assetasset_embeddingasset_id1:N 一对多素材 → 素材
workspaceaudit_logworkspace_id0..1:N 一对多(父可选)租户与成员 → 平台
nodeedgedst_node_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
nodeedgesrc_node_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
projectedgeproject_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
assetexport_joboutput_asset_id0..1:N 一对多(父可选)素材 → 画布 · 时间轴(项目文档)
holdexport_jobhold_id0..1:1 一对一(父可选)积分账本 · 订阅 · 额度 → 画布 · 时间轴(项目文档)
timelineexport_jobtimeline_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
workspaceexport_jobworkspace_id1:N 一对多租户与成员 → 画布 · 时间轴(项目文档)
generation_quotegeneration_approvalquote_id1:N 一对多生成 · 报价批准 · 模型 → 生成 · 报价批准 · 模型
generation_taskgeneration_approvaltask_id0..1:N 一对多(父可选)生成 · 报价批准 · 模型 → 生成 · 报价批准 · 模型
agent_rungeneration_quotesource_run_id0..1:N 一对多(父可选)Agent 交互 → 生成 · 报价批准 · 模型
model_versiongeneration_quotemodel_version_id1:N 一对多生成 · 报价批准 · 模型 → 生成 · 报价批准 · 模型
workspacegeneration_quoteworkspace_id1:N 一对多租户与成员 → 生成 · 报价批准 · 模型
agent_rungeneration_taskrun_id0..1:N 一对多(父可选)Agent 交互 → 生成 · 报价批准 · 模型
api_keygeneration_taskapi_key_id0..1:N 一对多(父可选)租户与成员 → 生成 · 报价批准 · 模型
holdgeneration_taskhold_id0..1:1 一对一(父可选)积分账本 · 订阅 · 额度 → 生成 · 报价批准 · 模型
model_cataloggeneration_taskmodel_id0..1:N 一对多(父可选)生成 · 报价批准 · 模型 → 生成 · 报价批准 · 模型
nodegeneration_tasknode_id0..1:N 一对多(父可选)画布 · 时间轴(项目文档) → 生成 · 报价批准 · 模型
projectgeneration_taskproject_id0..1:N 一对多(父可选)画布 · 时间轴(项目文档) → 生成 · 报价批准 · 模型
workspacegeneration_taskworkspace_id1:N 一对多租户与成员 → 生成 · 报价批准 · 模型
workspaceholdworkspace_id1:N 一对多租户与成员 → 积分账本 · 订阅 · 额度
projectintentproject_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
skillintentskill_id0..1:N 一对多(父可选)Agent 交互 → 画布 · 时间轴(项目文档)
workspaceintentworkspace_id1:N 一对多租户与成员 → 画布 · 时间轴(项目文档)
agent_runinteractionrun_id1:N 一对多Agent 交互 → Agent 交互
workspaceledger_accountworkspace_id0..1:N 一对多(父可选)租户与成员 → 积分账本 · 订阅 · 额度
ledger_accountledger_entryaccount_id1:N 一对多积分账本 · 订阅 · 额度 → 积分账本 · 订阅 · 额度
point_lotledger_entrylot_id0..1:N 一对多(父可选)积分账本 · 订阅 · 额度 → 积分账本 · 订阅 · 额度
app_usermemberuser_id1:N 一对多租户与成员 → 租户与成员
workspacememberworkspace_id1:N 一对多租户与成员 → 租户与成员
model_catalogmodel_versionmodel_id1:N 一对多生成 · 报价批准 · 模型 → 生成 · 报价批准 · 模型
nodenodeparent_id0..1:N 一对多(父可选)画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
projectnodeproject_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
workspacenodeworkspace_id1:N 一对多租户与成员 → 画布 · 时间轴(项目文档)
assetnode_versionoutput_asset_id0..1:N 一对多(父可选)素材 → 画布 · 时间轴(项目文档)
generation_tasknode_versiontask_id0..1:N 一对多(父可选)生成 · 报价批准 · 模型 → 画布 · 时间轴(项目文档)
nodenode_versionnode_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
workspacepoint_lotworkspace_id1:N 一对多租户与成员 → 积分账本 · 订阅 · 额度
model_catalogprice_rulemodel_id1:N 一对多生成 · 报价批准 · 模型 → 生成 · 报价批准 · 模型
app_userprojectowner_id1:N 一对多租户与成员 → 画布 · 时间轴(项目文档)
assetprojectcover_asset_id0..1:N 一对多(父可选)素材 → 画布 · 时间轴(项目文档)
workspaceprojectworkspace_id1:N 一对多租户与成员 → 画布 · 时间轴(项目文档)
assetproject_asset_refasset_id1:N 一对多素材 → 画布 · 时间轴(项目文档)
projectproject_asset_refproject_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
workspacepurchase_orderworkspace_id1:N 一对多租户与成员 → 积分账本 · 订阅 · 额度
workspacequota_windowworkspace_id1:N 一对多租户与成员 → 积分账本 · 订阅 · 额度
assetretrieval_seenasset_id1:N 一对多素材 → 素材
agent_runrun_actionrun_id1:N 一对多Agent 交互 → Agent 交互
agent_runrun_eventrun_id1:N 一对多Agent 交互 → Agent 交互
app_usersession_tokenuser_id1:N 一对多租户与成员 → 租户与成员
workspaceskillowner_workspace_id0..1:N 一对多(父可选)租户与成员 → Agent 交互
skillskill_versionskill_id1:N 一对多Agent 交互 → Agent 交互
plansubscriptionplan_code1:N 一对多租户与成员 → 积分账本 · 订阅 · 额度
workspacesubscriptionworkspace_id1:N 一对多租户与成员 → 积分账本 · 订阅 · 额度
generation_tasktask_attempttask_id1:N 一对多生成 · 报价批准 · 模型 → 生成 · 报价批准 · 模型
model_catalogtask_attemptmodel_id1:N 一对多生成 · 报价批准 · 模型 → 生成 · 报价批准 · 模型
projecttimelineproject_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
workspacetimelineworkspace_id1:N 一对多租户与成员 → 画布 · 时间轴(项目文档)
timelinetimeline_versiontimeline_id1:N 一对多画布 · 时间轴(项目文档) → 画布 · 时间轴(项目文档)
workspacewebhook_endpointworkspace_id1:N 一对多租户与成员 → 平台
orgworkspaceorg_id1:N 一对多租户与成员 → 租户与成员
planworkspaceplan_code1:N 一对多租户与成员 → 租户与成员
完整 ER:46 个实体的全部列,带主键 / 外键与基数符号(鸦脚)
erDiagram
  agent_message {
    CHAR id PK
    CHAR session_id FK
    CHAR run_id FK
    TEXT role
    TEXT delivery
    TEXT content
    JSONB attachments
    TEXT status
    INT content_seq
    JSONB suggestions
    TEXT client_action_id
    TIMESTAMPTZ created_at
    TIMESTAMPTZ completed_at
  }
  agent_run {
    CHAR id PK
    CHAR session_id FK
    CHAR workspace_id FK
    TEXT status
    TEXT workflow_id
    TEXT skill_version
    BIGINT budget_centi
    BIGINT spent_centi
    JSONB input
    TEXT error_code
    TEXT error_message
    TIMESTAMPTZ started_at
    TIMESTAMPTZ finished_at
    TIMESTAMPTZ created_at
  }
  agent_session {
    CHAR id PK
    CHAR project_id FK
    CHAR workspace_id FK
    CHAR user_id FK
    TEXT skill_id FK
    TEXT skill_version
    TEXT title
    TIMESTAMPTZ last_message_at
    TIMESTAMPTZ created_at
  }
  api_key {
    CHAR id PK
    CHAR workspace_id FK
    TEXT name
    TEXT key_prefix
    BYTEA key_hash
    TEXT_ARR scopes
    TEXT status
    TIMESTAMPTZ expires_at
    TIMESTAMPTZ last_used_at
    TIMESTAMPTZ created_at
  }
  app_user {
    CHAR id PK
    TEXT email
    TEXT display_name
    TEXT password_hash
    TEXT status
    TIMESTAMPTZ created_at
    TIMESTAMPTZ last_login_at
  }
  approval_policy {
    CHAR id PK
    CHAR workspace_id FK
    BIGINT member_id FK
    TEXT mode
    BIGINT per_task_limit_centi
    BIGINT daily_limit_centi
  }
  asset {
    CHAR id PK
    CHAR workspace_id FK
    TEXT kind
    TEXT source
    TEXT storage_key
    TEXT mime
    BIGINT bytes
    INT duration_ms
    INT width
    INT height
    REAL fps
    BYTEA sha256
    CHAR origin_task_id FK
    TEXT origin_url
    TEXT moderation
    TEXT_ARR tags
    JSONB meta
    TIMESTAMPTZ deleted_at
    TIMESTAMPTZ created_at
  }
  asset_embedding {
    CHAR asset_id PK
    INT segment_no PK
    INT start_ms
    INT end_ms
    TEXT caption
    FLOAT4__ embedding
  }
  audit_log {
    BIGINT id PK
    CHAR workspace_id FK
    TEXT actor
    TEXT action
    TEXT target_type
    TEXT target_id
    JSONB diff
    INET ip
    TIMESTAMPTZ created_at
  }
  edge {
    CHAR id PK
    CHAR project_id FK
    CHAR src_node_id FK
    CHAR dst_node_id FK
    TEXT kind
  }
  export_job {
    CHAR id PK
    CHAR workspace_id FK
    CHAR timeline_id FK
    BIGINT timeline_ver
    TEXT preset
    TEXT status
    TEXT workflow_id
    CHAR output_asset_id FK
    CHAR hold_id FK
    TEXT error_code
    TEXT error_message
    TIMESTAMPTZ created_at
    TIMESTAMPTZ finished_at
  }
  generation_approval {
    CHAR id PK
    CHAR quote_id FK
    TEXT client_approval_id
    CHAR request_sha256
    CHAR quote_sha256
    CHAR task_id FK
    TIMESTAMPTZ created_at
  }
  generation_quote {
    CHAR id PK
    CHAR workspace_id FK
    TEXT client_request_id
    CHAR request_sha256
    CHAR quote_sha256
    CHAR model_version_id FK
    BIGINT quoted_centi
    TIMESTAMPTZ expires_at
    CHAR source_run_id FK
    CHAR source_sha256
    TEXT status
  }
  generation_task {
    CHAR id PK
    CHAR workspace_id FK
    CHAR project_id FK
    CHAR node_id FK
    CHAR run_id FK
    CHAR api_key_id FK
    TEXT capability
    CHAR model_id FK
    JSONB request
    BYTEA request_hash
    TEXT idem_key
    TEXT status
    CHAR hold_id FK
    BIGINT quoted_centi
    BIGINT charged_centi
    JSONB outputs
    TEXT error_code
    TEXT error_message
    TEXT workflow_id
    INT attempt_count
    TIMESTAMPTZ created_at
    TIMESTAMPTZ finished_at
  }
  hold {
    CHAR id PK
    CHAR workspace_id FK
    TEXT ref_type
    TEXT ref_id
    BIGINT amount_centi
    TEXT status
    JSONB lot_allocation
    TIMESTAMPTZ expires_at
    TIMESTAMPTZ created_at
  }
  intent {
    CHAR id PK
    CHAR workspace_id FK
    CHAR project_id FK
    TEXT adapter
    JSONB source_ref
    TEXT skill_id FK
    TEXT skill_version
    TEXT status
    TIMESTAMPTZ created_at
  }
  interaction {
    CHAR id PK
    CHAR run_id FK
    TEXT kind
    JSONB request
    JSONB response
    CHAR card_sha256
    JSONB schema
    TEXT client_action_id
    TEXT status
    TIMESTAMPTZ expires_at
    TIMESTAMPTZ created_at
    TIMESTAMPTZ resolved_at
  }
  ledger_account {
    CHAR id PK
    CHAR workspace_id FK
    TEXT kind
    BIGINT balance_centi
    BIGINT version
  }
  ledger_entry {
    BIGINT id PK
    CHAR txn_id
    CHAR account_id FK
    SMALLINT direction
    BIGINT amount_centi
    TEXT ref_type
    TEXT ref_id
    CHAR lot_id FK
    TEXT memo
    TIMESTAMPTZ created_at PK
  }
  member {
    BIGINT id PK
    CHAR workspace_id FK
    CHAR user_id FK
    TEXT role
    BOOL seat
    TIMESTAMPTZ created_at
  }
  model_catalog {
    CHAR id PK
    TEXT capability
    TEXT provider_key
    TEXT model_name
    TEXT display_name
    JSONB input_schema
    JSONB limits
    JSONB default_params
    INT max_concurrency
    INT timeout_s
    TEXT status
    TEXT adapter_key
  }
  model_version {
    CHAR id PK
    CHAR model_id FK
    TEXT version_key
    JSONB config
    CHAR config_sha256
    TIMESTAMPTZ published_at
  }
  node {
    CHAR id PK
    CHAR project_id FK
    CHAR workspace_id FK
    TEXT type
    CHAR parent_id FK
    TEXT title
    REAL pos_x
    REAL pos_y
    JSONB data
    INT current_ver
    CHAR lock_owner
    TIMESTAMPTZ lock_until
    TEXT status
    TEXT created_by
    TIMESTAMPTZ deleted_at
    TIMESTAMPTZ created_at
    TIMESTAMPTZ updated_at
  }
  node_version {
    CHAR node_id PK
    INT version PK
    JSONB data
    CHAR output_asset_id FK
    CHAR task_id FK
    TEXT created_by
    TIMESTAMPTZ created_at
  }
  org {
    CHAR id PK
    TEXT name
    TEXT status
    TIMESTAMPTZ created_at
  }
  outbox {
    BIGINT id PK
    TEXT topic
    TEXT key
    JSONB payload
    TEXT status
    INT attempts
    TIMESTAMPTZ next_at
    TIMESTAMPTZ created_at
  }
  payment_event {
    BIGINT id PK
    TEXT provider
    TEXT event_id
    TEXT type
    JSONB payload
    TIMESTAMPTZ processed_at
    TIMESTAMPTZ received_at
  }
  plan {
    TEXT code PK
    TEXT name
    TEXT interval
    INT price_cents
    TEXT currency
    BIGINT monthly_points_centi
    INT seats
    JSONB features
  }
  point_lot {
    CHAR id PK
    CHAR workspace_id FK
    TEXT source
    BIGINT granted_centi
    BIGINT remaining_centi
    TIMESTAMPTZ expires_at
    SMALLINT priority
    TIMESTAMPTZ created_at
  }
  price_rule {
    CHAR id PK
    CHAR model_id FK
    TEXT unit
    BIGINT cost_micro
    BIGINT charge_centi
    JSONB modifiers
    TIMESTAMPTZ effective_from
    TIMESTAMPTZ effective_to
  }
  project {
    CHAR id PK
    CHAR workspace_id FK
    CHAR owner_id FK
    TEXT title
    TEXT kind
    TEXT aspect_ratio
    TEXT language
    CHAR cover_asset_id FK
    TEXT status
    BIGINT doc_version
    TIMESTAMPTZ deleted_at
    TIMESTAMPTZ created_at
    TIMESTAMPTZ updated_at
  }
  project_asset_ref {
    CHAR project_id PK
    CHAR asset_id PK
    TEXT purpose
    TIMESTAMPTZ created_at
  }
  purchase_order {
    CHAR id PK
    CHAR workspace_id FK
    TEXT kind
    TEXT sku_code
    INT amount_cents
    TEXT currency
    TEXT status
    TIMESTAMPTZ created_at
  }
  quota_window {
    CHAR workspace_id PK
    DATE window_start PK
    TEXT kind PK
    BIGINT limit_units
    BIGINT used_units
    BOOL overage_enabled
  }
  retrieval_seen {
    TEXT context_key PK
    CHAR asset_id PK
  }
  run_action {
    CHAR id PK
    CHAR run_id FK
    TEXT kind
    TEXT client_action_id
    CHAR request_sha256
    TEXT status
    INT attempt
    JSONB result
    TIMESTAMPTZ created_at
  }
  run_event {
    BIGINT id PK
    CHAR run_id FK
    INT seq
    TEXT type
    JSONB payload
    TIMESTAMPTZ created_at PK
  }
  session_token {
    CHAR id PK
    CHAR user_id FK
    BYTEA token_hash
    TIMESTAMPTZ expires_at
    TIMESTAMPTZ revoked_at
    TIMESTAMPTZ created_at
  }
  skill {
    TEXT id PK
    TEXT display_name
    CHAR owner_workspace_id FK
    TEXT status
    TIMESTAMPTZ created_at
  }
  skill_version {
    TEXT skill_id PK
    TEXT version PK
    JSONB manifest
    CHAR sha256
    TEXT status
    TIMESTAMPTZ published_at
  }
  subscription {
    CHAR id PK
    CHAR workspace_id FK
    TEXT plan_code FK
    TEXT status
    TEXT provider
    TEXT provider_sub_id
    TIMESTAMPTZ current_period_start
    TIMESTAMPTZ current_period_end
    BOOL cancel_at_period_end
  }
  task_attempt {
    CHAR id PK
    CHAR task_id FK
    SMALLINT seq
    CHAR model_id FK
    TEXT provider_task_id
    TEXT provider_request_id
    TEXT status
    BIGINT cost_micro
    JSONB usage
    TEXT error_code
    TEXT error_message
    TIMESTAMPTZ submitted_at
    TIMESTAMPTZ finished_at
  }
  timeline {
    CHAR id PK
    CHAR project_id FK
    CHAR workspace_id FK
    SMALLINT fps
    INT width
    INT height
    INT duration_ms
    JSONB doc
    BIGINT version
    TIMESTAMPTZ updated_at
  }
  timeline_version {
    CHAR timeline_id PK
    BIGINT version PK
    JSONB doc
    BIGINT restored_from
    TEXT created_by
    TIMESTAMPTZ created_at
  }
  webhook_endpoint {
    CHAR id PK
    CHAR workspace_id FK
    TEXT url
    BYTEA secret_hash
    TEXT_ARR events
    TEXT status
    TIMESTAMPTZ created_at
  }
  workspace {
    CHAR id PK
    CHAR org_id FK
    TEXT name
    TEXT region
    TEXT plan_code FK
    TEXT status
    TIMESTAMPTZ created_at
    TIMESTAMPTZ updated_at
  }
  plan ||--o{ workspace : "plan_code"
  workspace ||--o{ member : "workspace_id"
  workspace ||--o{ api_key : "workspace_id"
  workspace ||--o{ project : "workspace_id"
  asset |o--o{ project : "cover_asset_id"
  project ||--o{ node : "project_id"
  workspace ||--o{ node : "workspace_id"
  node |o--o{ node : "parent_id"
  node ||--o{ node_version : "node_id"
  asset |o--o{ node_version : "output_asset_id"
  generation_task |o--o{ node_version : "task_id"
  project ||--o{ edge : "project_id"
  node ||--o{ edge : "src_node_id"
  node ||--o{ edge : "dst_node_id"
  project ||--o{ timeline : "project_id"
  workspace ||--o{ timeline : "workspace_id"
  workspace ||--o{ export_job : "workspace_id"
  timeline ||--o{ export_job : "timeline_id"
  asset |o--o{ export_job : "output_asset_id"
  hold |o--o| export_job : "hold_id"
  project ||--o{ agent_session : "project_id"
  workspace ||--o{ agent_session : "workspace_id"
  agent_session ||--o{ agent_run : "session_id"
  workspace ||--o{ agent_run : "workspace_id"
  agent_run ||--o{ run_event : "run_id"
  agent_run ||--o{ interaction : "run_id"
  model_catalog ||--o{ price_rule : "model_id"
  workspace ||--o{ generation_task : "workspace_id"
  project |o--o{ generation_task : "project_id"
  node |o--o{ generation_task : "node_id"
  agent_run |o--o{ generation_task : "run_id"
  api_key |o--o{ generation_task : "api_key_id"
  model_catalog |o--o{ generation_task : "model_id"
  hold |o--o| generation_task : "hold_id"
  generation_task ||--o{ task_attempt : "task_id"
  model_catalog ||--o{ task_attempt : "model_id"
  workspace ||--o{ asset : "workspace_id"
  generation_task |o--o{ asset : "origin_task_id"
  asset ||--o{ asset_embedding : "asset_id"
  asset ||--o{ retrieval_seen : "asset_id"
  workspace |o--o{ ledger_account : "workspace_id"
  ledger_account ||--o{ ledger_entry : "account_id"
  point_lot |o--o{ ledger_entry : "lot_id"
  workspace ||--o{ point_lot : "workspace_id"
  workspace ||--o{ hold : "workspace_id"
  workspace ||--o{ subscription : "workspace_id"
  plan ||--o{ subscription : "plan_code"
  workspace ||--o{ purchase_order : "workspace_id"
  workspace |o--o{ audit_log : "workspace_id"
  generation_quote ||--o{ generation_approval : "quote_id"
  generation_task |o--o{ generation_approval : "task_id"
  model_version ||--o{ generation_quote : "model_version_id"
  model_catalog ||--o{ model_version : "model_id"
  workspace ||--o{ quota_window : "workspace_id"
  workspace ||--o{ approval_policy : "workspace_id"
  project ||--o{ intent : "project_id"
  agent_run ||--o{ run_action : "run_id"
  org ||--o{ workspace : "org_id"
  app_user ||--o{ member : "user_id"
  app_user ||--o{ agent_session : "user_id"
  app_user ||--o{ project : "owner_id"
  app_user ||--o{ session_token : "user_id"
  skill ||--o{ skill_version : "skill_id"
  skill ||--o{ agent_session : "skill_id"
  skill |o--o{ intent : "skill_id"
  workspace |o--o{ skill : "owner_workspace_id"
  workspace ||--o{ webhook_endpoint : "workspace_id"
  workspace ||--o{ generation_quote : "workspace_id"
  workspace ||--o{ intent : "workspace_id"
  agent_run |o--o{ generation_quote : "source_run_id"
  member |o--o{ approval_policy : "member_id"
  project ||--o{ project_asset_ref : "project_id"
  agent_session ||--o{ agent_message : "session_id"
  agent_run |o--o{ agent_message : "run_id"
  asset ||--o{ project_asset_ref : "asset_id"
  timeline ||--o{ timeline_version : "timeline_id"
实测验证 最终 DDL 在真实 PostgreSQL 16 上整体执行通过,展开看核对项
最终版已实测:折叠后的最终 DDL 在真实 PostgreSQL 16 上整份执行成功,共 37 张表(另有 2 个默认分区),库里外键 0 个(按设计不建外键);ER 里的 57 条关系,两端表和子表外键列都在实际库里存在;表结构页的列、类型、可空、默认值全部来自这次执行后的库内读取。

实测验证(真实 PostgreSQL 16)

把本页的核心 DDL 与增量 DDL 在一个临时的 PostgreSQL 16 实例里真正执行,再把实际建出的表、列、主键、类型,逐项与本页的 ER 图对照;增量里的唯一键、检查约束用真实插入去触发。20 / 20 项通过。

查看全部核对项
核对项结果
核心 DDL 在真实 PostgreSQL 16 上全部建成(pgvector 的类型与 HNSW 索引除外,本机没有该扩展)通过
ER 实体 = 文档声明的 30 张表通过
除 30 张表外只多出 2 个默认分区(run_event_default、ledger_entry_default)通过
ER 中列出的每一列都在实际表里存在通过
ER 中标 PK 的列确实是主键通过
实际主键列在 ER 中都标了 PK通过
ER 的每条关系:两端表存在,子表里确有该外键列通过
外键列类型与父表主键类型一致通过
所有「<表名>_id」列都画成了关系(没有漏画)通过
核心 DDL 与设计声明一致:不建数据库外键通过
ER 关系数 49,没有重复通过
增量 DDL 在核心 DDL 之上执行成功,新增 7 张表通过
增量 ER 里的每个实体与列都在建出的表里通过
增量 ER 的每条关系:两端表存在,子表里确有该外键列通过
增量关系:外键列类型与父表主键类型一致通过
增量 DDL 同样不建数据库外键(沿用 Lumen 约定),关系靠应用层与唯一键通过
重放批准(同一报价、同一 client_approval_id)被唯一键拒绝通过
报价状态只能取 4 个值(检查约束)通过
同一个 run 内同一个 client_action_id 只能有一条动作(唯一键,重复写入被拒绝)通过
每个工作区只能有一条默认审批策略(表达式唯一索引,NULL 成员也受约束)通过
验证中发现并已修正的两个问题
  • 核心 DDL 的订单表原来叫 "order"(保留字),而 ER 图里画的是 purchase_order,两处对不上;已统一为 purchase_order。
  • 增量 DDL 里审批策略表用了表达式做主键,PostgreSQL 不允许;已改成独立主键加表达式唯一索引。
没有验证的:pgvector 的 vector(1024) 列与 HNSW 索引(本机没有该扩展,验证时换成数组类型);分区表在大数据量下的表现;与应用代码的集成。

最终设计 · 表结构

重点本页重点
  • 每个域一张总览表(存什么、主要列),完整列定义在表下方展开。★ 新增、◆ 补全;最该细看的是“生成 · 报价批准”和“积分账本”两组。
  • 账本只追加、金额用整数最小单位,状态迁移用条件更新,影响 0 行即冲突。完整 DDL 可直接执行。

最终产物:46 张表 · 405 列 · 76 条关系(另有 2 个默认分区表)。下面按域列出每张表的列;完整 DDL 是同一份、可直接执行的脚本。★ = 吸收竞品后新增;◆ = 补全(原设计提到或被引用,没给 DDL)。

租户与成员

表存什么列数主要列
org ◆组织4name, status …
app_user ◆用户7email, display_name, password_hash, status, last_login_at …
session_token ◆登录会话6user_id, token_hash, expires_at, revoked_at …
plan套餐8name, interval, price_cents, currency, monthly_points_centi, seats …
workspace工作区8org_id, name, region, plan_code, status …
member成员6workspace_id, user_id, role, seat …
api_keyAPI Key10workspace_id, name, key_prefix, key_hash, scopes, status …
租户与成员:每张表的全部列、类型、默认值

org

列类型空默认
id PKchar非空
nametext非空
statustext非空'active'::text
created_attimestamptz非空now()

app_user

列类型空默认
id PKchar非空
emailtext非空
display_nametext可空
password_hashtext可空
statustext非空'active'::text
created_attimestamptz非空now()
last_login_attimestamptz可空

session_token

列类型空默认
id PKchar非空
user_idchar非空
token_hashbytea非空
expires_attimestamptz非空
revoked_attimestamptz可空
created_attimestamptz非空now()

plan

列类型空默认
code PKtext非空
nametext非空
intervaltext非空
price_centsinteger非空
currencytext非空
monthly_points_centibigint非空
seatsinteger非空
featuresjsonb非空

workspace

列类型空默认
id PKchar非空
org_idchar非空
nametext非空
regiontext非空
plan_codetext非空'free'::text
statustext非空'active'::text
created_attimestamptz非空now()
updated_attimestamptz非空now()

member

列类型空默认
id PKbigint非空
workspace_idchar非空
user_idchar非空
roletext非空
seatboolean非空true
created_attimestamptz非空now()

api_key

列类型空默认
id PKchar非空
workspace_idchar非空
nametext非空
key_prefixtext非空
key_hashbytea非空
scopestext[]非空
statustext非空'active'::text
expires_attimestamptz可空
last_used_attimestamptz可空
created_attimestamptz非空now()

画布 · 时间轴(项目文档)

表存什么列数主要列
project项目13workspace_id, owner_id, title, kind, aspect_ratio, language …
project_asset_ref ◆项目引用的素材4purpose …
node节点17project_id, workspace_id, type, parent_id, title, pos_x …
node_version节点版本7data, output_asset_id, task_id, created_by …
edge连线5project_id, src_node_id, dst_node_id, kind
timeline时间轴10project_id, workspace_id, fps, width, height, duration_ms …
timeline_version ◆时间轴版本6doc, restored_from, created_by …
export_job导出任务13workspace_id, timeline_id, timeline_ver, preset, status, workflow_id …
intent ★创作意图9workspace_id, project_id, adapter, source_ref, skill_id, skill_version …
画布 · 时间轴(项目文档):每张表的全部列、类型、默认值

project

列类型空默认
id PKchar非空
workspace_idchar非空
owner_idchar非空
titletext非空
kindtext非空
aspect_ratiotext非空'9:16'::text
languagetext非空'en'::text
cover_asset_idchar可空
statustext非空'draft'::text
doc_versionbigint非空0
deleted_attimestamptz可空
created_attimestamptz非空now()
updated_attimestamptz非空now()

project_asset_ref

列类型空默认
project_id PKchar非空
asset_id PKchar非空
purposetext非空'reference'::text
created_attimestamptz非空now()

node

列类型空默认
id PKchar非空
project_idchar非空
workspace_idchar非空
typetext非空
parent_idchar可空
titletext可空
pos_xreal非空0
pos_yreal非空0
datajsonb非空'{}'::jsonb
current_verinteger非空1
lock_ownerchar可空
lock_untiltimestamptz可空
statustext非空'idle'::text
created_bytext非空
deleted_attimestamptz可空
created_attimestamptz非空now()
updated_attimestamptz非空now()

node_version

列类型空默认
node_id PKchar非空
version PKinteger非空
datajsonb非空
output_asset_idchar可空
task_idchar可空
created_bytext非空
created_attimestamptz非空now()

edge

列类型空默认
id PKchar非空
project_idchar非空
src_node_idchar非空
dst_node_idchar非空
kindtext非空

timeline

列类型空默认
id PKchar非空
project_idchar非空
workspace_idchar非空
fpssmallint非空30
widthinteger非空
heightinteger非空
duration_msinteger非空0
docjsonb非空
versionbigint非空1
updated_attimestamptz非空now()

timeline_version

列类型空默认
timeline_id PKchar非空
version PKbigint非空
docjsonb非空
restored_frombigint可空
created_bytext非空
created_attimestamptz非空now()

export_job

列类型空默认
id PKchar非空
workspace_idchar非空
timeline_idchar非空
timeline_verbigint非空
presettext非空
statustext非空'queued'::text
workflow_idtext可空
output_asset_idchar可空
hold_idchar可空
error_codetext可空
error_messagetext可空
created_attimestamptz非空now()
finished_attimestamptz可空

intent

列类型空默认
id PKchar非空
workspace_idchar非空
project_idchar非空
adaptertext非空
source_refjsonb非空
skill_idtext可空
skill_versiontext可空
statustext非空'open'::text
created_attimestamptz非空now()

⭕ Agent 交互

表存什么列数主要列
agent_session会话9project_id, workspace_id, user_id, skill_id, skill_version, title …
agent_message ◆13session_id, run_id, role, delivery, content, attachments …
agent_run一次回复14session_id, workspace_id, status, workflow_id, skill_version, budget_centi …
run_event事件流6run_id, seq, type, payload …
interaction澄清卡12run_id, kind, request, response, card_sha256, schema …
run_action ★动作回执9run_id, kind, client_action_id, request_sha256, status, attempt …
skill ◆技能5display_name, owner_workspace_id, status …
skill_version ◆技能版本6manifest, sha256, status, published_at …
Agent 交互:每张表的全部列、类型、默认值

agent_session

列类型空默认
id PKchar非空
project_idchar非空
workspace_idchar非空
user_idchar非空
skill_idtext非空
skill_versiontext非空
titletext可空
last_message_attimestamptz可空
created_attimestamptz非空now()

agent_message

列类型空默认
id PKchar非空
session_idchar非空
run_idchar可空
roletext非空
deliverytext可空
contenttext非空''::text
attachmentsjsonb非空'[]'::jsonb
statustext非空'complete'::text
content_seqinteger非空0
suggestionsjsonb非空'[]'::jsonb
client_action_idtext可空
created_attimestamptz非空now()
completed_attimestamptz可空

agent_run

列类型空默认
id PKchar非空
session_idchar非空
workspace_idchar非空
statustext非空
workflow_idtext非空
skill_versiontext非空
budget_centibigint非空
spent_centibigint非空0
inputjsonb非空
error_codetext可空
error_messagetext可空
started_attimestamptz可空
finished_attimestamptz可空
created_attimestamptz非空now()

run_event

列类型空默认
id PKbigint非空
run_idchar非空
seqinteger非空
typetext非空
payloadjsonb非空
created_at PKtimestamptz非空now()

interaction

列类型空默认
id PKchar非空
run_idchar非空
kindtext非空
requestjsonb非空
responsejsonb可空
card_sha256char可空
schemajsonb可空
client_action_idtext可空
statustext非空'pending'::text
expires_attimestamptz可空
created_attimestamptz非空now()
resolved_attimestamptz可空

run_action

列类型空默认
id PKchar非空
run_idchar非空
kindtext非空
client_action_idtext非空
request_sha256char非空
statustext非空
attemptinteger非空1
resultjsonb可空
created_attimestamptz非空now()

skill

列类型空默认
id PKtext非空
display_nametext非空
owner_workspace_idchar可空
statustext非空'active'::text
created_attimestamptz非空now()

skill_version

列类型空默认
skill_id PKtext非空
version PKtext非空
manifestjsonb非空
sha256char非空
statustext非空'published'::text
published_attimestamptz可空

生成 · 报价批准 · 模型

表存什么列数主要列
model_catalog模型12capability, provider_key, model_name, display_name, input_schema, limits …
model_version ★模型版本6model_id, version_key, config, config_sha256, published_at
price_rule价格规则8model_id, unit, cost_micro, charge_centi, modifiers, effective_from …
generation_quote ★报价11workspace_id, client_request_id, request_sha256, quote_sha256, model_version_id, quoted_centi …
generation_approval ★批准7quote_id, client_approval_id, request_sha256, quote_sha256, task_id …
generation_task生成任务22workspace_id, project_id, node_id, run_id, api_key_id, capability …
task_attempt提交尝试13task_id, seq, model_id, provider_task_id, provider_request_id, status …
生成 · 报价批准 · 模型:每张表的全部列、类型、默认值

model_catalog

列类型空默认
id PKchar非空
capabilitytext非空
provider_keytext非空
model_nametext非空
display_nametext非空
input_schemajsonb非空
limitsjsonb非空
default_paramsjsonb非空'{}'::jsonb
max_concurrencyinteger非空10
timeout_sinteger非空900
statustext非空'active'::text
adapter_keytext非空'default'::text

model_version

列类型空默认
id PKchar非空
model_idchar非空
version_keytext非空
configjsonb非空
config_sha256char非空
published_attimestamptz可空

price_rule

列类型空默认
id PKchar非空
model_idchar非空
unittext非空
cost_microbigint非空
charge_centibigint非空
modifiersjsonb非空'{}'::jsonb
effective_fromtimestamptz非空
effective_totimestamptz可空

generation_quote

列类型空默认
id PKchar非空
workspace_idchar非空
client_request_idtext非空
request_sha256char非空
quote_sha256char非空
model_version_idchar非空
quoted_centibigint非空
expires_attimestamptz非空
source_run_idchar可空
source_sha256char可空
statustext非空'quoted'::text

generation_approval

列类型空默认
id PKchar非空
quote_idchar非空
client_approval_idtext非空
request_sha256char非空
quote_sha256char非空
task_idchar可空
created_attimestamptz非空now()

generation_task

列类型空默认
id PKchar非空
workspace_idchar非空
project_idchar可空
node_idchar可空
run_idchar可空
api_key_idchar可空
capabilitytext非空
model_idchar可空
requestjsonb非空
request_hashbytea非空
idem_keytext可空
statustext非空
hold_idchar可空
quoted_centibigint非空
charged_centibigint可空
outputsjsonb可空
error_codetext可空
error_messagetext可空
workflow_idtext可空
attempt_countinteger非空0
created_attimestamptz非空now()
finished_attimestamptz可空

task_attempt

列类型空默认
id PKchar非空
task_idchar非空
seqsmallint非空
model_idchar非空
provider_task_idtext可空
provider_request_idtext可空
statustext非空
cost_microbigint可空
usagejsonb可空
error_codetext可空
error_messagetext可空
submitted_attimestamptz可空
finished_attimestamptz可空

素材

表存什么列数主要列
asset素材19workspace_id, kind, source, storage_key, mime, bytes …
asset_embedding素材向量6start_ms, end_ms, caption, embedding …
retrieval_seen检索去重2 …
素材:每张表的全部列、类型、默认值

asset

列类型空默认
id PKchar非空
workspace_idchar非空
kindtext非空
sourcetext非空
storage_keytext非空
mimetext非空
bytesbigint非空
duration_msinteger可空
widthinteger可空
heightinteger可空
fpsreal可空
sha256bytea非空
origin_task_idchar可空
origin_urltext可空
moderationtext非空'pending'::text
tagstext[]非空'{}'::text[]
metajsonb非空'{}'::jsonb
deleted_attimestamptz可空
created_attimestamptz非空now()

asset_embedding

列类型空默认
asset_id PKchar非空
segment_no PKinteger非空0
start_msinteger可空
end_msinteger可空
captiontext可空
embeddingfloat4[]非空

retrieval_seen

列类型空默认
context_key PKtext非空
asset_id PKchar非空

积分账本 · 订阅 · 额度

表存什么列数主要列
ledger_account账户5workspace_id, kind, balance_centi, version
ledger_entry账本分录10txn_id, account_id, direction, amount_centi, ref_type, ref_id …
point_lot积分批次8workspace_id, source, granted_centi, remaining_centi, expires_at, priority …
hold冻结9workspace_id, ref_type, ref_id, amount_centi, status, lot_allocation …
quota_window ★额度窗口6limit_units, used_units, overage_enabled …
approval_policy ★审批策略6workspace_id, member_id, mode, per_task_limit_centi, daily_limit_centi
subscription订阅9workspace_id, plan_code, status, provider, provider_sub_id, current_period_start …
purchase_order订单8workspace_id, kind, sku_code, amount_cents, currency, status …
payment_event支付事件7provider, event_id, type, payload, processed_at, received_at
积分账本 · 订阅 · 额度:每张表的全部列、类型、默认值

ledger_account

列类型空默认
id PKchar非空
workspace_idchar可空
kindtext非空
balance_centibigint非空0
versionbigint非空0

ledger_entry

列类型空默认
id PKbigint非空
txn_idchar非空
account_idchar非空
directionsmallint非空
amount_centibigint非空
ref_typetext非空
ref_idtext非空
lot_idchar可空
memotext可空
created_at PKtimestamptz非空now()

point_lot

列类型空默认
id PKchar非空
workspace_idchar非空
sourcetext非空
granted_centibigint非空
remaining_centibigint非空
expires_attimestamptz可空
prioritysmallint非空
created_attimestamptz非空now()

hold

列类型空默认
id PKchar非空
workspace_idchar非空
ref_typetext非空
ref_idtext非空
amount_centibigint非空
statustext非空
lot_allocationjsonb非空
expires_attimestamptz非空
created_attimestamptz非空now()

quota_window

列类型空默认
workspace_id PKchar非空
window_start PKdate非空
kind PKtext非空
limit_unitsbigint非空
used_unitsbigint非空0
overage_enabledboolean非空false

approval_policy

列类型空默认
id PKchar非空
workspace_idchar非空
member_idbigint可空
modetext非空
per_task_limit_centibigint可空
daily_limit_centibigint可空

subscription

列类型空默认
id PKchar非空
workspace_idchar非空
plan_codetext非空
statustext非空
providertext非空
provider_sub_idtext非空
current_period_starttimestamptz可空
current_period_endtimestamptz可空
cancel_at_period_endboolean非空false

purchase_order

列类型空默认
id PKchar非空
workspace_idchar非空
kindtext非空
sku_codetext可空
amount_centsinteger非空
currencytext非空
statustext非空
created_attimestamptz非空now()

payment_event

列类型空默认
id PKbigint非空
providertext非空
event_idtext非空
typetext非空
payloadjsonb非空
processed_attimestamptz可空
received_attimestamptz非空now()

平台

表存什么列数主要列
outbox事件外发8topic, key, payload, status, attempts, next_at …
webhook_endpoint ◆Webhook 地址7workspace_id, url, secret_hash, events, status …
audit_log审计9workspace_id, actor, action, target_type, target_id, diff …
平台:每张表的全部列、类型、默认值

outbox

列类型空默认
id PKbigint非空
topictext非空
keytext非空
payloadjsonb非空
statustext非空'pending'::text
attemptsinteger非空0
next_attimestamptz非空now()
created_attimestamptz非空now()

webhook_endpoint

列类型空默认
id PKchar非空
workspace_idchar非空
urltext非空
secret_hashbytea非空
eventstext[]非空
statustext非空'active'::text
created_attimestamptz非空now()

audit_log

列类型空默认
id PKbigint非空
workspace_idchar可空
actortext非空
actiontext非空
target_typetext可空
target_idtext可空
diffjsonb可空
ipinet可空
created_attimestamptz非空now()

原设计提到、最终版没有单独建表的表,去哪了

原设计的“表域总览”列了约 60 个名字,但只给了 30 张的 DDL。最终版把每个名字都交代清楚:要么建了,要么说明由谁代替,要么明确没做。

原设计提到的表最终版去向
user建为 app_user(“user”是 PostgreSQL 保留字)
identity_provider_binding未建。app_user.password_hash 为空表示走 SSO;第三方登录绑定表放到做 SSO 时再设计
canvas不单建:画布 = project + node + edge
timeline_track / timeline_clip不单建:轨道和片段在 timeline.doc(JSONB)里;历史在 timeline_version
checkpoint不建业务表:由 Agent 运行时(LangGraph)的检查点存储管理,可与业务库同一个 PostgreSQL 实例、独立 schema
task_event不单建:任务状态变化走 outbox 投递,运行过程事件在 run_event
model_route不单建:路由规则放配置,价格与约束在 model_version / price_rule
quota建为 quota_window
asset_variant / asset_tag / character / character_voice / collection_job不单建:放在 asset.kind(如 character_ref)和 asset 的 JSONB 元数据里
order / payment建为 purchase_order / payment_event
sku / redeem_code未建:套餐与积分包由 plan 表达;兑换码以后需要时再加
moderation_record / banned_term / risk_rule / config_item未建:违禁词与规则放配置中心;输入审核拒绝记为任务的 content_rejected,供应商审核结果记在 task_attempt,素材审核结果在 asset.moderation;F11 的 L5 人工复核队列依赖这些表,属于后续
usage_record不单建:用量从 ledger_entry 与 generation_task 聚合进 ClickHouse,GET /billing/usage 读聚合

完整 DDL(PostgreSQL 16)

展开完整脚本
CREATE TABLE workspace (
  id            CHAR(26) PRIMARY KEY,
  org_id        CHAR(26) NOT NULL,
  name          TEXT NOT NULL,
  region        TEXT NOT NULL CHECK (region IN ('cn','global')),
  plan_code     TEXT NOT NULL DEFAULT 'free',
  status        TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','suspended','closed')),
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE member (
  id            BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  workspace_id  CHAR(26) NOT NULL,
  user_id       CHAR(26) NOT NULL,
  role          TEXT NOT NULL CHECK (role IN ('owner','admin','editor','viewer')),
  seat          BOOLEAN NOT NULL DEFAULT true,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (workspace_id, user_id)
);

CREATE TABLE api_key (
  id            CHAR(26) PRIMARY KEY,
  workspace_id  CHAR(26) NOT NULL,
  name          TEXT NOT NULL,
  key_prefix    TEXT NOT NULL,            -- 展示用前 8 位
  key_hash      BYTEA NOT NULL UNIQUE,    -- SHA-256,明文不入库
  scopes        TEXT[] NOT NULL,          -- e.g. {'generate:write','project:read'}
  status        TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','revoked','expired')),
  expires_at    TIMESTAMPTZ,
  last_used_at  TIMESTAMPTZ,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE project (
  id            CHAR(26) PRIMARY KEY,
  workspace_id  CHAR(26) NOT NULL,
  owner_id      CHAR(26) NOT NULL,
  title         TEXT NOT NULL,
  kind          TEXT NOT NULL CHECK (kind IN ('freeform','url_ad','viral_clone','drama','batch')),
  aspect_ratio  TEXT NOT NULL DEFAULT '9:16',
  language      TEXT NOT NULL DEFAULT 'en',
  cover_asset_id CHAR(26),
  status        TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','generating','ready','archived')),
  doc_version   BIGINT NOT NULL DEFAULT 0,        -- 画布整体版本,节点变更自增
  deleted_at    TIMESTAMPTZ,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX project_ws_updated ON project (workspace_id, updated_at DESC) WHERE deleted_at IS NULL;

CREATE TABLE node (
  id            CHAR(26) PRIMARY KEY,
  project_id    CHAR(26) NOT NULL,
  workspace_id  CHAR(26) NOT NULL,
  type          TEXT NOT NULL CHECK (type IN
                 ('text','script','image','video','audio','character','storyboard','scene','shot','ref_video','product','plan','final_video')),
  parent_id     CHAR(26),                       -- scene → shot 层级
  title         TEXT,
  pos_x         REAL NOT NULL DEFAULT 0,
  pos_y         REAL NOT NULL DEFAULT 0,
  data          JSONB NOT NULL DEFAULT '{}',    -- 按 type 的 schema,见 4.5
  current_ver   INT NOT NULL DEFAULT 1,
  lock_owner    CHAR(26),                       -- 节点级编辑提示锁(软锁,30s 租期)
  lock_until    TIMESTAMPTZ,
  status        TEXT NOT NULL DEFAULT 'idle' CHECK (status IN ('idle','pending','running','done','failed','stale')),
  created_by    TEXT NOT NULL,                  -- 'user:<id>' | 'agent:<run_id>'
  deleted_at    TIMESTAMPTZ,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX node_project ON node (project_id) WHERE deleted_at IS NULL;

CREATE TABLE node_version (
  node_id       CHAR(26) NOT NULL,
  version       INT NOT NULL,
  data          JSONB NOT NULL,
  output_asset_id CHAR(26),                     -- 该版本产物
  task_id       CHAR(26),                       -- 来源生成任务(溯源)
  created_by    TEXT NOT NULL,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  PRIMARY KEY (node_id, version)
);

CREATE TABLE edge (
  id            CHAR(26) PRIMARY KEY,
  project_id    CHAR(26) NOT NULL,
  src_node_id   CHAR(26) NOT NULL,
  dst_node_id   CHAR(26) NOT NULL,
  kind          TEXT NOT NULL CHECK (kind IN ('data','ref','sequence')),
  UNIQUE (src_node_id, dst_node_id, kind)
);

CREATE TABLE timeline (
  id           CHAR(26) PRIMARY KEY,
  project_id   CHAR(26) NOT NULL,
  workspace_id CHAR(26) NOT NULL,
  fps          SMALLINT NOT NULL DEFAULT 30,
  width        INT NOT NULL, height INT NOT NULL,
  duration_ms  INT NOT NULL DEFAULT 0,
  doc          JSONB NOT NULL,             -- 轨道/片段/转场/字幕样式,见 4.5
  version      BIGINT NOT NULL DEFAULT 1,
  updated_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE export_job (
  id           CHAR(26) PRIMARY KEY,
  workspace_id CHAR(26) NOT NULL,
  timeline_id  CHAR(26) NOT NULL,
  timeline_ver BIGINT NOT NULL,
  preset       TEXT NOT NULL,              -- '1080p_h264' | '720p_h264' | '4k_h265'
  status       TEXT NOT NULL DEFAULT 'queued'
               CHECK (status IN ('queued','rendering','succeeded','failed','cancelled')),
  workflow_id  TEXT,
  output_asset_id CHAR(26),
  hold_id      CHAR(26) UNIQUE,                 -- 一个导出只对应一笔冻结(与 hold.UNIQUE(ref_type, ref_id) 一致)
  error_code   TEXT, error_message TEXT,
  created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
  finished_at  TIMESTAMPTZ
);

CREATE TABLE agent_session (
  id            CHAR(26) PRIMARY KEY,
  project_id    CHAR(26) NOT NULL,
  workspace_id  CHAR(26) NOT NULL,
  user_id       CHAR(26) NOT NULL,
  skill_id      TEXT NOT NULL,              -- 'url_to_ad' | 'viral_clone' | 'freeform'
  skill_version TEXT NOT NULL,              -- 会话创建时固定的技能版本(决策 D9)
  title         TEXT,
  last_message_at TIMESTAMPTZ,                -- 历史列表排序;每写入一条消息时更新
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE agent_run (
  id             CHAR(26) PRIMARY KEY,
  session_id     CHAR(26) NOT NULL,
  workspace_id   CHAR(26) NOT NULL,
  status         TEXT NOT NULL CHECK (status IN
                  ('queued','running','waiting_user','succeeded','failed','cancelled')),
  workflow_id    TEXT NOT NULL,
  skill_version  TEXT NOT NULL,
  budget_centi   BIGINT NOT NULL,           -- 本 run 预算
  spent_centi    BIGINT NOT NULL DEFAULT 0,
  input          JSONB NOT NULL,
  error_code     TEXT, error_message TEXT,
  started_at     TIMESTAMPTZ, finished_at TIMESTAMPTZ,
  created_at     TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 同一会话同一时刻只允许一个"活跃 run"
CREATE UNIQUE INDEX one_active_run_per_session
  ON agent_run (session_id) WHERE status IN ('queued','running','waiting_user');

CREATE TABLE run_event (
  id           BIGINT GENERATED ALWAYS AS IDENTITY,
  run_id       CHAR(26) NOT NULL,
  seq          INT NOT NULL,                -- run 内单调递增,SSE 断线续传依据
  type         TEXT NOT NULL,               -- 见 5.6 事件表
  payload      JSONB NOT NULL,
  created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
  PRIMARY KEY (created_at, id)
) PARTITION BY RANGE (created_at);
CREATE UNIQUE INDEX run_event_run_seq ON run_event (run_id, seq, created_at);

CREATE TABLE interaction (
  id            CHAR(26) PRIMARY KEY,
  run_id        CHAR(26) NOT NULL,
  kind          TEXT NOT NULL,              -- 'collect_info'|'confirm_plan'|'pick_option'|'confirm_cost'|'review_script'|'payment_required'
  request       JSONB NOT NULL,
  response      JSONB,
  card_sha256   CHAR(64),                   -- 卡片内容哈希:答案按原卡校验
  schema        JSONB,                      -- 字段 key / kind / required / options
  client_action_id TEXT,                    -- 幂等键
  status        TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','resolved','expired','cancelled')),
  expires_at    TIMESTAMPTZ,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  resolved_at   TIMESTAMPTZ
);
CREATE UNIQUE INDEX interaction_idem ON interaction (id, client_action_id) WHERE client_action_id IS NOT NULL;

CREATE TABLE model_catalog (
  id             CHAR(26) PRIMARY KEY,
  capability     TEXT NOT NULL,             -- 't2v' 'i2v' 'flf2v' 'r2v' 't2i' 'tts' 'lipsync' ...
  provider_key   TEXT NOT NULL,             -- 'ark' 'dashscope' 'tencent_vod' ...
  model_name     TEXT NOT NULL,
  display_name   TEXT NOT NULL,
  input_schema   JSONB NOT NULL,            -- JSON Schema:可传参数
  limits         JSONB NOT NULL,            -- 最长时长、分辨率、参考图数量
  default_params JSONB NOT NULL DEFAULT '{}',
  max_concurrency INT NOT NULL DEFAULT 10,
  timeout_s      INT NOT NULL DEFAULT 900,
  status         TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','beta')),
  adapter_key    TEXT NOT NULL DEFAULT 'default',  -- 适配器注册键:新增模型只加数据 + 一个适配器
  UNIQUE (provider_key, model_name, capability)
);

CREATE TABLE price_rule (
  id            CHAR(26) PRIMARY KEY,
  model_id      CHAR(26) NOT NULL,
  unit          TEXT NOT NULL CHECK (unit IN ('second','image','token','call','char')),
  cost_micro    BIGINT NOT NULL,            -- 我方成本,1e-6 USD
  charge_centi  BIGINT NOT NULL,            -- 向用户收取积分(1/100 点)/单位
  modifiers     JSONB NOT NULL DEFAULT '{}',-- {"resolution":{"1080p":1.5},"audio":1.3}
  effective_from TIMESTAMPTZ NOT NULL,
  effective_to   TIMESTAMPTZ
);

CREATE TABLE generation_task (
  id             CHAR(26) PRIMARY KEY,
  workspace_id   CHAR(26) NOT NULL,
  project_id     CHAR(26),
  node_id        CHAR(26),
  run_id         CHAR(26),
  api_key_id     CHAR(26),
  capability     TEXT NOT NULL,
  model_id       CHAR(26),                  -- 路由决定后写入
  request        JSONB NOT NULL,
  request_hash   BYTEA NOT NULL,
  idem_key       TEXT,
  status         TEXT NOT NULL CHECK (status IN
                  ('created','held','queued','submitting','submit_uncertain','running',
                   'succeeded','failed','cancelling','cancelled','expired')),
  hold_id        CHAR(26) UNIQUE,                   -- 一个任务只对应一笔冻结(与 hold.UNIQUE(ref_type, ref_id) 一致)
  quoted_centi   BIGINT NOT NULL,
  charged_centi  BIGINT,
  outputs        JSONB,
  error_code     TEXT, error_message TEXT,
  workflow_id    TEXT,
  attempt_count  INT NOT NULL DEFAULT 0,
  created_at     TIMESTAMPTZ NOT NULL DEFAULT now(),
  finished_at    TIMESTAMPTZ
);
CREATE UNIQUE INDEX task_idem ON generation_task (workspace_id, idem_key) WHERE idem_key IS NOT NULL;
CREATE INDEX task_ws_created ON generation_task (workspace_id, created_at DESC);
CREATE INDEX task_active ON generation_task (status) WHERE status IN ('queued','submitting','submit_uncertain','running','cancelling');

CREATE TABLE task_attempt (
  id             CHAR(26) PRIMARY KEY,
  task_id        CHAR(26) NOT NULL,
  seq            SMALLINT NOT NULL,
  model_id       CHAR(26) NOT NULL,
  provider_task_id TEXT,
  provider_request_id TEXT,
  status         TEXT NOT NULL,             -- submitted/succeeded/failed/uncertain
  cost_micro     BIGINT,
  usage          JSONB,
  error_code     TEXT, error_message TEXT,
  submitted_at   TIMESTAMPTZ, finished_at TIMESTAMPTZ,
  UNIQUE (task_id, seq)
);
CREATE INDEX attempt_provider ON task_attempt (model_id, provider_task_id);

CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE asset (
  id           CHAR(26) PRIMARY KEY,
  workspace_id CHAR(26) NOT NULL,
  kind         TEXT NOT NULL CHECK (kind IN ('image','video','audio','font','doc','character_ref')),
  source       TEXT NOT NULL CHECK (source IN ('upload','generated','collected','library')),
  storage_key  TEXT NOT NULL,               -- COS/S3 key
  mime         TEXT NOT NULL, bytes BIGINT NOT NULL,
  duration_ms  INT, width INT, height INT, fps REAL,
  sha256       BYTEA NOT NULL,
  origin_task_id CHAR(26),                  -- 生成溯源
  origin_url   TEXT,                        -- 采集来源
  moderation   TEXT NOT NULL DEFAULT 'pending' CHECK (moderation IN ('pending','pass','review','reject')),
  tags         TEXT[] NOT NULL DEFAULT '{}',
  meta         JSONB NOT NULL DEFAULT '{}',
  deleted_at   TIMESTAMPTZ,
  created_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX asset_ws_kind ON asset (workspace_id, kind, created_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX asset_hash ON asset (workspace_id, sha256);

CREATE TABLE asset_embedding (
  asset_id   CHAR(26) NOT NULL,
  segment_no INT NOT NULL DEFAULT 0,        -- 视频按镜头切分
  start_ms   INT, end_ms INT,
  caption    TEXT,
  embedding  vector(1024) NOT NULL,
  PRIMARY KEY (asset_id, segment_no)
);
CREATE INDEX asset_emb_hnsw ON asset_embedding USING hnsw (embedding vector_cosine_ops) WITH (m=16, ef_construction=128);

CREATE TABLE retrieval_seen (                -- 继承旧 knowhub 的 context_key 去重语义
  context_key TEXT NOT NULL,
  asset_id    CHAR(26) NOT NULL,
  PRIMARY KEY (context_key, asset_id)
);

CREATE TABLE ledger_account (
  id            CHAR(26) PRIMARY KEY,
  workspace_id  CHAR(26),                   -- 系统账户为 NULL
  kind          TEXT NOT NULL CHECK (kind IN
                 ('wallet_paid','wallet_bonus','wallet_subscription','held','revenue','cost_of_goods','expired','refund_pool')),
  balance_centi BIGINT NOT NULL DEFAULT 0,  -- 缓存余额,可由 entry 重算
  version       BIGINT NOT NULL DEFAULT 0,
  UNIQUE (workspace_id, kind)
);

CREATE TABLE ledger_entry (
  id            BIGINT GENERATED ALWAYS AS IDENTITY,
  txn_id        CHAR(26) NOT NULL,          -- 同一笔业务的所有分录
  account_id    CHAR(26) NOT NULL,
  direction     SMALLINT NOT NULL CHECK (direction IN (1,-1)),   -- +入 / -出
  amount_centi  BIGINT NOT NULL CHECK (amount_centi > 0),
  ref_type      TEXT NOT NULL,              -- 'hold'|'task'|'run'|'order'|'subscription'|'adjust'|'expire'(run = 对话推理用量,task = 媒体生成,决策 D4)
  ref_id        TEXT NOT NULL,
  lot_id        CHAR(26),                   -- 积分批次(有过期时间、扣减顺序)
  memo          TEXT,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  PRIMARY KEY (created_at, id)
) PARTITION BY RANGE (created_at);
CREATE INDEX entry_txn ON ledger_entry (txn_id);
CREATE INDEX entry_ref ON ledger_entry (ref_type, ref_id);

CREATE TABLE point_lot (                      -- 积分批次:过期时间 + 扣减优先级
  id            CHAR(26) PRIMARY KEY,
  workspace_id  CHAR(26) NOT NULL,
  source        TEXT NOT NULL CHECK (source IN ('purchase','subscription','bonus','promo','compensation')),
  granted_centi BIGINT NOT NULL,
  remaining_centi BIGINT NOT NULL,
  expires_at    TIMESTAMPTZ,
  priority      SMALLINT NOT NULL,          -- 小者先扣:promo < subscription < bonus < purchase
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX lot_spend ON point_lot (workspace_id, priority, expires_at NULLS LAST) WHERE remaining_centi > 0;

CREATE TABLE hold (
  id            CHAR(26) PRIMARY KEY,
  workspace_id  CHAR(26) NOT NULL,
  ref_type      TEXT NOT NULL, ref_id TEXT NOT NULL,
  amount_centi  BIGINT NOT NULL,
  status        TEXT NOT NULL CHECK (status IN ('active','settled','released','expired')),
  lot_allocation JSONB NOT NULL,            -- [{lot_id, centi}],释放/结算时按此回写
  expires_at    TIMESTAMPTZ NOT NULL,       -- 兜底:超时自动 release
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (ref_type, ref_id)                 -- 同一业务只能一个冻结 → 幂等
);

CREATE TABLE plan (
  code TEXT PRIMARY KEY, name TEXT NOT NULL, interval TEXT NOT NULL CHECK (interval IN ('month','year')),
  price_cents INT NOT NULL, currency TEXT NOT NULL,
  monthly_points_centi BIGINT NOT NULL, seats INT NOT NULL, features JSONB NOT NULL
);
CREATE TABLE subscription (
  id CHAR(26) PRIMARY KEY, workspace_id CHAR(26) NOT NULL, plan_code TEXT NOT NULL,
  status TEXT NOT NULL CHECK (status IN ('trialing','active','past_due','cancelled','expired')),
  provider TEXT NOT NULL, provider_sub_id TEXT NOT NULL,
  current_period_start TIMESTAMPTZ, current_period_end TIMESTAMPTZ,
  cancel_at_period_end BOOLEAN NOT NULL DEFAULT false,
  UNIQUE (provider, provider_sub_id)
);
CREATE TABLE purchase_order (
  id CHAR(26) PRIMARY KEY, workspace_id CHAR(26) NOT NULL,
  kind TEXT NOT NULL CHECK (kind IN ('subscription','topup','seat','storage')),
  sku_code TEXT, amount_cents INT NOT NULL, currency TEXT NOT NULL,
  status TEXT NOT NULL CHECK (status IN ('created','paid','failed','refunded','partially_refunded')),
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE payment_event (                  -- Webhook 原始事件,幂等入账依据
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  provider TEXT NOT NULL, event_id TEXT NOT NULL, type TEXT NOT NULL,
  payload JSONB NOT NULL, processed_at TIMESTAMPTZ,
  received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (provider, event_id)
);

CREATE TABLE outbox (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  topic TEXT NOT NULL, key TEXT NOT NULL, payload JSONB NOT NULL,
  status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','sent','dead')),
  attempts INT NOT NULL DEFAULT 0, next_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX outbox_pending ON outbox (next_at) WHERE status='pending';

CREATE TABLE audit_log (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  workspace_id CHAR(26), actor TEXT NOT NULL, action TEXT NOT NULL,
  target_type TEXT, target_id TEXT, diff JSONB, ip INET, created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 分区父表必须有分区才能写入。DEFAULT 分区兜底;生产用 pg_partman 按月预建并滚动归档。
CREATE TABLE run_event_default    PARTITION OF run_event    DEFAULT;
CREATE TABLE ledger_entry_default PARTITION OF ledger_entry DEFAULT;

-- ===== 吸收竞品后新增的 7 张表 =====
-- 1) 报价与批准:批准必须带两个哈希,并有有效期(来自复刻里跑通的流程;LibTV 预算只提醒的做法被否决)
CREATE TABLE generation_quote (
  id              CHAR(26) PRIMARY KEY,
  workspace_id    CHAR(26) NOT NULL,
  client_request_id TEXT NOT NULL,
  request_sha256  CHAR(64) NOT NULL,              -- 提示词、参数、模型版本的规范化摘要
  quote_sha256    CHAR(64) NOT NULL,              -- 价格与有效期的摘要
  model_version_id CHAR(26) NOT NULL,
  quoted_centi    BIGINT NOT NULL CHECK (quoted_centi >= 0),
  expires_at      TIMESTAMPTZ NOT NULL,
  source_run_id   CHAR(26),                       -- 来自 Agent 回复时记录来源
  source_sha256   CHAR(64),
  status          TEXT NOT NULL DEFAULT 'quoted' CHECK (status IN ('quoted','approved','expired','rejected')),
  UNIQUE (workspace_id, client_request_id)
);
CREATE TABLE generation_approval (
  id               CHAR(26) PRIMARY KEY,
  quote_id         CHAR(26) NOT NULL,             -- → generation_quote.id(沿用 Lumen 约定:不建数据库外键,靠应用层与唯一键)
  client_approval_id TEXT NOT NULL,
  request_sha256   CHAR(64) NOT NULL,
  quote_sha256     CHAR(64) NOT NULL,
  task_id          CHAR(26),
  created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (quote_id, client_approval_id)            -- 重放只返回同一个任务
);
-- 2) 审批策略:Ask First / Always Allow(来自 TopView),带额度上限
CREATE TABLE approval_policy (
  id              CHAR(26) PRIMARY KEY,
  workspace_id    CHAR(26) NOT NULL,
  member_id       BIGINT,                         -- NULL = 工作区默认策略
  mode            TEXT NOT NULL CHECK (mode IN ('ask_first','always_allow_within_limit')),
  per_task_limit_centi BIGINT,
  daily_limit_centi    BIGINT
);
CREATE UNIQUE INDEX approval_policy_scope ON approval_policy (workspace_id, COALESCE(member_id, 0));
-- 3) 额度窗口:日额度与「额外用量」开关(来自 CreaTok);与积分分开
CREATE TABLE quota_window (
  workspace_id    CHAR(26) NOT NULL,
  window_start    DATE NOT NULL,
  kind            TEXT NOT NULL CHECK (kind IN ('agent_daily','plan_monthly')),
  limit_units     BIGINT NOT NULL,
  used_units      BIGINT NOT NULL DEFAULT 0,
  overage_enabled BOOLEAN NOT NULL DEFAULT FALSE,
  PRIMARY KEY (workspace_id, window_start, kind)
);
-- 4) 创作意图:URL / 商品 / 脚本 / 参考视频等入口统一成 intent(来自 Creatify、Pollo、TopView)
CREATE TABLE intent (
  id              CHAR(26) PRIMARY KEY,
  workspace_id    CHAR(26) NOT NULL,
  project_id      CHAR(26) NOT NULL,
  adapter         TEXT NOT NULL,                  -- 'url' | 'product' | 'script' | 'ref_video' | 'freeform'
  source_ref      JSONB NOT NULL,
  skill_id        TEXT,
  skill_version   TEXT,
  status          TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','planned','running','done','cancelled')),
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 5) 模型注册:版本、价格表、适配器键(我们在复刻里遇到的最大扩展障碍)
CREATE TABLE model_version (
  id              CHAR(26) PRIMARY KEY,
  model_id        CHAR(26) NOT NULL,              -- → model_catalog.id
  version_key     TEXT NOT NULL,
  config          JSONB NOT NULL,                 -- 时长/画幅/分辨率约束、价格表、是否可取消
  config_sha256   CHAR(64) NOT NULL,
  published_at    TIMESTAMPTZ,
  UNIQUE (model_id, version_key)
);
-- 6) 动作回执:消息、停止、切换模型、澄清卡回答都是幂等动作
CREATE TABLE run_action (
  id              CHAR(26) PRIMARY KEY,
  run_id          CHAR(26) NOT NULL,
  kind            TEXT NOT NULL CHECK (kind IN ('message','stop','model_change','interaction_response')),
  client_action_id TEXT NOT NULL,
  request_sha256  CHAR(64) NOT NULL,
  status          TEXT NOT NULL,                  -- accepted | terminal_observed | rejected | unknown
  attempt         INT NOT NULL DEFAULT 1,
  result          JSONB,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (run_id, kind, client_action_id)         -- 同一个动作 ID 内容不同则冲突(409)
);
-- 7) 澄清卡:带内容哈希,答案按原卡校验

-- 补全:最终设计里被其他表、接口、决策引用,但原设计没有给 DDL 的表。
-- 沿用 Lumen 的约定:不建数据库外键,关系由应用层与唯一键保证。
CREATE TABLE org (
  id            CHAR(26) PRIMARY KEY,
  name          TEXT NOT NULL,
  status        TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','suspended','closed')),
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE app_user (                          -- “user” 是 PostgreSQL 保留字,所以叫 app_user
  id            CHAR(26) PRIMARY KEY,
  email         TEXT NOT NULL UNIQUE,
  display_name  TEXT,
  password_hash TEXT,                              -- 为空表示走 SSO
  status        TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','locked','deleted')),
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  last_login_at TIMESTAMPTZ
);

CREATE TABLE session_token (                     -- 刷新令牌(只存哈希),登出与轮换都落在这里
  id            CHAR(26) PRIMARY KEY,
  user_id       CHAR(26) NOT NULL,
  token_hash    BYTEA NOT NULL UNIQUE,
  expires_at    TIMESTAMPTZ NOT NULL,
  revoked_at    TIMESTAMPTZ,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE skill (
  id                 TEXT PRIMARY KEY,             -- 'url_to_ad' | 'viral_clone' | 'freeform' | 开发者发布的 id
  display_name       TEXT NOT NULL,
  owner_workspace_id CHAR(26),                     -- 为空表示官方
  status             TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','retired')),
  created_at         TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE skill_version (                     -- 会话创建时固定一个版本,发布新版不影响进行中的会话
  skill_id      TEXT NOT NULL,
  version       TEXT NOT NULL,
  manifest      JSONB NOT NULL,
  sha256        CHAR(64) NOT NULL,
  status        TEXT NOT NULL DEFAULT 'published' CHECK (status IN ('draft','published','retired')),
  published_at  TIMESTAMPTZ,
  PRIMARY KEY (skill_id, version)
);

CREATE TABLE webhook_endpoint (                  -- 开放平台回调地址;投递走 outbox
  id            CHAR(26) PRIMARY KEY,
  workspace_id  CHAR(26) NOT NULL,
  url           TEXT NOT NULL,
  secret_hash   BYTEA NOT NULL,
  events        TEXT[] NOT NULL,
  status        TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled')),
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE project_asset_ref (                 -- 素材归工作区,项目只记引用和用途(项目 ⇄ 素材 多对多)
  project_id    CHAR(26) NOT NULL,
  asset_id      CHAR(26) NOT NULL,
  purpose       TEXT NOT NULL DEFAULT 'reference',
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  PRIMARY KEY (project_id, asset_id)
);

CREATE TABLE timeline_version (                  -- 时间线每次保存一条历史;恢复 = 以旧内容新建一个版本
  timeline_id   CHAR(26) NOT NULL,
  version       BIGINT NOT NULL,
  doc           JSONB NOT NULL,
  restored_from BIGINT,
  created_by    TEXT NOT NULL,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  PRIMARY KEY (timeline_id, version)
);

-- 账本只追加:分录写入后不能改、不能删(决策 D3)。归档用分区的 DETACH,不经过这个触发器。
CREATE FUNCTION ledger_entry_append_only() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
  RAISE EXCEPTION 'ledger_entry is append-only';
END $$;
CREATE TRIGGER ledger_entry_no_change BEFORE UPDATE OR DELETE ON ledger_entry
  FOR EACH ROW EXECUTE FUNCTION ledger_entry_append_only();

CREATE TABLE agent_message (                     -- 对话消息:历史、刷新恢复、下一轮上下文都读这里;run_event 只记执行过程
  id               CHAR(26) PRIMARY KEY,
  session_id       CHAR(26) NOT NULL,
  run_id           CHAR(26),                      -- 用户消息:它启动或补充的 run;助手消息:产生它的 run
  role             TEXT NOT NULL CHECK (role IN ('user','assistant')),
  delivery         TEXT CHECK (delivery IN ('new_run','guidance','resume')),   -- 仅用户消息:新任务 / 补充到运行中的任务 / 回答卡片
  content          TEXT NOT NULL DEFAULT '',      -- Markdown;助手消息流式期间按 content_seq 定期落库
  attachments      JSONB NOT NULL DEFAULT '[]',   -- [{asset_id, purpose}]
  status           TEXT NOT NULL DEFAULT 'complete' CHECK (status IN ('streaming','complete','stopped','failed')),
  content_seq      INT NOT NULL DEFAULT 0,        -- content 已包含到哪个事件 seq;刷新后从这里续接
  suggestions      JSONB NOT NULL DEFAULT '[]',   -- 助手消息结束时给出的后续建议,最多 3 条
  client_action_id TEXT,                          -- 用户消息的幂等键,与 run_action.client_action_id 相同
  created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
  completed_at     TIMESTAMPTZ
);
CREATE UNIQUE INDEX agent_message_idem ON agent_message (session_id, client_action_id) WHERE client_action_id IS NOT NULL;
CREATE INDEX agent_message_page ON agent_message (session_id, created_at DESC, id DESC);
CREATE UNIQUE INDEX one_streaming_message_per_run ON agent_message (run_id) WHERE status = 'streaming';
为什么好
  • 约束写进数据库:应用会有 bug,约束不会。报价哈希、幂等唯一键、状态枚举都是数据库层面的保证。
  • 账本只追加:每一分都有来源和去向,失败任务释放冻结。
  • PostgreSQL + JSONB:节点、模型约束、Skill 参数变化很快,用 JSONB 承载可变部分。
状态机、JSONB 约定、索引与分区(保留)

4.1 数据设计总则

  • PostgreSQL 16,不建数据库外键(沿用旧网关经验),一致性由应用事务、唯一索引、对账任务保证。
  • 主键:对外资源用 ULID(CHAR(26),带前缀如 prj_、tsk_ 仅在 API 层加);内部流水用 BIGINT IDENTITY。
  • 租户内的业务表带 workspace_id(分库键);子表经父表确定工作区(例如 node_version 经 node);全局表(用户、组织、模型目录、技能、套餐)不带。时间用 timestamptz;需要软删除的表用 deleted_at。
  • 金额与积分:BIGINT 存最小单位(积分 = 1/100 点,即 "centi-point";货币 = 分/美分),禁止浮点。
  • 大表按月分区:run_event、ledger_entry。原设计还列了 task_event、usage_record,最终版没有建这两张表(任务事件走 outbox,用量从账本与任务聚合进 ClickHouse)。
  • 状态字段用 TEXT + CHECK,状态迁移通过 UPDATE ... WHERE status = :from 乐观实现,不满足影响行数为 0 即视为冲突。

4.4 状态机

生成任务 generation_task

stateDiagram-v2
  [*] --> created: 批准报价(与冻结同一事务)
  created --> held: 冻结积分成功(同一事务内)
  held --> queued: outbox 启动工作流
  queued --> cancelled: 排队中取消
  queued --> submitting: Worker 领取
  submitting --> running: 供应商受理(拿到 provider_task_id)
  submitting --> submit_uncertain: 超时/连接断开,是否受理未知
  submit_uncertain --> running: 查询确认已受理
  submit_uncertain --> queued: 查询确认未受理,重投(可换供应商)
  running --> succeeded: 产物落库+审核通过
  running --> failed: 供应商失败/审核拒绝
  running --> cancelling: 用户取消
  cancelling --> cancelled: 供应商确认/超时强制
  running --> expired: 超过 timeout
  succeeded --> [*]
  failed --> [*]
  cancelled --> [*]
  expired --> [*]

结算规则:succeeded → settle(实扣 = 报价,可含供应商返回的实际 usage 修正);failed/cancelled/expired → release 全额;submit_uncertain 保持冻结,直到确认;供应商返回失败但没有原因、无法确认是否已计费时同样保持冻结,界面显示“费用待核对”,人工处理。余额不足、报价过期、哈希不符在批准时被拒(402 / 409),输入审核在报价时被拒(403),这些情况都不建任务、不扣费;路由也在报价时完成。对外只暴露 6 态:queued / running / succeeded / failed / cancelled / expired。

Agent Run

stateDiagram-v2
  [*] --> queued
  queued --> running: Workflow 启动
  running --> waiting_user: 发出 interaction
  waiting_user --> running: interaction_response(Signal)
  waiting_user --> cancelled: 取消 / 卡片过期
  running --> succeeded
  running --> failed
  running --> cancelled

积分冻结 hold

stateDiagram-v2
  [*] --> active: reserve
  active --> settled: settle(实扣 ≤ 冻结,差额返还)
  active --> released: release(失败/取消)
  active --> expired: 到期兜底释放(跳过状态不明、待人工核对的任务)

节点 node

stateDiagram-v2
  direction LR
  [*] --> idle
  idle --> pending: 触发生成
  pending --> running
  running --> done
  running --> failed
  failed --> pending: 重试
  done --> stale: 上游变更
  stale --> pending: 重生成
  done --> pending: 重新生成

导出 export_job

stateDiagram-v2
  [*] --> queued
  queued --> rendering: Worker 领取
  rendering --> succeeded: 产出入库
  rendering --> failed
  queued --> cancelled: 取消
  rendering --> cancelled: 取消

4.5 JSONB 结构约定(节选)

// node.data  type=shot
{
  "prompt": "Close-up of the bottle, water droplets, soft light",
  "negative": "",
  "duration_s": 5,
  "aspect_ratio": "9:16",
  "capability": "i2v",
  "model": {"mode":"auto","quality":"standard"},        // auto → 报价时由路由决定,批准后固定为报价里的版本
  "refs": [{"role":"first_frame","asset_id":"01J..."},{"role":"character","node_id":"01J..."}],
  "camera": {"move":"push_in","speed":0.4},
  "audio": {"mode":"native|tts|none","tts_text":"..."},
  "seed": 123456
}

// timeline.doc
{
  "tracks":[
    {"id":"v1","type":"video","clips":[
       {"id":"c1","asset_id":"01J..","in_ms":0,"out_ms":5000,"start_ms":0,"transform":{"scale":1,"x":0,"y":0},
        "transition_in":{"type":"fade","ms":300},"source_node":"01J.."}]},
    {"id":"a1","type":"audio","clips":[{"asset_id":"01J..","start_ms":0,"gain_db":-6}]},
    {"id":"s1","type":"subtitle","clips":[{"text":"...","start_ms":200,"end_ms":2400,"style_id":"sty_01"}]}
  ]
}

4.6 索引与分区策略

表分区保留说明
run_event按月热 3 月 → 归档 Parquet 至 COS 2 年SSE 续传只读热区
ledger_entry按月永久(法务 7 年)冷分区压缩
generation_task / ledger_entry 的用量聚合——不建 task_event / usage_record 表,聚合结果进 ClickHouse,GET /billing/usage 读聚合
asset_embedding不分区—HNSW,超 5000 万行迁向量库

最终设计 · 接口清单

重点本页重点
  • 55 条接口按 7 个领域分组,每组标出它读写的表;点组名可收起。
  • 变化集中在两处:报价与批准拆成两个接口;所有写操作带幂等键,重放返回原回执。

最终产物:55 条接口,按业务领域分成 7 组,和 ER 图里的表域一一对应(每组标出它主要读写的表)。原有 40,调整 6,新增 9;「状态」列只标明它相对原设计是否变化。

用户领域 11 项目与会话领域 7 Agent 交互领域 9 画布与时间轴领域 11 生成与报价批准领域 7 素材领域 6 开放平台与能力 4

用户领域 · 11 条 谁在用、怎么付费:登录与成员、API Key、支付、订阅、积分、用量

谁在用、怎么付费:登录与成员、API Key、支付、订阅、积分、用量。读写的表:org、app_user、session_token、plan、workspace、member、api_key、generation_task、ledger_account、ledger_entry、point_lot、hold、quota_window、approval_policy、subscription、purchase_order、payment_event

方法 路径说明鉴权 scope状态
身份与成员
POST /auth/login · /auth/refresh · /auth/logout登录、刷新、登出—原有
GET /me用户 + 当前工作区 + 权益user原有
GET/POST /workspaces/{id}/members成员管理admin原有
GET/POST/DELETE /api-keysAPI Key 管理admin原有
支付 · 订阅 · 积分
GET /billing/balance余额、冻结、即将过期billing:read原有
GET /workspaces/{id}/admission发送前准入:额度/积分是否够用、原因、下一步(购买/升级/打开额外用量)billing:read新增
GET /billing/ledger明细(可导出 CSV)billing:read原有
POST /billing/checkout创建支付会话(订阅/积分包)billing:write原有
POST /billing/portal订阅管理入口billing:write原有
POST /webhooks/stripe · /webhooks/wechatpay支付回调(验签)—原有
GET /billing/usage?group_by=model|member|project用量报表billing:read原有
项目与会话领域 · 7 条 项目是一份文档;会话挂在项目下;创作意图从不同入口汇入

项目是一份文档;会话挂在项目下;创作意图从不同入口汇入。读写的表:workspace、project、node、edge、intent、agent_session、agent_run、interaction、skill、skill_version

方法 路径说明鉴权 scope状态
项目
POST /projects新建项目(可带 kind、输入)project:write原有
POST /intents创建创作意图:adapter + source_ref(URL、商品、脚本、参考视频)project:write新增
GET /projects?cursor=列表project:read原有
GET /projects/{id}详情 + 画布快照(节点、边)project:read原有
PATCH /projects/{id}改标题/比例/语言project:write原有
会话
POST /projects/{id}/sessions创建会话(选 skill)agent:write原有
GET /projects/{id}/sessions会话列表(历史):按 last_message_at 倒序,q 搜索标题;每项带 active_run {id, status} 与 needs_action(等用户回答卡片或充值时为 true)agent:read新增
⭕ Agent 交互领域 · 9 条 发消息、事件流、停止、澄清卡片、技能

发消息、事件流、停止、澄清卡片、技能。读写的表:agent_session、agent_message、agent_run、run_event、interaction、run_action、skill、skill_version

方法 路径说明鉴权 scope状态
运行与交互
GET /sessions/{id}打开或刷新会话:返回会话、最近 50 条消息、进行中的 run(状态、last_seq、计划、未关闭的卡片);前端先按它渲染,再从 last_seq 接事件流agent:read新增
GET /sessions/{id}/messages向上翻历史:before=<message_id>&limit≤50,按时间倒序agent:read新增
POST /sessions/{id}/messages请求头 / body 带 client_action_id;返回 202 + run_id;同 ID 不同内容 409agent:write调整
GET /sessions/{id}/events (SSE)事件流,支持 Last-Event-IDagent:read原有
GET /runs/{id}run 状态agent:read原有
POST /runs/{id}:stop幂等停止;回执 status=terminal_observed | unknown;已输出的文字保存为一条 status=stopped 的助手消息;不承诺供应商侧已取消计费agent:write调整
POST /interactions/{id}:respond带 card_sha256;答案按卡片 schema 校验;已恢复过的卡再提交返回原回执agent:write调整
技能
GET /skills · GET /skills/{id}可用 Skillagent:read原有
POST /skills (发布,开发者)上传 Skill 包skill:publish原有
画布与时间轴领域 · 11 条 节点与边、批量变更、版本、时间线、导出

节点与边、批量变更、版本、时间线、导出。读写的表:project、node、node_version、edge、timeline、timeline_version、export_job、asset、hold

方法 路径说明鉴权 scope状态
节点、边与版本
POST /projects/{id}/nodes创建节点project:write原有
PATCH /nodes/{id}更新节点(If-Match)project:write原有
POST /projects/{id}/graph:patch批量变更节点/边(画布拖拽、Agent 写入)project:write原有
GET /nodes/{id}/versions版本历史project:read原有
POST /nodes/{id}/versions/{v}:restore回滚project:write原有
时间轴与导出
GET/PUT /projects/{id}/timeline读写(If-Match)project:write原有
GET /timelines/{id}/versions时间线版本历史(每次保存一条)project:read新增
POST /timelines/{id}/versions/{v}:restore恢复到某个版本:以旧内容新建一个版本,不覆盖历史project:write新增
POST /timelines/{id}/exports导出export:write原有
GET /exports/{id}导出状态export:read原有
POST /timelines/{id}/preview-proxy生成低码率预览代理export:write原有
生成与报价批准领域 · 7 条 先报价再批准、生成任务、模型目录与价格

先报价再批准、生成任务、模型目录与价格。读写的表:model_catalog、model_version、price_rule、generation_quote、generation_approval、generation_task、task_attempt、ledger_entry、point_lot、hold、quota_window、approval_policy

方法 路径说明鉴权 scope状态
POST /generations批准并创建任务:body 带 quote_id、client_approval_id、两个哈希;重放返回同一任务;报价过期返回 409 quote_expiredgenerate:write调整
POST /generations:quote报价,不扣费;返回 quote_id、request_sha256、quote_sha256、expires_atgenerate:read调整
GET /generations/{id}查询任务generate:read原有
POST /generations/{id}:cancel取消generate:write原有
GET /generations?project_id=&status=列表generate:read原有
PUT /workspaces/{id}/approval-policyAsk First / Always Allow(带单次与每日上限)generate:write新增
GET /models?capability=模型清单:版本、约束、价格表(来自 model_version)generate:read调整
素材领域 · 6 条 上传、检索、采集、角色

上传、检索、采集、角色。读写的表:project_asset_ref、asset、asset_embedding、retrieval_seen

方法 路径说明鉴权 scope状态
POST /assets:init-upload获取预签名直传 URLasset:write原有
POST /assets/{id}:complete确认上传完成,触发解析asset:write原有
GET /assets · /assets/{id}查询asset:read原有
POST /assets:search语义/混合检索(含 context_key 去重)asset:read原有
POST /collections:from-url链接采集(商品/视频)asset:write原有
POST /characters创建角色(形象 + 声音)asset:write原有
开放平台与能力 · 4 条 Webhook 注册、MCP、OpenAPI、能力注册表

Webhook 注册、MCP、OpenAPI、能力注册表。读写的表:api_key、skill、model_catalog、outbox、webhook_endpoint

方法 路径说明鉴权 scope状态
POST /webhook-endpoints注册回调admin原有
MCP: https://mcp.lumen.example/MCP Server(Streamable HTTP)OAuth2 / API Key原有
GET /openapi.jsonOpenAPI 3.1—原有
GET /capabilities能力注册表:每项能力状态(可用/灰度/未接入),界面只渲染可用项user新增
为什么好
  • 先报价后批准是两个接口:用户看到的价格就是被批准的价格。
  • 每个写动作都有幂等键:前端可以放心重试,后端不会产生第二次副作用。
  • 内外同一套工具面:界面能做的,外部 Agent 通过 MCP 也能做,鉴权、审批、计费完全一致。
  • 按领域分组:每组对应一组表和一支小团队,接口与表同进同出,新增能力时知道往哪里放。

本页涉及的决策

D2 先报价,再批准,批准带哈希 · D5 缺信息时用结构化澄清卡 · D8 事件流带游标,可停止、可恢复 · D13 修订可恢复 · D14 能力对内外一致:Agent、API、MCP 用同一套工具面 · D15 不展示做不到的功能 (借鉴来源与原因见“设计取舍与原因”)

通用约定、错误模型与核心接口详解(已按最终规则改写,与上表一致)

5.1 通用约定

项约定
Base URLhttps://api.lumen.example/v1(国内 api.cn.lumen.example)
鉴权Web:Authorization: Bearer <JWT>(15 分钟)+ Refresh Cookie;开放 API:Authorization: Bearer lm_sk_...(API Key,scope 限定)
幂等所有写动作都带客户端生成的幂等标识:发消息和 Run 动作用 client_action_id,批准用 client_approval_id,报价用 client_request_id;其余创建类 POST 用 Idempotency-Key(24h)。同标识同内容返回首次结果,同标识不同内容返回 409 idempotency_conflict
ID库里存 26 位 ULID,接口层加资源前缀:prj_ nod_ tsk_ run_ ast_ ord_ ses_ itx_ qte_ exp_ mdl_ req_
时间/金额RFC 3339 UTC;积分字段 *_centi 整数;API 同时给 points 展示值(字符串,2 位小数)
分页游标:?limit=50&cursor=... → {items, next_cursor}
并发控制单个资源的更新(PATCH /nodes、PUT /projects/{id}/timeline)用 If-Match: <version>,不匹配返回 412 version_mismatch;批量图变更用请求体里的 base_version(就是 project.doc_version)和每个节点的 if_ver,冲突返回 409 state_conflict 并列出冲突节点
限流响应头 X-RateLimit-Limit/Remaining/Reset;超限 429 + Retry-After
WebhookX-Lumen-Signature: t=...,v1=HMAC_SHA256(secret, t.body),失败指数退避 8 次(最长 24h)

错误模型

HTTP 4xx/5xx
{
  "error": {
    "code": "insufficient_points",       // 稳定的机器可读码,调用方只判断 code
    "message": "Insufficient points.",   // 人读,按 Accept-Language 本地化
    "details": {"required_centi": 12000, "available_centi": 3000},
    "request_id": "req_01J..."
  }
}
HTTPcode含义 / 客户端动作
400invalid_argument参数校验失败,details 含字段
401unauthenticated / token_expired刷新 token 或重新登录
402insufficient_points引导充值
403forbidden / content_rejected / region_blocked无权限 / 审核拒绝
404not_found—
409idempotency_conflict / run_busy / state_conflict / quote_expiredrun_busy:会话已有活跃 run;quote_expired:报价过期,不建任务、不扣费,重新报价;state_conflict:乐观锁冲突、卡片或报价与当前不一致
412version_mismatch重新拉取再合并
422unsupported_combination模型不支持该参数组合,details 给出可选项
429rate_limited / quota_exceeded退避重试
503provider_unavailable / overloaded可重试(幂等安全)

5.3 核心接口详解

5.3.1 POST /generations:quote 与 POST /generations — 先报价,再批准

POST /v1/generations:quote
{
  "client_request_id": "c-52ad",
  "capability": "i2v",
  "project_id": "prj_01J...",                       // 可选,挂到项目/节点
  "node_id": "nod_01J...",
  "input": { "prompt": "A bottle on a marble table, slow push-in", "duration_s": 5, "aspect_ratio": "9:16", "resolution": "1080p" },
  "model": {"mode": "auto", "quality": "standard"},    // 或 {"mode":"fixed","id":"mdl_..."}
  "source": {"run_id": "run_01J..."}                   // 可选:来自 Agent 回复时带上
}
200 { "quote_id": "qte_01J...", "request_sha256": "…", "quote_sha256": "…",
      "model": {"id": "mdl_01J...", "version": "v1"}, "quoted_centi": 15000, "points": "150.00",
      "expires_at": "2026-10-19T10:05:00Z" }          // 不扣费;输入审核和模型路由在这一步完成

POST /v1/generations
{ "quote_id": "qte_01J...", "client_approval_id": "c-9a10", "request_sha256": "…", "quote_sha256": "…" }
201 Created
{ "id": "tsk_01J...", "status": "queued", "quoted_centi": 15000, "links": {"self": "/v1/generations/tsk_01J...", "events": "/v1/generations/tsk_01J.../events"} }

同一个 (quote_id, client_approval_id) 重放 → 返回同一个任务,不会重复扣费
409 quote_expired        报价过期:不建任务、不扣费,重新报价
409 state_conflict       请求或价格与报价不一致(哈希不符)
402 insufficient_points  {"required_centi":15000,"available_centi":3000}
403 content_rejected     {"category":"violence","field":"input.prompt"}
422 unsupported_combination  {"field":"input.duration_s","allowed":[5,8,10]}

语义:批准时在一个本地事务内完成 ① 校验报价未过期、两个哈希一致 ② 冻结积分 ③ 写 generation_approval、generation_task 与 outbox ④ 通过 outbox 异步启动 Temporal 工作流(保证不丢)。用户看到的价格和提示词,就是被批准、被执行的那一份。

5.3.2 GET /generations/{id} — 查询

200
{
  "id":"tsk_01J...", "status":"succeeded",
  "outputs":[{"asset_id":"ast_01J...","type":"video","url":"https://cdn.../a.mp4?sig=..","duration_s":5,"width":1080,"height":1920}],
  "charged_centi":15000,
  "created_at":"2026-10-11T08:00:00Z","finished_at":"2026-10-11T08:01:34Z",
  "error":null,
  "attempts":[{"seq":1,"model":"Seedance 2.5","status":"succeeded"}],
  "quote_id":"qte_01J..."          // 这个任务是由哪份报价批准出来的
}

5.3.3 POST /sessions/{id}/messages — 向 Agent 发消息

POST /v1/sessions/ses_01J.../messages
{
  "client_action_id": "c-31ab",
  "content": [
    {"type":"text","text":"用这个商品做 3 条 15 秒的 TikTok 广告,风格偏 ASMR"},
    {"type":"url","url":"https://shop.example.com/p/123"},
    {"type":"asset","asset_id":"ast_01J..."}
  ]
}

202 Accepted
{"run_id":"run_01J...","mode":"new_run"}   // new_run | guidance;消息永远不会恢复一个等待中的 run

状态规则(沿用旧网关的规则;本方案没有重新验证):
- 会话空闲 → 新建 run
- run 运行中 → 作为 guidance 追加给当前 run(mode=guidance)
- run 等待交互且收到普通消息 → 取消旧 run,新建 run
- 只有 interactions/{id}:respond 才恢复同一个 run
409 run_busy:guidance 通道被关闭(run 正在收尾)时返回,客户端稍后重试

同一个 client_action_id 内容不同 → 409。消息不能附带“自动批准”:花积分只能走“报价 → 批准”。要免确认,只能由用户在工作区里开启审批策略(approval_policy,Always Allow,限额度内,仍写账本)。

5.3.4 GET /sessions/{id}/events — SSE

GET /v1/sessions/ses_01J.../events?from_seq=120
Accept: text/event-stream
Last-Event-ID: run_01J...:120

id: run_01J...:121
event: assistant.delta
data: {"run_id":"run_01J...","seq":121,"text":"我先分析商品页面…"}

id: run_01J...:130
event: node.created
data: {"seq":130,"node":{"id":"nod_01J...","type":"script","data":{...}},"graph_version":42}

id: run_01J...:135
event: interaction.requested
data: {"seq":135,"interaction":{"id":"itx_01J...","kind":"collect_info","card_sha256":"…",
        "title":"需要补充商品信息","schema":{"fields":[{"key":"product_name","kind":"input","label":"商品名称","required":true}]},
        "expires_at":"2026-10-11T09:00:00Z"}}      // 答案用 :respond 提交,必须带这个 card_sha256

: heartbeat   (每 15s)

断线续传:服务端按 (run_id, seq) 从 PG 热分区补读;Redis Stream 仅作低延迟提示。事件顺序以 seq 为准,客户端去重。

5.3.5 POST /interactions/{id}:respond

POST /v1/interactions/itx_01J...:respond
{ "client_action_id": "c-9f3e", "card_sha256": "…", "action": "submit", "values": {"product_name": "不锈钢保温杯"} }
200 {"status":"resolved","run_id":"run_01J..."}
同 client_action_id 重放 → 200 返回同样结果;同 id 不同内容 → 409 state_conflict
卡片哈希与当前卡不一致(卡已更新)→ 409 state_conflict,不提交旧答案
action="cancel" → 取消这一轮

答案按卡片 schema 校验(必填、选项取值);已经恢复过的卡再提交,返回原回执,不会恢复两次。只有这个接口能恢复同一个 run。

5.3.6 POST /projects/{id}/graph:patch — 批量图变更

{
  "base_version": 41,
  "ops": [
    {"op":"add_node","temp_id":"t1","type":"shot","parent_id":"nod_scene1","data":{"prompt":"..."},"pos":{"x":120,"y":80}},
    {"op":"update_node","id":"nod_02","if_ver":3,"data":{"prompt":"new"}},
    {"op":"add_edge","src":"nod_01","dst":"t1","kind":"data"},
    {"op":"delete_node","id":"nod_09"}
  ]
}
200 {"graph_version":42,"id_map":{"t1":"nod_01J..."},"stale_nodes":["nod_05"]}
409 state_conflict {"conflicts":[{"id":"nod_02","server_ver":4}]}   // 乐观锁失败,客户端 rebase

base_version 与响应里的 graph_version 都是 project.doc_version;if_ver 对应 node.current_ver。

5.3.7 POST /assets:search

{
  "query": "close-up of hands applying serum, soft light",
  "kinds": ["video"],
  "filters": {"duration_s": {"lte": 8}, "tags_any": ["beauty"]},
  "scope": ["workspace","library"],
  "context_key": "prj_01J...:scene3",     // 同 key 下已返回过的素材不再返回
  "top_k": 8
}
200 {"items":[{"asset_id":"ast_..","score":0.82,"segment":{"start_ms":1200,"end_ms":4200},"url":"..."}]}

5.4 内部接口(core ↔ model-gateway,gRPC)

service ModelGateway {
  rpc Route(RouteRequest) returns (RouteDecision);                 // 能力 → 模型 + 版本 + 价格(报价写入 generation_quote)
  rpc Submit(SubmitRequest) returns (SubmitResponse);               // 幂等键 = task_id + attempt_seq
  rpc Query(QueryRequest) returns (QueryResponse);                  // 按 provider_task_id 或 idempotency
  rpc Cancel(CancelRequest) returns (CancelResponse);
  rpc ListModels(ListModelsRequest) returns (ListModelsResponse);
}
message SubmitRequest {
  string task_id = 1; int32 attempt_seq = 2; string model_id = 3; string model_version_id = 8;   // 必须是报价时的版本
  google.protobuf.Struct input = 4; repeated AssetRef refs = 5;     // 网关负责把 asset 转成供应商可访问 URL
  string callback_url = 6; int64 deadline_unix = 7;
}
enum SubmitOutcome { ACCEPTED = 0; REJECTED = 1; UNCERTAIN = 2; }   // UNCERTAIN: 超时/断连,无法确认供应商是否受理

5.5 Webhook 事件

type触发data 关键字段
generation.succeeded / failed / cancelled任务终态task、outputs、charged_centi、error
export.succeeded / failed / cancelled导出终态export、asset
run.waiting_userAgent 需要用户interaction
billing.low_balance余额低于阈值available_centi、threshold
subscription.renewed / past_due订阅事件subscription

5.6 Agent 事件类型

type说明持久化
run.started / run.succeeded / run.failed / run.cancelled生命周期,状态变更与事件同事务是
assistant.delta / assistant.message对话流式文本delta 否 / message 是
plan.updatedTodo/计划变化是
tool.started / tool.completed / tool.failed工具调用生命周期是
node.created / node.updated / node.deleted画布变更(带 graph_version)是
task.progress / task.completed / task.failed生成任务进度与终态(来自 generation 事件)是
interaction.requested / interaction.resolved / interaction.expiredHITL是
budget.warning预算接近上限是

5.7 MCP 工具面(对内外一致)

工具入参要点说明
lumen_create_projectkind, title, inputs—
lumen_generate_videoprompt, refs, duration, model默认只报价(返回 quote_id 与两个哈希);带 quote_id 与 client_approval_id 才创建任务
lumen_get_tasktask_id—
lumen_search_assetsquery, context_key—
lumen_collect_urlurl采集商品/视频
lumen_edit_timelineops[]结构化修改时间线
lumen_exporttimeline_id, preset—
lumen_get_balance——
lumen_get_admissionworkspace_id发送前准入:额度、积分是否够用
lumen_send_messagesession_id, content, client_action_id发消息,返回 run_id
lumen_stop_runrun_id, client_action_id幂等停止
lumen_respond_interactioninteraction_id, card_sha256, values回答澄清卡,恢复同一个 run
所有写工具都带幂等标识(client_*_id 或 idempotency_key);成本类工具遵循"先报价后批准",批准要带 quote_id,防止外部 Agent 无意识烧积分。

最终设计 · 设计取舍与原因

重点本页重点
  • 17 条决策里标 核心 的 4 条是地基,落地时不能妥协:D1 共用文档、D2 报价批准、D3 复式账本、D17 共享内核。
  • 其余决策可以按“落地指南”的阶段逐步做。

每一条都回答三个问题:借鉴了谁、我们怎么做、为什么这样更好。借鉴来源来自我们对七个竞品的调研和复刻时亲手跑过的结果,不是想象。

#决策为什么好(一句话)
D1一个项目、三种视图共用一份文档 核心竞品里「对话产物」和「可编辑对象」常常是两套数据,用户改了画布,Agent 不知道;改了对话,画布不变。
D2先报价,再批准,批准带哈希 核心LibTV 的预算只在达到阈值时提醒,不阻止下一笔扣费,用户仍可能超支;我们把批准做成硬门槛。
D3积分用复式账本,先冻结再结算 核心竞品只暴露余额数字,对账和退款难以解释。
D4文字推理成本和媒体成本分开显示用户最怕的是不知道哪一步在花钱。
D5缺信息时用结构化澄清卡把一串问题写进聊天段落,无法做必填校验,也没法在断线后恢复。
D6方案是结构化实体,不是一大段文字整段文字改一处就要整体重写,容易误伤已满意的镜头。
D7发送前就说明准入条件失败在发送之后才出现,用户会反复重试。
D8事件流带游标,可停止、可恢复长回复和长任务一定会遇到断线、刷新、重复点击。
D9Skill 与模板有版本,会话固定绑定版本没有版本,同一个 Skill 今天和明天表现不同,问题无法复现;固定版本后可复现、可回滚,社区发布也有了安全边界。
D10模型靠注册表和适配器接入新增一个模型要改核心代码、重新编译、重启服务,是复刻里最大的扩展障碍。
D11不同入口汇成同一个「创作意图」入口多是竞品的增长手段,但内核不应该重复实现。
D12素材归用户,项目只引用素材被项目复制会产生所有权和删除纠纷;只引用则复用方便、权限清楚,删除素材前可以查到还有哪些项目在用。
D13修订可恢复创作工具里最痛的是改坏了回不去。
D14能力对内外一致:Agent、API、MCP 用同一套工具面多套接口会出现「界面能做、API 不能做」或相反,还要分别做计费和审计。
D15不展示做不到的功能假按钮会消耗用户信任,也会把「没做」误当成「做了但坏了」。
D16登录与会话状态要有可见入口用户遇到的第一个问题往往是「怎么登录」和「为什么突然不能用了」。
D17一个共享内核加品牌配置,而不是七套代码 核心我们的实测是:七个产品的后端结构几乎一致,区别在入口、模板和视觉。
展开完整版:借鉴来源、我们的做法、完整原因
#决策借鉴来源我们的做法为什么好对应章节
D1一个项目、三种视图共用一份文档TopView 同一项目双编辑模式;LibTV 画布+故事板+Agent;Flova 画布/分镜/时间轴;invideo Agent 到编辑器对话、画布、时间线都读写同一份项目文档(project.doc_version 乐观锁,node/edge/timeline 为其结构化投影)。Agent 的产出落成节点,而不是留在聊天记录里。竞品里「对话产物」和「可编辑对象」常常是两套数据,用户改了画布,Agent 不知道;改了对话,画布不变。共用一份文档后,Agent 可以只改第 2 个镜头,撤销、版本、协作都只实现一次。总览与架构 · ER 图
D2先报价,再批准,批准带哈希我们复刻中跑通的流程;TopView 的 Ask First / Always Allow;LibTV 的预算阈值只提醒生成前先给报价(含有效期),批准时必须带请求哈希和报价哈希;服务端重放同一个批准只返回同一个任务。「始终允许」只能在额度内由用户明确打开,并且仍写账本。LibTV 的预算只在达到阈值时提醒,不阻止下一笔扣费,用户仍可能超支;我们把批准做成硬门槛。哈希绑定让「用户看到的价格和提示词」与「实际执行的」不可能不一致,也防重复点击重复扣费。真实测试里报价过期后批准被拒,没有建任务也没有扣费。Agent 交互 · 接口清单
D3积分用复式账本,先冻结再结算各家都有积分/额度;CreaTok 有每日额度和额外用量;Pollo 显示余额充值、赠送、冻结、结算、释放都是账本分录,只追加,用触发器禁止修改;任务明确失败时释放冻结,状态无法确认时保持冻结并告警(原设计的 GN-05)。额度(日额度、套餐额度)与积分分开建模。竞品只暴露余额数字,对账和退款难以解释。复式账本让每一分都有来源和去向,失败任务不会吞积分;额度与积分分开,才能表达「额度用完后按用量扣积分」。表结构 · ER 图
D4文字推理成本和媒体成本分开显示LibTV TV Director:推理实时耗积分,素材生成另计界面和账本都区分「对话/推理用量」与「媒体生成」(账本分录 ref_type=run 记推理,ref_type=task 记生成),对话前显示当前模型倍率,不把文字对话标成免费。用户最怕的是不知道哪一步在花钱。分开显示让「聊一聊」和「真的生成」边界清楚,也方便给文字对话单独设上限。Agent 交互
D5缺信息时用结构化澄清卡CreaTok:4 题表单、一题一页、可回退,回答后继续原任务(已真实跑通两轮)运行进入 waiting_user(Agent Run 的等待状态),下发带 ID 的卡片(必填/可选、单选/文本);用户提交时带卡片哈希;服务端验证状态后恢复同一个 Run;重复提交不会恢复两次。把一串问题写进聊天段落,无法做必填校验,也没法在断线后恢复。卡片是一个有状态的对象:刷新后仍在、过期有明确状态、答案可校验。Agent 交互 · ER 图
D6方案是结构化实体,不是一大段文字CreaTok:3 镜头表格,自然语言只改第 2 镜头,其他镜头保持方案落成节点:storyboard → scene → shot(node 表,type=shot),每个镜头有稳定的 node id、时长、画面、对白、字幕、转场;修改只产生新的 node_version,旧版本保留可对比。整段文字改一处就要整体重写,容易误伤已满意的镜头。有稳定键才能做局部重生成、对比和只对改动的镜头重新计费。Agent 交互 · 表结构
D7发送前就说明准入条件Flova:积分不足时发送前就提示并给购买入口输入框旁实时显示额度/积分是否够用、原因和下一步;不够用时不让用户白白等待一轮再失败。失败在发送之后才出现,用户会反复重试。提前说明一次,既少一次无效请求,也少一次无效体验。Agent 交互
D8事件流带游标,可停止、可恢复Pollo 的流式事件;我们复刻里的游标轮询、停止回执、刷新后恢复每个 Run 的事件有递增序号;客户端断线后用序号续读;停止是幂等动作,回执只声明「已观测到终态」,不承诺供应商已取消计费;停止后对话里保留「已停止」标记。长回复和长任务一定会遇到断线、刷新、重复点击。序号让续读不丢不重;幂等停止让重复点击安全;诚实的回执避免把「已停止」说成「不收费」。我们在复刻里还修了一个停止按钮从不发请求的缺陷,说明这条路径必须有端到端测试。Agent 交互 · 接口清单
D9Skill 与模板有版本,会话固定绑定版本LibTV 的 Skill 社区与固定版本绑定会话;Pollo 的 63 个技能和模板库skill 与 skill_version 分开;会话创建时记录所用版本;发布新版本不影响进行中的会话。没有版本,同一个 Skill 今天和明天表现不同,问题无法复现;固定版本后可复现、可回滚,社区发布也有了安全边界。总览与架构 · 表结构
D10模型靠注册表和适配器接入Pollo/LibTV/TopView 的多模型矩阵;我们在复刻里发现后端把单个模型写死在代码和 worker 配置里,并已按这个方向改成:模型登记为数据、白名单由环境变量扩展、每个模型一个独立 worker;四个模型真实出片model_catalog + model_version(含价格表、时长/画幅/分辨率约束、adapter_key)放在数据里;适配器按 adapter_key 注册;每个模型有自己的路由与限流。新增一个模型要改核心代码、重新编译、重启服务,是复刻里最大的扩展障碍。把模型变成数据加适配器,新增模型只是加一行配置加一个适配器,核心不动,也能按模型灰度和下线。总览与架构 · 表结构
D11不同入口汇成同一个「创作意图」Creatify 的 URL→视频;Pollo 的营销批量;TopView 的商品链接广告;爆款复刻入口适配器(URL、商品、脚本、参考视频)把输入统一成 intent,后面的计划、报价、生成、导出走同一条链。入口多是竞品的增长手段,但内核不应该重复实现。统一成 intent 后,新增一个入口只写适配器,计费、审批、恢复全部复用。总览与架构 · Agent 交互
D12素材归用户,项目只引用Pollo / LibTV 的个人素材库、团队复制asset 属于工作区(个人空间也是一个工作区);项目通过 project_asset_ref 只记引用和用途,所以项目与素材是多对多;不复制素材。素材被项目复制会产生所有权和删除纠纷;只引用则复用方便、权限清楚,删除素材前可以查到还有哪些项目在用。表结构 · ER 图
D13修订可恢复Flova 文档版本;invideo 历史审阅节点每次修改写一条 node_version,时间线每次保存写一条 timeline_version;恢复是以旧内容新建一个版本,不覆盖历史;导出记录所用的时间线版本(export_job.timeline_ver)。创作工具里最痛的是改坏了回不去。恢复成新修订让历史不被改写,导出绑定修订则保证「导出的就是看到的」。表结构 · 接口清单
D14能力对内外一致:Agent、API、MCP 用同一套工具面TopView / Pollo 的 MCP 与 CLI;Flova、CreaTok 的 CLI平台内部 Agent 调用的工具,就是对外 MCP/API 暴露的工具;同样的鉴权、同样的审批和计费。多套接口会出现「界面能做、API 不能做」或相反,还要分别做计费和审计。一套工具面让外部 Agent 接入几乎零成本,也只需测试一次。接口清单
D15不展示做不到的功能我们复刻中的做法:Tools 页没有后端的工具标注「未接入」,不提供可点击的创建能力注册表(GET /capabilities,由 model_catalog.status、skill.status 和配置里登记的“未接入”能力合成)里每项能力有状态(可用/灰度/未接入);界面只渲染可用能力,其余明确标注。假按钮会消耗用户信任,也会把「没做」误当成「做了但坏了」。诚实标注让缺口可见,排期也有依据。总览与架构
D16登录与会话状态要有可见入口我们在复刻里发现:未登录页面没有登录入口,会话过期也无提示页面始终有登录/账号入口;会话过期时顶部提示并保留当前上下文,重新登录后恢复。用户遇到的第一个问题往往是「怎么登录」和「为什么突然不能用了」。把状态说出来,比让用户从失败的发送里猜要好得多。Agent 交互
D17一个共享内核加品牌配置,而不是七套代码七个复刻共用同一套 24 张表、55 个接口,只在前端令牌和少量入口上不同数据库按 workspace 隔离;品牌差异放在配置与前端令牌里(颜色、入口、模板、模型清单)。我们的实测是:七个产品的后端结构几乎一致,区别在入口、模板和视觉。把差异收敛到配置,维护成本是一份,而不是七份。总览与架构 · 表结构

我们明确没有照搬的

最终设计的边界

最终设计 · 落地指南

重点开工前只需记住
  • 第一版只做一条主线:一句话需求 → Agent 出镜头方案 → 报价 → 批准 → 生成 → 交付;每一步能停止、能恢复、不重复扣费。
  • 产品先拍板 6 件事(定价与免费额度等),上线后再改代价很大,见下表。
  • 技术按 P0 地基开始:身份、项目文档、积分账本、素材归属;每个阶段都有可写成测试的验收标准。

给马上要开发的同事:产品看“做什么、先拍板什么、怎么验收”,技术看“按什么顺序做、复用什么、哪里踩过坑”。结论来自七个竞品的复刻和实测;没有实测过的地方,在验收表和最后一节单独标出。

产品:第一版只做这条主线

一句话需求 → Agent 出镜头方案 → 报价 → 用户批准 → 生成 → 交付与下载。这条线上每一步都要能停止、能恢复、不重复扣费。其余先不做:

第一版做第二版再做明确不做
一个入口(Agent 对话);一个官方技能并固定版本;镜头表可局部修改;先报价再批准;积分账本;3 个模型;停止与刷新恢复多入口(URL / 商品链接 / 参考视频);澄清卡;开发者发布技能与模板;图片附件;团队与素材库把文字对话标成免费;只提醒不拦截的预算;每个品牌一套代码;没有后端的工具放可点按钮

产品要先拍板的 6 件事

决策为什么要早定我们的建议
积分定价与免费额度决定账本里“额度”和“积分”怎么分;上线后改很贵日额度和积分分开;首版只给新用户一次性赠送
是否提供“始终允许”影响批准流程与风控首版只开放“每次先问”;approval_policy 表和接口保留,以后开“始终允许”时只能在额度内、用户显式打开、仍写账本
首发模型清单与价格每个模型要有独立价格表、时长/画幅限制和灰度开关3 个:快/标准/高质;价格放数据,不写代码
内容审核与版权口径生成前后都要有拦截点;影响提示词与产出的存储提交前过审核;产出保留审核结果;明确素材归属
入口优先级每个入口只是一个适配器,但要排期先 URL→视频(最短路径),再商品、参考视频
失败后积分策略供应商失败后是否自动退还,要产品给口径先冻结后结算;失败默认释放;供应商状态不明时标“待核对”,人工处理

验收标准(每条都能写成测试)

场景期望结果在复刻里验证过吗
同一个批准点两次只创建一个任务,只扣一次已验证(批准带请求哈希与报价哈希)
报价过期后批准被拒绝;不建任务,不扣费;界面说明“报价已过期”已验证
生成失败设计目标:供应商明确返回失败时释放冻结;状态无法确认(提交结果未知,或失败但没有原因)时保持冻结,显示“费用待核对”,人工处理“待核对”分支已验证(Seedance 1.5 Pro 两次失败且无原因,冻结保留);自动释放分支在复刻里没出现过,未验证
回复中点停止回复停止,对话里留“已停止”;重复点击安全已验证;修过“停止按钮从不发请求”的缺陷
刷新或断线对话与任务状态恢复;事件按序号续读已验证
新增一个模型只加数据与一个 worker,不改核心代码、不重启主服务已验证(Seedance 2.0 真实出片)
澄清卡缺商品信息时发卡;用户回答后同一个 Run 恢复;只发商品名称这一种卡已在 Pollo 测试部署上验证
图片附件发送图片后助手能描述它;超限或类型不对被拒已在 Pollo 测试部署上验证
生成图片图片模型与视频走同一套报价→批准→结算;图片只能是图片任务的产出已验证(5 个测试站,Qwen-Image 2.1)

技术:按这个顺序做

阶段交付依赖怎么验证
P0 地基身份、项目文档(版本号+乐观锁)、积分账本(只追加+触发器)、素材归属无账本分录对账;并发改同一文档只有一个成功
P1 Agent 运行对话 Run(会话固定一个官方技能版本)、事件流(递增序号)、停止、恢复P0断线续读不丢不重;重复停止幂等
P2 报价到生成报价、批准(双哈希)、冻结→提交→结算/释放、模型适配器与独立 workerP0、P1重复批准、过期、失败三类用例;加模型不改核心
P3 扩展澄清卡、开发者发布技能、入口适配器、图片附件、团队与素材库P1、P2每项各自的验收表;附件只在单独实例开

可以直接复用的:本站每个竞品页下载包里的前端、Go 后端(报价/批准/账本/模型适配器)、七个品牌库和身份库的 DDL、接口目录。Lumen 的最终 DDL(全部表)已在 PostgreSQL 16 上整体执行通过。

重点交给编码 Agent 开发
  • 下载 lumen-handoff.zip,交给 Codex 等编码 Agent,告诉它“按 AGENTS.md 实现 Lumen”。
  • 包里有:完整接口规格(OpenAPI 3.1,61 个操作)、可执行的建表脚本、哈希与事件等契约、状态机、可写成测试的验收用例、待拍板事项与默认值、未覆盖范围。

我们踩过的坑(别再踩)

坑后果做法
模型写死在代码和 worker 配置里加一个模型要改代码、重编译、重启模型与版本放数据;适配器按 key 注册;白名单可由环境变量扩展
共享的 Agent 会话状态被“隔离环境”改动克隆库里改了模型,生产会话读绑定返回 503隔离测试必须连 Agent 一起隔离;绑定与会话模型版本要有一致性校验
附件与统一品牌实例互斥开附件会让七品牌实例启动失败附件放单独实例;或先解开这条互斥
启动器只认固定库名与固定地址拷贝库、新端口被安全检查拒绝起独立实例时同时准备同名库的拷贝,并显式放行新地址
页面自带点击守卫吞掉不认识的按钮新加的按钮点了没反应新功能在更早的捕获阶段处理,并加端到端点击测试
前端模型切换层在澄清卡打开时拒绝一切操作用户永远答不了卡(卡片路径从没跑过,所以没发现)回答卡片要放行;任何“冻结的功能”都要有端到端用例
业务后端只认单字段商品名称卡,且要求卡的指纹是哈希Agent 发出别的卡会被静默丢弃,页面停在“待核对”后端与 Agent 对卡的格式要共用一份契约,并在 Agent 侧限制可发的卡
批准后供应商立刻失败且无原因积分冻结后需要人工判断账本保留冻结;界面写“费用待核对”;提供人工释放入口

还没验证的(立项前要知道)