Files
Genarrative/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md
suzmii 736a1b6ac6
Project CI / Repository checks (pull_request) Successful in 2m36s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Backend tests (pull_request) Successful in 7m35s
Project CI / Native shell tests (pull_request) Failing after 7m43s
接入 LLM Router 累计额度结算
按 Router used_quota 累计值与首次基线结算泥点
新增原子 checkpoint 事务及 llm_router_consume 钱包流水
同步额度查询校验、前端展示、生成绑定和技术文档
2026-09-06 00:36:31 +08:00

256 KiB
Raw Permalink Blame History

server-rs 与 SpacetimeDB 数据契约

2026-08-25 状态更新:旧创作入口、模板业务 API、worker 和运行态已退役。本文逐玩法路由、流程和 DTO 章节仅作为历史设计记录;相关持久化表仍按原结构作为最小 schema 数据壳编译。当前编译与运行边界以 server-rs/Cargo.tomlserver-rs/crates/api-server/src/app.rs当前产品与工程约束平台入口与玩法链路为准。

更新时间:2026-08-25

后端主线

当前后端固定为:

server-rs + Axum + SpacetimeDB

server-node、Express、PostgreSQL、Go 服务端和 maincloud 口径全部为历史残留。新功能不得回到旧接口、旧兼容层或前端临时业务真相。

Rust workspace

server-rs/Cargo.toml 是 workspace 事实源。默认构建成员为 crates/api-server;第三方依赖版本和 workspace 内 crate path 统一放在 [workspace.dependencies]

SpacetimeDB 版本口径:当前 Rust crate spacetimedbspacetimedb-sdkspacetimedb-lib 统一锁定 2.8.3;本地 spacetime CLI / standalone、生成的 spacetime-client bindings、容器压测镜像和生产 provision 也必须与 server-rs/Cargo.toml 锁定版本对齐,避免 BSATN / procedure result 反序列化错配。2.8.3 官方 CLI / standalone 发行包、Rust crates 与容器镜像使用同版本号;CLI / standalone 还必须核对 commit 8e410d2842147bd8e5a32a9589cc00c19f7478e2。遇到版本不匹配时,不继续沿着业务超时排查,先把 CLI / standalone 直接升级到锁定版本并重启后再重试。2.6.1 修复了 procedure context 中调用者 Identity / ConnectionId 丢失问题;2.8.3 修复 scheduled function 从实际执行时间重排导致的长期漂移。

当前主要 crate

  • HTTP 与运维入口:api-serverpingora-gatewayserver-manager-panel
  • 现役领域模块:module-aimodule-assetsmodule-authmodule-editor-agentmodule-runtimemodule-runtime 继续承载账号、钱包、公共设置、追踪、功能门禁等现役平台领域能力;该 crate 名称不代表旧玩法 runtime 路由仍在运行。
  • 平台副作用:platform-agent-harnessplatform-editor-agentplatform-authplatform-audioplatform-hyper3dplatform-imageplatform-llmplatform-mattingplatform-ossplatform-speechplatform-wechat。已退役 Creative Agent 的旧 platform-agent 不属于现役 workspace 或依赖图,旧实现已删除。
  • 共享层:shared-contractsshared-kernelshared-logging
  • SpacetimeDBspacetime-clientspacetime-module
  • 测试支撑:tests-support

旧玩法纯业务 crate 已从 server-rs workspace 配置和仓库删除,不属于 workspace member、default member 或现役依赖。

DDD 边界

  1. module-* 不直接依赖 Axum、SpacetimeDB table/reducer/procedure、reqwest、OSS、LLM、spacetime-clienttokio 或文件系统。
  2. 业务规则、命令校验、领域错误和可纯测逻辑优先沉到 module-*
  3. SpacetimeDB 表结构、reducer、procedure 和持久化 row shape 留在 spacetime-module
  4. 后端访问 SpacetimeDB 必须经 spacetime-client facade。
  5. HTTP 鉴权、BFF 聚合、SSE、外部模型编排、OSS 上传和第三方回调在 api-server
  6. 前端共享 DTO 通过 shared-contractspackages/shared 对齐,不在页面内重新发明旧接口;packages/shared 还可复用无业务真相的 UI 组件和纯工具,领域规则、后端副作用与正式状态仍留在各自分层。
  7. 微信能力按两层收口:server-rs/crates/platform-wechat 承载现役微信支付 V3 / 虚拟支付查单和发货确认的 HTTP header、签名、验签、解密、mock 响应和协议 payload 解析;server-rs/crates/api-server/src/wechat.rswechat/* 承载 Axum handler、AppConfig 到平台配置的映射、Genarrative 用户 / 订单 / 钱包 / SSE / 错误 envelope 编排。platform-auth 当前仍承载微信 OAuth / 小程序登录 provider 协议,api-server::wechat::provider 只作为组合根 adapter,不在业务 handler 内散落 provider 构造。旧玩法生成结果订阅消息服务不再参与编译。

验证:

npm run check:server-rs-ddd

spacetime-client mapper 组织

spacetime-client 的 Cargo lib.path 指向 src/active.rs,现役 mapper 聚合入口是 src/active/mapper.rs;原旧 facade 和 mapper 已删除。

当前 mapper 按现役调用领域拆分为 admin_account.rsadmin_dashboard.rsai.rsassets.rsauth.rseditor_agent.rseditor_project.rsexternal_api_key.rsexternal_generation.rsruntime.rsruntime_profile.rsstory.rs 只承接必要的历史资产记录兼容映射,不恢复旧故事业务 facade。跨领域轻量 helper 和共享 record 放在 common.rs;不得重新引入旧玩法 mutation 或跨层 JSON 兼容结构。

API 路由分组

路由树由 server-rs/crates/api-server/src/app.rs 统一构造。当前主要分组:

  • 健康检查:GET /healthzGET /readyz
  • 后台管理:/admin/api/*,现役路由包括登录与账号管理、Dashboard / 概览、HTTP debug、埋点、表查询、通用 feature gate、编辑器定价与素材 / 精选管理,以及账号侧兑换码、邀请码、任务、钱包、充值与退款管理;不再挂载旧创作入口配置、旧作品互动或旧玩法运营路由。环境变量管理员固定作为 owner,持久化 member 每次请求按当前 enabledtoken_version 和一级 Tab 权限实时校验;账号管理仅 owner 可访问,未登记权限映射的新后台路由对 member 默认拒绝。完整权限矩阵见 docs/technical/【后台管理】多账号与Tab访问权限方案-2026-07-14.mdDashboard 指标口径见 docs/technical/【后台管理】Dashboard运营看板方案-2026-06-23.md
  • 通用灰度控制面固定为后台 #gray-releaseGET/PUT /admin/api/feature-gates;页面固定目标只登记现役功能,不读取旧 /admin/api/creation-entry/config,也不恢复 creation-entry:* 动态目标。
  • 认证与账号:/api/auth/*/api/profile/me,包括短信、密码、微信、refresh session、多端会话和登出。
  • 个人中心:/api/profile/*,包括钱包流水、任务、领奖、充值、反馈、邀请和兑换等账号侧能力。
  • 平台基础能力:/api/llm/*/api/speech/volcengine/*,只保留通用 LLM 和语音代理。
  • 资产基础能力:/api/assets/direct-upload-tickets/api/assets/sts-upload-credentials/api/assets/objects/*/api/assets/read-url/api/assets/read-bytes,负责直传、确认、绑定和读取。两个读取入口共用同一授权函数,并通过受 runtime service identity 限制的 procedure 在同一事务快照内按配置 bucket 与精确 key 权威查询 asset_object、计算现役编辑器精选素材派生授权;不得把任意连接的订阅 cache miss 或命中解释为当前授权真相。一旦存在 metadata,即使 key 命中 legacy 前缀,也必须按 PublicRead、当前登录 owner,或同 owner 且已通过、已展示、返还完成的 editor_showcase_asset 顶层媒体 / 冻结角色动作帧精确授权读取;旧创作模板作品不再形成资产读取授权。动作帧只按快照中的 assetObjectId / objectKey 逐对象授权,不从 imageSrcgenerated-* 前缀推导宽泛权限;动作快照损坏、隐藏、拒绝或不再满足返还条件时不形成帧授权。只有同 bucket / key 的权威查询确认未登记时,才允许显式 legacyPublicPath 命中 platform_oss::LEGACY_PUBLIC_PREFIXES curated 白名单后匿名兼容。已登记资产继续保持 private,公开精选只获得与正式展示快照生命周期一致的精确派生读授权,不得批量改为 PublicRead 或放开 generated-* 前缀。任意未登记 objectKey、跨 owner 和未获授权的匿名私有读取统一返回不存在,read-bytes 不得成为绕过 read-url 授权的同源代理;精选派生授权、PublicRead 和 legacy 兼容读取签发的 URL 统一限制为最长 600 秒,owner / admin 读取保持原有有效期口径。
  • 外部 OpenAPI/api/external/v1/openapi.json/api/external/v1/assets/direct-upload-tickets/api/external/v1/assets/objects/confirm/api/external/v1/assets/read-url/api/external/v1/editor/*,使用 Bearer API Key 鉴权;API Key 管理仍在登录态 /api/profile/api-keys,不进入外部 OpenAPI JSON。主站和 External 的 asset object confirm 都必须从已认证主体派生 owner,不能信任请求体 owner;同 bucket / key 已登记后不得改变 owner。
  • 编辑器与素材生成:/api/editor/projects*/api/editor/assets*/api/editor/showcase/*/api/editor/*/generations/api/editor/images/*/api/editor/icon-spritesheets/*/api/editor/ui-designs/*,以及编辑器 Agent 会话路由。通用任务与素材支撑另保留 /api/ai/tasks*/api/assets/history/api/assets/character-visual/*/api/assets/character-animation/*/api/assets/character-workflow-cache*/api/assets/hyper3d/*
  • 现役 runtime 前缀公共能力:GET /api/runtime/frontend-config、鉴权的 GET/PUT /api/runtime/settings 以及鉴权的 /api/runtime/external-generation/*。这些分别承载非敏感前端开关、账号级公共设置和外部生成队列观测 / 确认,路径保留 runtime 前缀不代表旧玩法运行态恢复。/api/runtime/frontend-configapi-server 从运行时环境变量下发非敏感 UI 开关;画板右侧 Agent 入口由 GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR 控制,默认关闭。
  • 公共设置数据链:GET/PUT /api/runtime/settings 从 access token 取得 user_id,经 spacetime-client facade 调用 get_runtime_setting_or_default / upsert_runtime_setting_and_return,读写现役 runtime_setting 表的 music_volumeplatform_theme。该表不是历史数据壳,也不得为保留它而恢复任何旧存档、游玩历史或玩法 settings 路由。
  • 后台素材查询:GET /admin/api/editor-assets 通过 admin_list_editor_assets_and_return 后台只读 procedure 读取私有账号级 editor_assetsource_type = 'generated' 的素材,支持 ownerUserIdkeywordcreatedAftercreatedBeforecursorlimitownerUserId 接受内部用户 ID 或精确陶泥号,精确 SY-* keyword 也会在调用 procedure 前经认证服务解析成内部 user_id,未知陶泥号直接返回空列表。带 owner 条件时 procedure 直接走 by_editor_asset_owner_user_id,再按任务分组并以最终产物父项分页;响应中每个父项通过 children 携带可展开的中间产物,子项各占一行而不单独占分页名额。手动重拆图集保留真实 editor-atlas-split-* task_id;来源任务只能通过私有 provenance 由服务端生成账号素材的 source resource / asset object / Object Key 推导,再写入 group_task_id,不能信任素材库兼容推断值或普通资源创建接口可提交的 task_id/asset_kindprocedure 只有当前 editor generation runtime service identity 才能写入归组与完成事实。没有可信来源的现代拆分显式归到自身任务,不进入 legacy 回溯。每个切片同时写入 group_task_expected_asset_count,全部预期 asset ID 成功落库并逐行校验后写入不可逆 cohort 完成事实;read model 只让具有完成事实的一个拆分批次并入原图集父项,用户后来删除单片不会改变初始完成状态,部分失败批次与后续重复拆分按真实任务独立分页,避免残缺批次抢占根任务、单组无限增长或丢素材。历史行兼容沿项目资源链回溯,删除项目时只固化直接引用待删资源且尚未固化的历史行,持久化真实来源任务,不把有界展示 ID 反写覆盖来源。父项返回任务生成器和任务总成本,子项返回阶段生成器和阶段成本。provider 原图 / 角色动作预览承载模型生成成本,抠图、逐帧处理、透明图集和切片成本为 0;中间产物使用所属任务真实 asset_kind,不再新写 editor_green_screen_source,历史旧值只在 read model 中按 Object Key 映射为 charactericon-spritesheetcharacter-animation 并把旧任务成本只读归回 provider 原始产物。游标固定由父任务微秒时间和素材 ID 组成,API 必须同时识别整数微秒、seconds.microsZ 与 RFC3339 时间文本;内部时间无法编码时返回服务错误,不得静默返回 nextCursor = null。接口只用于查询,不提供分类筛选,也不执行精选审核、返还或展示状态修改,不通过后台 SQL 直查私有表。图片放大、视频和音频预览统一调用仅后台管理员会话可访问的 GET /admin/api/assets/read-url;该入口仅为预览允许后台跨 owner 签名,不改变主站 /api/assets/read-* 或 External API 的 owner 边界。成功换签后必须写入 event_key = admin_asset_read_urltracking_event,记录管理员 subject、Object Key / legacy path 和有效期,不记录 signed URL。
  • 后台精选审核:GET /admin/api/editor-showcase/assets 的素材 payload 与素材查询共同返回稳定媒体引用,并只透传底层正式 thumbnailSrcimageSequenceFramesimageSequenceDurationMs;缺少正式动作字段的存量数据必须先经角色动作规范化 procedure 迁移,不在 admin mapper 回读旧 generationInputs。两页前端共用同一缩略图、媒体类型、管理员换签和预览弹窗:动作列表缩略图只换签首帧,打开弹窗后才逐帧换签并按完整时长播放,不得在精选审核中复制一套立即全量换签或把绝对 OSS 私有地址直接交给浏览器的简化实现。角色动作弹窗由预览父组件持有当前序列有界的 signed URL、expiresAt、解析中、已加载和失败状态,帧元素只消费缓存 URL;缓存沿用主站 signed URL 读取缓存的 30 秒过期安全窗口,当前挂载帧进入窗口时自动换签,已经离开 DOM 的帧只在未临近过期时复用 URL,过期后再次进入窗口必须先换签。提前换签失败时必须在真实过期前继续展示已有已就绪 URL,并按有界退避自动重试;只有当前不存在可用 URL 或图片解码失败时才把该帧标记为失败。缓存最多覆盖当前序列帧,DOM 仍最多挂载当前可见窗口的 3 帧,切换序列、管理员 token 或关闭弹窗时中止在途换签并释放缓存;换签期间继续展示上一已就绪帧,不得在新地址尚未加载完成时提前隐藏可见帧。 现役路由只以 app.rs::build_router 实际 .merge(...) 的 module 和支付回调为准。旧玩法未挂载的 router、handler、worker 与 play_flow 实现已删除,不得由新的 .merge(...)、handler 转发或兼容 router 重新挂载。/api/creation-entry/config/admin/api/creation-entry/config*、旧 /api/creation/*、旧 /api/runtime/<play>/*、旧存档 / 游玩历史 / 公开作品路由均不是现役 API。新增公共路由仍必须经 app.rs 的统一中间件、鉴权、背压、埋点与可观测边界。

图片画布 Agent 对话

  • /api/editor/projects/{projectId}/agent-conversations 负责当前工程会话列表和新建;/api/editor/agent-conversations/{conversationId} 负责详情读取、终态工具消息懒回填和软删;POST /api/editor/agent-conversations/{conversationId}/messages 负责发送消息并返回普通 JSON EditorAgentMessageResponse,画布 Agent 不提供 /messages/stream SSE 路由。消息请求必须携带最长 128 字符的 clientMessageId;前端对该 POST 显式启用 1 次瞬时 transport 重试,并复用同一个序列化 body、clientMessageIdx-request-id。同一会话在锁内按该键幂等,重复键同内容返回已有回合或从已保存用户消息继续,异内容返回 409。数字 EditorAgentMessage.id 仍只作为工具确认 / 取消的后端消息定位符,不能复用为客户端幂等键。
  • module-editor-agent 只承载纯领域校验:标题派生、附件上限、消息输入规则和会话软删访问规则;不直接依赖 Axum、SpacetimeDB、OSS、LLM 或 Tokio。
  • platform-agent-harness 只承载与具体业务无关的 JSON function-calling 协议、工具 schema 注入、memory、hook、typed tool、轮次保护和待用户确认终止语义;platform-editor-agent 在其上叠加画布专属 LLM profile、system prompt、跨工具路由规则、图片上下文与八类生成工具。公共 harness 不依赖画布 DTO、计费、OSS、Axum 或 external job,也不得复用已退役的旧 platform-agent。harness 失败契约固定为 PromptRunError { error, partial_outputs }partial_outputs 显式携带失败前已产生的助手文本、成功工具和结构化失败工具输出。ToolFailurekindretryablefatal 及工具原始 output 必须对 harness 调用方可见,不得在公共层压成字符串或擅自丢弃。
  • prompt runner 对每个调用通过 AgentMemory::begin_staged 创建行为等价、写入隔离的 StagedAgentMemory 事务:限长、摘要、脱敏或持久化 memory 的 append 语义必须在本轮模型请求前生效,不得统一降级成 VecMemory。成功结束或已发生工具活动时必须显式调用 staged commit(),直接 drop 表示回滚。无工具活动的 completion、hook、解析或 max_turns 失败丢弃 staged transaction;已有成功或失败工具活动时在末尾追加 terminal error closure 后提交。外部 future drop / abort 若发生在工具完成后,必须提交工具结果与取消闭环;若工具仍在执行,则提交“已启动、结果未知”事实与取消闭环,供后续 reconcile,不能假装工具没有发生。
  • 画布 handler 的 18 分钟总 deadline 通过 runtime 提供的 deadline future 下沉到公共 runnercompletion await 可被 deadline 终止;effectful tool 在开始前检查 deadline,开始后不被中途 drop,返回后再携带结果收口为 PromptRunError。禁止用外层 timeout 直接 drop 整个 prompt future 并伪造空 partial_outputs
  • spacetime-moduleeditor_agent_conversation 只保存元数据;创建、列表、读取、更新时间和软删通过 create_editor_agent_conversation_and_returnlist_editor_agent_conversations_and_returnget_editor_agent_conversation_and_returntouch_editor_agent_conversation_and_returndelete_editor_agent_conversation_and_return procedure 完成,api-server 只能经 spacetime-client facade 访问。
  • 完整消息文档存 OSS editor-agent/{conversationId}.json,由 api-server 负责 2 MiB 上限、会话内串行锁、读改写、消息与工具结果持久化和 touch 元数据更新时间;该 JSON 不进入 editor_canvas.layers_json,也不作为画布布局真相。LLM 未配置、连接已经断开、请求明确失败、达到最终安全上限或规划不可解析时,必须写入 role=system、正文以 ERROR 开头的消息,并通过 deltaMessages 返回,errorMessage 保持为空;前端隐藏前缀并显示红色错误气泡,面向用户的错误正文使用中文语义,不暴露 completion error 等 framework 内部前缀或原始配置/定价错误;原始诊断只写后端结构化日志。后端仍把该 system 消息注入后续 LLM memory,使 Agent 能读取失败上下文。普通 JSON POST 尚未结束不形成持久化消息;工具失败同样必须形成可回读记录,不能只返回瞬时错误。
  • 画布 Agent 的 gpt-5.4-mini Chat Completions 规划使用 1024 生成 token 预算;VectorEngine 专用 client 显式发送当前字段 max_completion_tokens,其预算包含可见输出和隐藏 reasoning token。通用 OpenAI-compatible client 默认保留旧 max_tokens,只有确认 endpoint 能力后才 opt-in,禁止按模型名猜测或在 400 后自动重放。前端在 POST pending 120 秒后显示不入库的耐心等待提示;provider request future 明确返回 connect/timeout/HTTP/transport 错误时立即进入正式失败,尚未返回则继续等待。专用 provider 单 attempt hard timeout 为 8 分钟;请求发起阶段的 timeout、连接失败、4084295xx 读取 GENARRATIVE_LLM_MAX_RETRIES,但画布 Agent 最多重试 1 次,显式配置 0 仍可关闭,专用重试退避最多 60 秒。消息规划生命周期从 handler 入口开始计入 18 分钟总 deadline,进入 agent.prompt(...) 时只使用剩余预算;该 deadline 覆盖会话锁/上下文准备与最多 3 轮规划,并为错误持久化/HTTP 返回预留约 2 分钟,不允许多轮规划绕过前端 20 分钟 timeout。已收到成功响应头后的响应体读取或解析失败直接按明确失败收口,并使用该成功响应所属的真实 attempt 记录错误。重试只包围 LLM 规划请求并发生在任何待确认工具执行之前,因此不会重复提交生成任务或扣费。
  • 对话附件只允许引用当前工程 editor_project_resource 或当前账号 editor_asset 的图片;前端可提交展示用 imageSrc / thumbnailSrc,后端必须按 resourceId / assetId 重新归一、校验 owner / project 和 objectKey,再给 LLM 或生成工具使用。
  • 工具上下文不得把 OSS 消息附件当作 assetKind 真相;每次规划和每次确认都按附件 source + referenceIdspacetime-client 重新读取当前工程资源或账号素材库,只把权威 asset_kind 放入服务端内存 ImageMetadata。图标 spritesheet 的主参考必须精确为 icon-spec,普通图片或风格参考图只能作为额外参考;主参考类型缺失、已删除或不是 icon-spec 时必须在生成任务入队和用户确认生效前失败关闭,并提示重新选择图标规范。
  • edit-image 只接受当前图片上下文中的 object_image_idsource_image_id 不是现役 schema 字段,prompt、tool args、确认执行和测试中都不得生成或兼容该字段。
  • edit-image 在工具准备阶段生成的 direct EditorImageEditRequest 只用于待确认消息内部状态,不是 worker queue contract。确认接口必须先按当前 owner 对 sourceReferenceId 做权威来源解析、目标预检与参考图上限校验,再写入 { version: 1, request, source };站内图片编辑与画布 Agent 共用同一准备 helper。worker 不接受当前 direct request fallback,只保留正式 versioned payload 和已经存在的历史任务迁移解析。
  • 画布 Agent 工具复用既有编辑器图片生成 / 修改 / 图标 spritesheet BFF,并继续使用后端模型定价和 execute_billable_asset_operation_with_cost;前端不提交 priceMudPoints
  • api-server 对 PromptRunError 的持久化顺序固定为:先按 partial_outputs 原顺序映射已成功工具,将其保存为 status=not_completed 且无 externalJobId 的待确认消息;再在同一会话增量末尾追加 ERROR terminal system 消息并整体写入 OSS。后续规划失败不得吞掉失败前已执行的成功工具结果;结构化 ToolFailed 可用于调用方诊断与流程决策,但画布确认面不得把它伪装成成功待确认卡。
  • /messages/{messageId}/confirm/messages/{messageId}/cancel 只返回成功确认;前端成功后立即重新读取整个会话,以会话详情中的权威消息状态和 externalJobId 驱动气泡展示与任务轮询。
  • 会话详情的终态懒回填必须在单次 GET 和同一 conversation lock 内完成有界重试:任务结果读取、completed payload 解析或工具 formatter 首次失败后最多重试 3 次,每次等待 100ms 并重新读取主任务。任务读取失败或 completed 任务暂缺 result_payload_json 时,本次重试耗尽后仍保留 OSS 工具消息的 not_completed + externalJobId,由下次会话读取继续 reconcile;JSON 损坏、结果结构不兼容或 formatter 失败等确定性致命错误在重试耗尽后原子写为 failed,保存“重试 3 次后仍失败”的最后错误,避免永久循环。
  • 画布 Agent 是“正式任务 payload 不进入通用用户 read model”规则的窄例外消费者:GET /api/editor/agent-conversations/{conversationId} 只按会话中已有的 externalJobId 定向读取主任务,完成后由对应工具 formatter 从 result_payload_json 提取并归一有界的图片 / 视频 / 音频引用,写入 OSS 工具消息后返回。前端仍不得通过通用任务列表 / 状态接口读取或解析 request_payload_json / result_payload_json;OSS 轻量媒体引用只是会话展示与后续 Agent 上下文,不替代 editor_project_resourceeditor_asset、结构化画布表或 external_generation_job 的业务真相。未激活结构化存储的 canvas 才继续以 editor_canvas.layers_json 作为 legacy 布局真相。

创作 / 游玩统一流程主干

历史设计记录:本节所述 play_flow 和逐玩法 Adapter 已退役,不代表当前 app.rs 路由树。

modules/play_flow.rs 是后端创作与游玩流程的统一入口。现有外部 URL、DTO、错误 envelope、鉴权方式、入口开关语义和 SpacetimeDB schema 默认不变,但路由组织必须遵循:

  1. app.rs 只合并 modules::play_flow::router(state),不直接合并 RPG、拼图、抓大鹅、跳一跳、敲木鱼、拼消消、汪汪声浪、视觉小说或儿童向创作等逐玩法模块。
  2. play_flow 统一注册每个玩法的 playId、领域模块 key、创作路由前缀和运行态路由前缀;后续新增玩法或迁移旧玩法时,先补这个注册表,再挂具体领域模块路由。
  3. 新建创作、首次生成和 Remix 成草稿等会产生新创作的入口开关匹配规则同样归 play_flow 管理;creation_entry_config.rs 只复用该规则执行 open=false 熔断,不再维护第二份路径判断。
  4. play_flow 在进入领域 handler 前先解析并挂载 PlayFlowRequestContext,统一标记请求处于 CreationRuntimeCreationEntryConfigCreationSupportRuntimeSupportAiTaskPublicReadModelRuntimeInventory 阶段,并记录目标 playId / 领域模块 key;领域 handler 可以读取该上下文做后续收口,但不能绕过主干自建平行流程。
  5. play_flow 只做平台共性编排和领域 Adapter 组合,不下沉玩法规则;最后一步的草稿编译、资产生成、发布、运行态 start/action/finish、计分和排行榜仍交给对应 module-*spacetime-module procedure 和玩法 HTTP handler 处理。
  6. 公开作品聚合、作品详情、运行态库存、运行态设置 / 存档、游玩历史、存档归档、游玩统计、历史素材、AI task、runtime chat、文档解析、角色资产工坊、角色图像 / 动画生成和 Hyper3D 代理属于跨玩法或玩法支撑流程,也从 play_flow 主干挂入;modules/platform.rs 只保留通用 LLM / 语音代理,不再承接创作 / 游玩支撑路由。
  7. 如果某个旧玩法仍使用历史 /api/runtime/<play>/agent/* 作为创作命名空间,只保留外部兼容路径;新增实现和文档仍按“统一主干 -> 领域 Adapter”的语义描述,不把历史路径当新架构模板。

认证态用户与会话摘要下发口径

  • /api/auth/entry/api/auth/phone/*/api/auth/password/reset/api/auth/wechat/bind-phone 的普通手机号请求统一使用 purePhoneNumber 与可选 countryCode,不再接受旧 phone 字段;countryCode 未提供时默认中国大陆 86,显式值必须使用微信同口径的无加号国家码并且当前只允许 86module-auth 先校验国家码,再复用纯手机号规范化规则,最终统一以 +86 E.164 写入认证投影。微信小程序 getPhoneNumber 链路仍只接收客户端 wechatPhoneCode,后端必须要求微信 provider 成功响应中的 phoneNumberpurePhoneNumbercountryCode 均存在且非空,并只使用后两项执行国家码校验和 E.164 构造;腾讯未承诺 phoneNumber 为 E.164,不得依赖其前缀格式,也不得在微信链路默认 86
  • AuthUserPayload / AuthUser 只保留前端当前会用到的身份与绑定展示字段:idpublicUserCodedisplayNameavatarUrlphoneNumberphoneNumberMaskedloginMethodbindingStatuswechatBoundwechatDisplayNamewechatAccount。账号信息面板展示微信绑定时优先使用 wechatDisplayName;该字段只能来自微信平台 profile、历史已保存的微信身份资料,或小程序原生 input type="nickname" 提交的 displayName,不得用系统账号显示名或“微信旅人”这类假昵称兜底。小程序 /api/auth/wechat/miniprogram-login/api/auth/wechat/bind-phone 可接收 displayName/api/auth/wechat/miniprogram-login 额外返回 created,供小程序壳在快捷登录后判断是否需要补采集微信昵称。jscode2session 无法直接返回微信昵称或个人微信号,只能稳定拿到小程序维度 openid,后端以 wechatAccount 下发可区分的绑定账号标识,前端在缺少真实昵称时展示账号尾号。
  • AuthSessionSummaryPayload / AuthSessionSummary 只保留设备卡片与撤销需要的摘要字段:sessionIdsessionIdssessionCountclientLabelipMaskedisCurrentcreatedAtlastSeenAtexpiresAt
  • 设备诊断信息(例如原始 clientType / clientRuntime / clientPlatform / userAgent / miniProgramAppId / miniProgramEnv / deviceDisplayName)不再默认下发到前端;若未来确需展示,优先单独加窄 DTO,而不是把账号 / 会话快照恢复为全量对象。
  • 多端登录语义以 refresh_session 为粒度:同一账号可保留多个 active session,普通登录不会撤销旧设备;POST /api/auth/logout 只撤销当前 refresh session,不提升 token_versionPOST /api/auth/logout-all、改密、重置密码才吊销全端 session 并提升 token_version。鉴权中间件仍校验 Bearer sid 对应的 refresh session 是否 active,单独踢下线或当前设备退出可以让目标设备立即失效而不误伤其它设备。

api-server 模块化演进规则

api-server 的长期方向是从超大 app.rs 和超大 handler 文件收敛为按能力组织的 HTTP/BFF Module。后续改造必须保持 HTTP route、DTO、error envelope、SpacetimeDB schema、前端行为和计费语义默认不变;任何 contract 或 schema 变化都要先在当前文档中写清影响范围和迁移计划。

路由模块化规则:

  1. 每个能力 Module 只暴露 router(state) -> Router<AppState>;平台创作 / 游玩相关 Module 和支撑能力由 modules/play_flow.rs 统一 .merge(...) 或在支撑 router 内挂载,其它账号、资产基础、后台和平台基础能力再由 app.rs 直接合并。
  2. app.rs 只保留全局 middleware、TraceLayer、request context、tracking middleware、入口开关和少量顶层 glue;不得重新恢复逐玩法 creation/runtime merge 列表。
  3. 能力 Module 可在路由内部用 FromRef<AppState> 派生自己的 Feature State,例如 PuzzleApiState。全局 AppState 仍作为进程组合根、鉴权层和全局中间件状态,但业务 handler 优先只提取对应 Feature State,不直接暴露完整 AppState
  4. Feature State 只暴露该能力实际需要的 facade / adapter / 配置快照;若必须复用仍要求 AppState 的横切 helper(例如计费、外部失败审计或通用 tracking),应通过 Feature State 的窄方法或显式 root_state() 过渡,并在后续继续收窄。
  5. 路由迁移和业务重构分阶段处理;先移动路由装配,再拆 handler 内部实现,再收窄 handler 可见状态。
  6. 大 handler 拆分时优先按 router.rshandlers.rsapplication.rsassets.rsmapper.rserrors.rs 分层。handlers.rs 只做 Axum extract、鉴权和 request/response,业务规则继续下沉到 module-*
  7. 手写 Rust 模块入口统一使用同名 .rs 文件,例如 puzzle.rs + puzzle/*.rsmatch3d.rs + match3d/*.rs;不要再新增 mod.rs 入口。生成的 SpacetimeDB Rust bindings 也由生成脚本同步为 module_bindings.rs + module_bindings/*.rs 布局。

拼图 api-server 内部拆分:

  • server-rs/crates/api-server/src/modules/puzzle.rs 只负责路由装配、鉴权层和参考图 body limit;对外继续引用同一批 handler 名称。
  • server-rs/crates/api-server/src/state.rs 中的 PuzzleApiState 是拼图 HTTP/BFF 的 Feature State,集中暴露 SpacetimeClientPuzzleGalleryCache、OSS client、作者查询所需认证服务、拼图 LLM client 和少量 VectorEngine / Agent 配置快照。拼图 handler 只提取 State<PuzzleApiState>,不得重新改回 State<AppState>
  • server-rs/crates/api-server/src/puzzle.rs 只作为聚合入口,保留共享 import / 常量、内部模块声明和 handler re-export,不继续承载大段实现。
  • server-rs/crates/api-server/src/puzzle/handlers.rs 承接 Axum handler,负责 extract、鉴权上下文、调用 SpacetimeDB facade / 编排 helper,并返回 HTTP/SSE 响应。
  • server-rs/crates/api-server/src/puzzle/draft.rs 承接表单草稿保存、草稿编译、首关命名、UI 背景 prompt 和初始资产就绪校验。
  • server-rs/crates/api-server/src/puzzle/generation.rs 承接拼图图片与 UI 背景的生成编排、计费包裹和 reference image 路径选择。
  • server-rs/crates/api-server/src/puzzle/vector_engine.rs 承接 VectorEngine 请求体、HTTP 调用、下载 / base64 解码、OSS 写入、asset object / binding 持久化和上游错误归一。
  • server-rs/crates/api-server/src/puzzle/mappers.rs 承接 SpacetimeDB record 到 shared-contracts DTO 的映射。
  • server-rs/crates/api-server/src/puzzle/tags.rs 保留拼图标签生成、拼图通用错误映射和 SSE helper。

拼图发布 / 待发布门槛必须同时要求首图、关卡画面、UI spritesheet 与关卡背景资产包完整;module-puzzle::validate_publish_requirementsapi-server::puzzle::tags::is_puzzle_session_snapshot_publish_ready 使用同一资产语言,不得只凭 cover、标题、描述和标签把半成品标为 publishReadyready_to_publish

该拆分只改变 api-server 文件组织,不改变 /api/runtime/puzzle/* route、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义或计费语义;后续继续细分时也必须先保持行为不变,再单独讨论领域规则下沉。

/api/runtime/puzzle/runs* 当前接受 RuntimePrincipal,可同时识别登录用户 Bearer 和 runtime guest token。推荐页嵌入运行态的正式开局、交换、拖拽、下一关、暂停、道具与排行榜请求,应由前端在登录态下继续携带账号 access token;匿名游客仅在确认为未登录时走 runtime guest token。不要再把拼图 runtime 当成只认普通 Bearer 的纯账号接口。

公开正式 runtime 的启动与局内同步动作统一接受 RuntimePrincipal,包括拼图、拼消消、跳一跳、敲木鱼、抓大鹅 Match3D、方洞挑战、视觉小说、大鱼吃小鱼和汪汪声浪。登录用户仍使用账号 Bearer;未登录推荐页或公开运行态使用 Runtime Guest Token,后端以 principal.subject() 作为本局 owner / player subject,并用 WorkPlayTrackingDraft::runtime_principal(...) 记录游玩。创作、个人作品、删除、发布、Remix、点赞等账号或所有权动作不得改成 runtime guest 鉴权。

抓大鹅 Match3D api-server 内部拆分:

  • server-rs/crates/api-server/src/modules/match3d.rs 继续负责路由装配和 body limit;对外 handler 名称保持不变。
  • server-rs/crates/api-server/src/match3d.rs 只作为聚合入口,保留共享 import / 常量 / 内部类型、模块声明和 handler re-export。
  • server-rs/crates/api-server/src/match3d/handlers.rs 承接 Axum handler,负责 extract、鉴权上下文、调用 SpacetimeDB facade / 编排 helper,并返回 HTTP 响应。
  • /api/runtime/match3d/works/{profile_id}/runs/api/runtime/match3d/runs/{run_id}/click/stop/restart/time-up 属于正式运行态局部请求,必须接受 RuntimePrincipal;登录用户使用账号 Bearer,推荐页匿名游客使用 runtime guest token,后端以 principal subject 作为本局 owner,不得退回只认普通 Bearer 的路由。
  • server-rs/crates/api-server/src/match3d/draft.rs 承接 Agent session、草稿编译、题材 / 难度 / 物品计划和草稿持久化编排。
  • server-rs/crates/api-server/src/match3d/works.rs 承接作品 CRUD、封面 / 背景 / 容器资产生成入口、发布 / Remix / 点赞 / 游玩记录和作品级 helper。
  • server-rs/crates/api-server/src/match3d/item_assets.rs 承接物品生成批次编排、append / replace / delete / sort / merge、计费外层和草稿素材映射;sheet prompt、绿幕 / 近白底透明化、切图和切片持久化复用 generated_asset_sheets 通用模块。
  • server-rs/crates/api-server/src/match3d/vector_engine_gemini.rs 仅保留历史 VectorEngine Gemini 物品 sheet helper;当前草稿物品 spritesheet 以关卡整图为参考走 gpt-image-2 编辑链路,提示词、绿幕透明化和 OSS 持久化由 item_assets.rs / works.rs 约束。
  • server-rs/crates/api-server/src/match3d/runtime.rs 保留运行态轻量归一 helpermappers.rs / tags.rs / tests.rs 分别承接 DTO 映射、标签 / 通用错误 helper 和原有单测。

该拆分只改变 api-server 文件组织,不改变 /api/creation/match3d/*/api/runtime/match3d/* route、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义、VectorEngine / OSS 副作用边界或计费语义;后续继续细分时也必须先保持行为不变,再单独讨论领域规则下沉到 module-match3d

生成资产 Adapter 规则:

  1. 稳定单图链路可收敛到 api-server 内部生成资产 Adapterprovider 生成、下载/base64 解码、MIME/extension 归一、OSS private upload、HEAD、asset object confirm、entity binding。
  2. Adapter 输入应显式包含 provider、prompt、reference images、OSS prefix/path/file name、asset kind、entity kind/id、slot、owner/profile/source job、metadata 和可选透明背景后处理。
  3. Adapter 输出应保留 legacy public path、object key、asset object id、MIME、extension、task id 和实际 prompt。
  4. Adapter 不负责扣费、退款或钱包读取;计费仍由调用方显式包裹。
  5. 图片 provider 协议不再放在玩法模块里实现。产品、计费、DTO、持久化和 VectorEngine 创建 / 编辑首选请求统一使用 gpt-image-2;只有符合回退条件时,provider 边界才切到兜底模型 gpt-image-2-c。URL / base64 图片解析、远端图片下载、请求超时 / 上游状态 / 响应解析 / 缺图 / 下载失败的结构化日志统一在 server-rs/crates/platform-image/src/vector_engine/;其中 client.rs 只保留 provider 调用编排,transport.rs 负责 HTTP client 与 reqwest 错误归一,request.rs 负责请求体和路径,payload.rs 负责响应 JSON 字段提取,response.rs 负责响应状态分流和图片结果归一。api-server 只负责配置校验、玩法 prompt 编排、OSS / asset object / binding 持久化、计费和外部 API 失败审计落库。
  6. OSS 平台适配日志统一在 server-rs/crates/platform-oss 输出,覆盖 sign_post_objectsign_get_object_urlhead_objectput_object。日志字段固定使用 provideroperationbucketendpointobject_key / key_prefixaccesscontent_typecontent_lengthstatusstatus_classerror_kindelapsed_ms,只记录对象定位和排障信息;不得输出 AccessKey、policy、signature、Authorization header 或完整 signed URL。generated 私有对象上传时必须由 OSS 对象头承载浏览器 / CDN 缓存策略,默认写入 Cache-Control: public, max-age=31536000, immutable,不得改成 api-server 本地磁盘静态资源兜底。
  7. Puzzle、Match3D、音频、GLB、视频等复杂媒体可以复用 OSS + asset object + binding 的底层持久化能力,但玩法专属处理规则留在各自编排层,不塞进公共接口。
  8. 拼图入口页与结果页新增关卡的本地参考图不走浏览器直传 OSS,前端读取为 Data URL 后随创作 action 提交,并在读取前限制 6MB、显示“图片≤6MB”。api-server 必须对 Data URL 实际字节数再次校验;历史图片才提交 referenceImageAssetObjectId(s),后端校验 asset_object 的 bucket、kind、图片 MIME、大小和 owner 后签发只读 URL 给 VectorEngine 读取。
  9. 系列素材图集实现真相源在 server-rs/crates/platform-image/src/generated_asset_sheets/:调用方必须传入 grid_size 作为 n*nn,可选传入物品名称 prompt 模板和特殊设定 prompt;模块负责 sheet prompt 组装、按 n*n 切片、透明化、PNG 输出、OSS private upload 请求构造和 sheet / item / special prompt 元数据持久化。server-rs/crates/api-server/src/generated_asset_sheets.rs 只保留 AppState / AppError 适配和兼容导出。玩法只负责规划 slot、调用具体生图 provider、计费、失败回写,以及把通用切片结果映射回自己的 DTO / 草稿 / runtime 字段。

SpacetimeDB schema 变更规则

  1. 任何 table、view、reducer、procedure、row shape 或 bindings 变化,都必须同步本文件表 / view 目录和生成绑定;真实 table 变化还必须同步 server-rs/crates/spacetime-module/src/migration.rs,view 属于派生投影,不写入迁移导入导出表清单。
  2. 已有表新增字段必须放在 Rust 表结构体最后,并设置明确 #[default(...)]
  3. 已发布并持久化的 enum 新增 variant 只能追加到末尾,不能插入中间、删除、重命名或重排;否则会移动既有 variant 的判别序号,导致旧数据解释错误或生产发布 schema 迁移失败。
  4. 删除字段、改名、重排字段、改类型或修改字段属性前,必须先询问用户并确认迁移计划。
  5. Vec 字段不要直接写无法 const 求值的 default;需要默认空集合时优先使用 Option<Vec<T>>#[default(None::<Vec<T>>)],业务层归一为空数组。
  6. 运行态读表必须按已声明索引访问。只要 table 上存在覆盖查询前缀的 #[index(...)] 或主键 / unique accessor,列表、详情、快照组装和计数都先用对应 accessor .filter(...) / .find(...),再在内存中处理索引无法覆盖的残余条件;不得用 .iter().filter(...) 扫整表替代现成索引。
  7. 面向公开列表的只读投影优先做成 public view / public 读模型表,并由 api-serverspacetime-client 长期订阅后读本地 cache。跨玩法公开作品统一主读模型是 public_work_gallery_entrypublic_work_detail_entry;公开作品资产读取授权投影是 public_work_asset_read_grant;各玩法既有 *_gallery_card_view / *_gallery_view / custom_world_gallery_entry 保留为 source view 和兼容路径。短期不把作品列表整体交给浏览器前端直接订阅;不要让 HTTP 列表接口每次请求都调用 procedure 重新组装全量列表。需要请求时间窗口的轻量统计可订阅 public_work_play_daily_stat 后在 api-server 本地聚合,需要写入副作用的详情、点赞、游玩记录仍走玩法 procedure / reducer。前端不得直接订阅 puzzle_work_profilecustom_world_profile 等领域源表,也不得自己做 join、聚合或权限逻辑。首屏、排序、字段归一、权限降级和 HTTP fallback 由 api-server BFF 维持。
  8. 多列索引按 SpacetimeDB 绑定生成的元组参数直接传入,例如 .filter((source_type, profile_id, played_day));前缀查询只传前缀元组,例如 .filter((scope_kind, scope_id.as_str()))。不要为了绕过类型问题退回整表遍历。
  9. procedure result 必须返回 typed snapshot / typed value。spacetime-client mapper 不得再通过 row_json/session_json/work_json/items_json/run_json/event_json/feedback_json: Option<String> 做跨层 JSON 字符串传输,也不得在 mapper 里反序列化旧 *JsonRecord 兼容结构。业务内部持久化字段如 profile_payload_jsonlevels_json 等不属于 procedure result 载荷例外,仍按各自表契约处理。
  10. procedure 需要按调用者 identity 鉴权时,必须先从外层 ProcedureContext::sender() 捕获 caller,再把 caller 显式传入 try_with_tx 闭包内的事务函数。当前 workspace 锁定 SpacetimeDB 2.8.3;鉴权边界不得依赖事务上下文的隐式 sender 语义,即使 SDK 升级也继续保持显式 caller 参数。
  11. 旧创作模板历史表采用两阶段退役:阶段一只允许 migration operator 调用固定范围的 clear_retired_database_tables procedure 清空 63 张旧玩法表,输入仅有 dry_rundry_run=true 只返回逐表行数统计,dry_run=false 在单一事务内逐表删除全部行,任一失败整体回滚。清单必须在 spacetime-module/src/migration.rs 中静态维护,不接受调用方传入任意表名;runtime_settingruntime_snapshotuser_browse_historycreation_entry_config 等现役表不属于清理范围。阶段一保留所有旧表定义、legacy_schema/**、migration 导入导出白名单和生成 bindings,不执行 DROP TABLE--delete-data=always 或系统表写入。阶段二待备份、客户端兼容性和运行态确认完成后,另行评估从 module 定义与 migration 白名单移除空表,并同步 schema / bindings;当前在固定清单旁保留 TODO,不提前改动表定义。
  12. 修改后运行:
npm run spacetime:generate
npm run check:spacetime-runtime-access
npm run check:spacetime-schema
npm run check:server-rs-ddd

本地联调和人工排障不要使用 spacetime --root-dir。本地数据隔离使用项目脚本或 --data-dir;发布目标显式传 --server / --server-url

账户充值数据契约

  1. profile_recharge_product_config 是泥点和会员商品配置真相源,默认商品只在表为空时由 SpacetimeDB 播种。module-runtime 中的默认商品 helper 只作为空库种子和兼容入口,不再作为运行期业务真相。
  2. 默认泥点商品固定为四档:points_60 = 60 泥点 / 600 分points_180 = 180 + 90 泥点 / 1800 分points_300 = 300 + 150 泥点 / 3000 分points_680 = 680 + 340 泥点 / 6800 分points_60 的首充赠送为 0;后三档首次购买分别赠送基础泥点的 50%
  3. 后台通过 /admin/api/profile/recharge-products 读写充值商品配置;字段覆盖 productId、标题、商品类型、金额分、基础泥点、首充赠送泥点、会员天数、徽标、说明、会员层级、会员每周期限时泥点、周期天数、队列上限、折扣率、启用状态和排序。
  4. 充值中心、下单校验和支付确认入账都读取 profile_recharge_product_config。充值中心 BFF 还必须在 mudPointBalance 下发 totalPointspermanentPointslimitedPointslimitedExpiresAtdailyFreePointsdailyFreeResetPointsdailyFreeResetsAt;前端以该 read model 为真相源,不得自行用总额相减推算余额桶。当前版本公开 UI 只渲染不限时泥点和每日免费泥点,limitedPointslimitedExpiresAt 仅保留给存量兼容和后端结算。历史订单保留下单时写入的商品标题、金额、渠道、状态和 provider transaction id,不随配置改动回写。
  5. 泥点首充资格按 user_id + product_id 的历史 paid 订单独立判断。某个档位已支付后,只隐藏该档位的首充赠送;其它未购买档位仍展示和结算首充赠送。
  6. hasPointsRecharged 只保留为账号是否发生过任一泥点充值的兼容字段,不得驱动所有商品展示隐藏或结算金额计算。前端只渲染后端返回的商品快照。
  7. 当前版本公开充值 UI 只展示泥点商品,不渲染会员购买页签、会员商品、购买会员或升级会员入口。充值中心响应中的会员商品兼容字段、默认会员商品、profile_membership 和周期刷新逻辑继续保留;存量会员的 cycle_remaining_points 仍通过充值中心 read model 下发用于兼容和结算,但不作为限时泥点在当前版本前台展示。
  8. 默认会员商品为空库播种时使用 Starter / Basic / Pro / Ultimate 四档,默认有效期均为 30 天,每周期限时泥点分别为 200 / 800 / 2500 / 6000,队列上限分别为 2 / 2 / 5 / 10
  9. 会员有效期和周期重置时间是两条独立时间线。expires_at 只决定会员是否生效;cycle_resets_at 只决定当前周期限时泥点何时重置。会员升级只更新档位并补齐当前周期限时泥点差额,不延长 expires_at,不移动 cycle_resets_at 和周期天数。同级会员购买只从当前 expires_at 延长有效期,不发放额外当前周期泥点,也不移动重置时间。
  10. 会员周期刷新发生在个人中心、充值中心、任务中心、账单读取和钱包扣费入口:到达 cycle_resets_at 时先清除上周期剩余限时泥点,再发放当前会员档位周期额度;会员过期时清除剩余限时泥点并把状态降为普通。周期发放和重置流水分别使用 membership_period_grantmembership_period_reset
  11. paymentChannel 缺失、未知或冒用小程序支付设备时必须拒绝;真实微信渠道只允许 wechat_mpwechat_mp_virtualwechat_jsapiwechat_h5wechat_native,生产配置不得把真实支付静默降级为 mock
  12. access JWT 只携带最小设备快照 device.client_typedevice.client_runtimedevice.client_platform。充值下单按该快照拦截小程序渠道:小程序只允许 wechat_mp / wechat_mp_virtual;移动网页和微信内 H5 走 wechat_h5;桌面网页和桌面微信走 wechat_nativewechat_jsapi 仅保留后端能力,未接微信开放平台前不由前端自动选择。历史普通 Web 登录态若缺少设备快照也允许继续进入 JSAPI / H5 / Native 渠道的后续支付配置校验,但不放宽小程序虚拟支付。
  13. 所有微信真实渠道都以微信支付通知或服务端查单确认 SUCCESS 为到账事实;小程序、H5 跳转和 Native 二维码返回都不能直接发放泥点或会员。
  14. 微信 JSAPI / H5 / 小程序 / Native 下单统一显式传 5 分钟 time_expire,格式为 RFC3339 秒级时间;Native 额外通过 wechatNativePayment.expiresAt 下发给前端二维码弹窗展示。
  15. 真实微信渠道的新建 pending 充值订单会写入 SpacetimeDB 原生 scheduled 表 profile_recharge_order_expiration_timer。到期 reducer 只做数据库内状态转换:订单仍为 pending 时更新为 expired 并写 expired_at,同时删除 timer。HTTP api-server 只订阅这张活跃 timer 表的删除事件,收到 order_id 后通过 procedure 重新读取订单,只有状态确认为 expired 才执行微信查单补偿;支付或主动关闭同样会删除 timer,但会被状态判断忽略。监听断线期间遗漏的删除事件由未检查过期订单 catch-up 补齐,不订阅完整 profile_recharge_order 历史表。普通微信支付查单中 SUCCESS 可把 expired 补确认成 paid 入账;NOTPAY 会调用微信关单并把本地订单保持为 expiredCLOSED / REVOKED / PAYERROR / ORDER_NOT_EXIST 只记录检查结果。wechat_mp_virtual 使用小程序 access_token 和虚拟支付 AppKey 调用官方 /xpay/query_order,只在返回单号、支付类型 order_type=0/7、金额、合法 paid_time 与本地契约一致且状态为 2/3/4 时补入账;退款类型 1/8 不得触发充值,其余已知状态只记录检查结果。short_series_goodsstatus=2 恢复时先幂等入账,再调用 /xpay/notify_provide_goods,失败后允许基于本地 paid 状态只重试发货;short_series_coin 不调用现金单发货接口。external-generation-worker / controller 不处理充值过期。
  16. 普通微信支付 V3 退款事实由 profile_recharge_refund 保存,回调、退款 API 响应、主动查单和交易账单发现统一调用 record_profile_recharge_refund_observation_and_returnout_refund_no 是商户幂等键,provider_refund_id 唯一;退款申请响应和主动查单等生产者生成 observation ID 时必须同时纳入来源和事实指纹,同一来源同一事实稳定重放、不同来源不得复用 ID;重复 observation 必须校验订单、交易、金额、状态、来源和指纹,不允许仅按主键直接吞掉冲突。
  17. profile_recharge_order_refund_settlement 按原订单聚合累计成功退款金额和权益回收。部分退款不改充值订单 paid;累计金额等于订单金额时才改为 refunded。历史支付和首充资格以 paid_at 是否存在判断,退款不把用户重新变成首充。
  18. 泥点退款按累计成功退款比例计算目标回收量,全额退款强制精确回收原 points_delta。自动回收只扣普通永久泥点,每日免费和会员周期限时泥点保持不变;不足部分持久化为 shortfall 并冻结正式钱包消费,后续 worker 只重试本地回收。已成功退款的泥点订单若出现微信交易号、订单总额冲突或结算计划无效,必须将 settlement 标记为 wallet_frozen 并阻断普通消费。管理员只能对交易号或订单总额冲突执行“确认退款归属”:BFF 必须把微信退款事实的交易号、订单总额、当前冲突类型与本地订单值并排下发并展示,提交时携带管理员实际看到的 expectedErrorCodeSpacetimeDB procedure 在同一事务内核对当前错误码一致后,才在退款行尾部不可变记录管理员、原因、时间和本次获批的错误码并重新运行标准结算;已处理请求只有管理员、归一化原因和错误码全部一致时才视为幂等重放,状态变化或任一审计内容不一致必须 fail-closed。一次确认只豁免对应的交易号或订单总额冲突,渠道、支付状态及其他未获批冲突继续 fail-closed。能追回的永久泥点照常扣除,余额不足继续形成欠账;不能直接清空冻结或伪造已追回量。非法结算计划仍保持冻结等待数据/代码修复。流水来源为 recharge_refund_recovery。会员充值没有可逆 grant 快照,退款统一标记 manual_review,不猜测回滚档位、有效期或周期泥点,也不复用泥点退款冻结语义。
  19. 外部现金退款 SUCCESS 必须先持久化并 ACK,即使本地订单缺失、金额冲突、权益不足或会员需要人工处理,也不能回滚已经发生的现金事实。冲突 observation 记录 resolution code 并进入告警;只有验签、解密、契约解析或 SpacetimeDB 持久化失败才让微信重试。
  20. 主动查询与退款交易账单 worker 只由 HTTP 角色运行并由 WECHAT_PAY_REFUND_RECONCILIATION_ENABLED=true 显式开启。生产 env 示例和 API deploy 会为缺失配置补 true;当 WECHAT_PAY_ENABLED=true + WECHAT_PAY_PROVIDER=real 时显式关闭该开关必须阻断发布,启动日志也必须明确记录启用或异常关闭状态。非终态退款按 1 / 5 / 10 / 20 / 30 分钟衰减查单;北京时间次日 10 点后按 30 个稳定分片轮转补扫微信 API 可查询的近 90 天 bill_type=REFUND 交易账单,每 30 分钟覆盖完整窗口。单行失败不阻塞同日其他退款或其他日期,但该日不写完成 checkpoint 并继续重试;昨日返回 NO_STATEMENT_EXIST 时至少延迟到次日 10 点后再确认空账单。账单申请响应验签,GZIP 解压后按 SHA1 验真,CSV 用结构化 parser 和十进制定点金额解析;发现手工退款后必须再查单取得当前状态。
  21. 普通 V3 支付通知同时校验 AppID、商户号、本地订单渠道、金额和微信支付单号;success_time 缺失或非法时拒绝,不能用本机时间补齐。晚到通知遇到 refunded 订单只做交易号一致性幂等校验,不再次发放权益。
  22. 后台主动退款只支持 wechat_mpwechat_jsapiwechat_h5wechat_native 普通 V3 泥点订单。api-server 必须先按正式支付渠道和商品类型拦截不支持的订单,再做微信支付订单查单预检,然后调用 SpacetimeDB procedure 原子创建退款 hold;只有 hold 成功才允许调用微信退款。wechat_mp_virtual、历史非正式渠道值、会员、未支付、对账未完成、退款已满额、人工冻结、退款欠账或永久泥点不足必须在调用普通 V3 provider 前 fail-closed。
  23. profile_recharge_refund_hold 以稳定 out_refund_no 为主键,保存订单、用户、本次退款金额、占用永久泥点、管理员、原因和 active / settled / released 状态。重试只有在订单、out_refund_no、退款金额、管理员和归一化原因全部与原 hold 一致时才可复用,任一不一致都按幂等内容冲突 fail-closed,不能用新原因调用微信后保留旧审计。部分退款的 hold 在累计应追回增量之外额外保留 1 泥点并发舍入缓冲,全额退款不加缓冲;活动 hold 不改变钱包总额,但普通钱包消费必须预留全部活动 hold;成功退款 observation 扣款并结算匹配 hold,关闭退款释放 hold,外部退款追回不得消耗其他活动 hold。
  24. 退款欠账继续以 profile_recharge_order_refund_settlement.unrecovered_points 为唯一真相;不新增平行 debt 累计。profile_wallet_manual_restriction 只保存人工冻结,普通消费同时检查人工冻结与退款欠账。后续永久泥点到账后继续偿还欠账,每日免费与会员周期泥点不参与;解除人工冻结不得清除退款欠账限制。
  25. 管理员充值订单、用户详情、历史花费手动对账、退款预检/执行、应急退款号登记、退款人工复核和钱包冻结接口只留在 api-server 管理员鉴权路由。用户详情中的历史花费泥点数读取 profile_wallet_consumption_total 投影;已有投影时,每次 asset_operation_consume 负向流水落账在同一事务内按主键 O(1) 原子累加,退款不回减,充值退款追回、余额重置、赠送和 hold 均不计入。首次上线必须在停止业务写入的维护窗口内,由 owner 调用 POST /admin/api/profile/users/initialize-consumption-projections,一次扫描全部权威钱包流水,为每个已有钱包流水的用户初始化存量投影;接口成功后才能恢复流量。维护遗漏或新用户缺行时,首次消费按该用户索引一次性重建(当前消费流水已经在同一事务中,不能重复加本次金额);钱包详情首次读取也保留同一按用户兜底。POST /admin/api/profile/users/reconcile-consumption 是显式手动对账入口:owner 始终可用,member 必须单独持有 profile-wallet-consumption-reconcile 操作权限,任何一级 Tab 都不自动附带;用户详情只在后端返回 canReconcileConsumption=true 时展示按钮。操作经二次确认后扫描该用户全部权威钱包流水、比较并校准投影,同时记录管理员和对账时间。退款人工复核 BFF 必须从管理员会话写入操作人,要求非空原因,返回微信退款交易号、订单总额以及获批错误码等正式审计字段,并调用 runtime service identity 受限 procedure;后台确认面板必须展示这些后端事实,前端不得自行改 settlement、钱包冻结或消费累计。外部微信副作用由 platform-wechat 执行,退款/hold/钱包事务留在 spacetime-module,后台前端只展示 BFF 返回的正式状态。

创作入口泥点扣费契约

  1. creation_entry_type_config.unified_creation_spec_json 内的 mudPointCost 是玩法新建草稿初始生成的泥点成本真相源,同时供入口卡展示和前端余额前置校验使用;旧契约缺失时允许按代码默认成本兜底。
  2. api-server 执行拼图首图生成、抓大鹅完整草稿生成和汪汪声浪初始三图生成时,必须通过 GET /api/creation-entry/config 同源配置解析对应玩法成本后再调用钱包扣费 procedure,不得继续使用前端或后端硬编码常量作为实际扣费真相。
  3. 结果页单图重生成、发布、道具使用和其它独立资产操作仍按各自业务操作成本执行;不要把初始草稿成本误套到这些单次操作上。
  4. 资产操作的预扣费必须 fail-closed:钱包或 SpacetimeDB 预扣费不可达、超时或返回业务错误时,api-server 直接返回错误,不允许继续调用图片、音频、GLB 等外部生成 provider。
  5. 需要支持 HTTP retry 的计费 ledger id 必须包含当前请求的 request_id;前端 fetchWithApiAuth 同一次业务请求的静默刷新重试复用同一个 x-request-id,后端不得再使用 prompt 指纹或随机 asset id 作为扣费幂等键。
  6. 外部生成已预扣费但后续失败时,失败/任务状态变更事务必须在 SpacetimeDB 内按 refund ledger id 幂等写入 profile_wallet_refund_outbox pending 行;跨节点 worker 从库内 pending 行批量处理并在库内事务执行退款,成功后删除 outbox 行,失败按 available_atattempts 重试。当前 attempt 若 consume 尚不可见,事务仍必须先写 asset_operation_wallet_settlement 取消 intent,阻止迟到扣费。普通 inline 资产失败也先调用同一 DB outbox procedure;只有 SpacetimeDB 完全不可达时才写 wallet-refund-outbox 本机 emergency spool。默认启用,配置项为 GENARRATIVE_WALLET_REFUND_OUTBOX_ENABLEDGENARRATIVE_WALLET_REFUND_OUTBOX_DIRGENARRATIVE_WALLET_REFUND_OUTBOX_BATCH_SIZEGENARRATIVE_WALLET_REFUND_OUTBOX_FLUSH_INTERVAL_MSGENARRATIVE_WALLET_REFUND_OUTBOX_MAX_BYTES。本机文件按 refund ledger id 幂等落盘;成功重放后删除,坏文件隔离为 corrupt-*,不能替代库内 outbox。外部生成任务触发的扣费和退款必须在 profile_wallet_ledger.metadata_json 与两类 outbox 中保留 externalGenerationJobIdexternalGenerationClaimAttempt,便于从退款记录追溯到具体 attempt。
  7. 拼图首图后台生成的跨实例互斥锁必须落在 SpacetimeDB puzzle_background_compile_task 表,claim id 由 task_id + request_id 构成,释放时必须校验 claim id,避免旧后台任务释放新请求抢到的租约。

用户钱包与编辑器生成扣费契约

  1. profile_wallet_config 是账号初始泥点和每日免费泥点基础发放量的统一真相源;后台通过 /admin/api/profile/wallet-config 一次读写 initialMudPointsdailyFreePointsPerDay。新用户账号完成注册并成功同步正式认证表后,注册赠送金额读取 initial_mud_points;未写入配置时默认为 100。每日免费基础发放量未写入时默认为 20。注册赠送流水原因仍使用 new_user_registration_reward,流水 ID 继续保持幂等,重复发放请求不得叠加余额。
  2. 用户钱包余额对外仍暴露为一个总余额,但后端扣费必须按“每日免费泥点 -> 会员周期限时泥点 -> 普通永久泥点”的顺序消耗,前端不得自行决定扣费桶。扣费流水 metadata_json 必须记录 dailyFreePointsDeltadailyFreeDayKeymembershipPeriodPointsDeltapermanentPointsDelta 和会员限时泥点所属 cycleResetsAtMicros;资产退款中的会员限时泥点只在原周期仍有效时恢复会员额度,其余会员部分进入普通永久泥点。原每日免费消费部分在同一业务日退款时恢复原当日额度;跨北京时间业务日退款时叠加到退款当日每日免费桶,不进入普通永久泥点,当日 granted_pointsremaining_points 均可因此超过当前基础发放量。原永久泥点消费部分无论是否跨业务日,均按退款流水中的 permanentPointsDelta 退回普通永久泥点。
  3. 每日免费泥点是独立于每日任务和会员周期的正式余额额度,基础发放量读取 profile_wallet_config.daily_free_points_per_day,不得由前端或后台任务配置改写。profile_daily_free_points 保存当前北京时间业务日、当日基础发放及跨日退款叠加后的总额度和剩余额度;北京时间每日 00:00 作为业务日边界,个人中心、充值中心、账单读取和钱包扣费入口在首次触达新业务日时原子清除昨日剩余及退款叠加量,并按当时最新配置重置今日 granted_pointsremaining_points。同一业务日已初始化的用户不因后台改配置被即时追补或回收;新配置从尚未初始化当日额度的用户或下一次跨日重置起生效。首次初始化使用 daily_free_grant 流水,跨日重置使用 daily_free_reset 流水。充值中心的 dailyFreeResetPoints 显式来自该配置,不得用可因跨日退款增大的当日 granted_points 反推。惰性落库不能改变“北京时间 00:00 后读取即为新日额度”的对外语义。
  4. 每日任务奖励继续使用 daily_task_reward 流水并进入普通永久泥点,但主站隐藏每日任务卡片和任务中心入口,不再把每日登录任务描述为“每日免费泥点”。任务配置、进度、领取记录和后台管理能力暂时保留,除非后续需求明确删除。
  5. 编辑器画板所有会调用外部生成 provider 的入口都不从前端请求接收 priceMudPoints;同步请求以 SpacetimeDB editor_generation_pricing_config 当前全局配置计算,外部生成队列则以 external_generation_job.price_mud_points 保存的入队价格为准,worker 的扣费、退款、响应和资产成本不得按执行时配置重算。前端按钮泥点只作为展示。
  6. 编辑器图片生成 / 图片修改 / 图标 spritesheet / UI 设计图提取素材 / 视频 / 角色动作 / 音效 / 背景音乐必须在后端计算模型价格后使用 execute_billable_asset_operation_with_cost 预扣泥点;预扣失败必须 fail-closed,不得继续提交 VectorEngine、Ark、Suno 或 Vidu 上游任务。进入预扣或外部生成队列前,还必须调用只读 preflight_editor_generation_target_and_return,按认证 owner 校验可选 projectId,并校验归一化后的可选 assetFolderId。请求 JSON、纯本地格式、引用数量及 data: / blob: 稳定媒体门禁必须先于该数据库预检返回 4xx;目标预检随后执行,并且仍必须早于定价读取、引用 owner 解析、generation input rebuild、入队、扣费、provider 请求或 OSS 写入,不得为调整错误优先级把任何远端读取或副作用搬到预检前。project、任意旧 folder-* 与 owner 默认目录 ID 都统一指向当前 owner 的默认素材目录;角色图片、角色动作、图标 spritesheet 与 UI 提取在请求省略目录时也必须按最终真实写入的默认目录预检。尚未创建的默认目录允许通过,自定义目录必须已存在且归属当前 owner。预检 helper 必须返回同一份 canonical projectId + assetFolderId,调用方在入队、worker/provider 执行和原子结果准备中都复用这份值;禁止校验 trim/默认映射后的值却继续序列化或持久化原始请求。worker / inline 执行在首个 provider 或 OSS 写副作用前再次执行同一预检,不能只信任入队时结果;任一读取不可达、超时、项目或目录不匹配都失败关闭。该预检不创建锁或 reservation,最终资源 / 素材 procedure 仍必须重新校验归属,以处理预检后并发删除或转移。
  7. 队列任务按 job_id + claim_attempt 使用独立 consume/refund ledger。新 attempt 结算旧 attempt 时必须先写 asset_operation_wallet_settlement:旧 consume 已存在则原子退款,尚不存在则写取消 intent;迟到 consume 在同一 SpacetimeDB 事务内看到 intent 后必须失败关闭。重复 consume/refund 只有用户、金额、来源和配对 ledger 全部一致时才可视为幂等成功。lease 过期时只有 attempt < max_attempts 才能递增并重领;最终 attempt 已耗尽时,claim transaction 必须直接把 job 收口为 failed、清理 lease、写失败事件并结算当前 attempt,不能再把任务返回 worker 或调用 provider。
  8. 音频生成的编辑器链路虽然任务提交和结果发布分离,仍必须把提交时后端计算出的模型价格写入 AudioAssetBindingTarget.billing_points_cost,最终发布落资产时按该价格扣费;创作音频目标未提供该字段时才使用旧的创作音频固定成本。
  9. 编辑器进入外部生成持久队列的图片生成、去背景、图标 spritesheet、UI 设计图提取、角色动作和视频参考图,调用方必须提交 objectKey / resourceId / assetId 候选稳定引用;BFF 只做内联媒体与 payload 门禁,登记状态和归属由 worker 统一解析。图片修改主来源是明确例外:站内 /api/editor/images/edits 与 External v1 API 调用方只提交必填 sourceReferenceId,且只接受当前账号已登记的 editor_project_resource.resource_ideditor_asset.asset_idobjectKey、URL、Data URL、Blob URL、未登记 ID 以及旧 sourceImageSrc/sourceResourceId/assetKind 字段必须在入队前返回 400。api-server 必须通过共享 SpacetimeDB 窄查询分别按资源 ID、素材 ID 主键定点解析,双表同时命中、未命中、跨 owner、缺失或越权 asset object、禁止或未知类型均失败关闭;objectKey 与素材类型只能来自服务端解析结果。提供 targetLayerId 时必须同步读取指定 projectId,校验目标图层关联资源、双方权威对象和默认类型;双方都有 assetObjectId 时必须比较 ID,任一方缺失时才回退 canonical (bucket, objectKey)。来源默认类型只参与同一权威对象的绑定一致性校验;快速编辑准入必须在解析目标图层后按 assetKindOverride ?? resource.assetKind 的有效类型判断,不能先按来源默认类型拒绝,因此默认 icon 资源覆盖为 scene/spec 时允许,未覆盖时仍拒绝。图片修改入队载荷保存版本化权威来源快照,worker 调用 provider 前必须按同一 sourceReferenceId 再次定点解析并拒绝身份或类型漂移;历史载荷只把非空旧资源 ID 或旧来源字符串本身作为业务 ID 尝试迁移,禁止按 objectKey 反查或信任旧 assetKind。去背景入口必须在入队前把 assetKind 收口为来源项目资源或素材库解析出的静态图片语义类型,资产对象存储类型只参与非静态媒体门禁:显式项目资源 ID / 素材 ID 必须优先于 objectKey 回退;纯 objectKey 对应多条且权威元数据不一致时必须返回 400 并要求 sourceResourceId 或业务 ID 消歧,不能按列表首条选择。请求值与权威类型冲突,或任一记录表示视频、音频、动画、图片序列时失败关闭;无 canvasCompletiontargetLayerId 还必须与来源优先按 assetObjectId、缺失时按 canonical (bucket, objectKey) 证明为同一对象,且默认类型一致,禁止用来源 A 覆盖目标 B。原位替换使用纯 objectKey 且省略 sourceResourceId 时,入队与 Worker 都以目标图层当前资源作为显式来源绑定并持续复验。任务 request_payload_json / result_payload_json 任意层级都禁止 data: / blob:,并受统一字节上限保护。其它候选引用的 objectKey 最终必须归属于当前账号的 editor_project_resourceeditor_assetasset_object,由 worker 在解析后、签名读取 OSS 前完成归属校验。本地红框序号标注图必须先上传并确认对象,再作为图片修改的辅助 referenceImageSrcs 入队,主来源仍使用原图业务 ID;不得把既有 objectKey 下载成 Data URL 后写入任务。图标素材、图片快速编辑和 UI 素材提取的额外参考图必须真正传入 provider,不得只写入 generationInputs 展示快照。普通图片生成最多 5 张参考图;图片修改、图标素材和 UI 提取的额外参考图上限还必须与所选 provider 的总容量共同取最小值:GPT-image-2 总计 5 张,nanobanana2 总计 14 张。前端添加和提交、api-server 入队 / 扣费前以及 platform-image provider 边界都必须明确拒绝超限,禁止用 .take(...) 静默截断。同步且不持久化的历史兼容入口即使仍能解析 Data URL,也不能把该值转存到工程、素材、元数据、审计或任务表。
    • 图标规范结构化分析里位于 <playSetting> / <artStyle> XML 元素内的数据必须转义 & < > " ';玩法润色、美术风格润色、规范图生图和图标 spritesheet 等自然语言 prompt 必须保留已经过边界校验的原文。图标 spritesheet 的 iconDescriptions 在请求边界执行独立合同:原始数组满足 OpenAPI 1..100,去空后至少保留 1 条;单条最多 200 个 Unicode 字符、拼接后合计最多 2000 个 Unicode 字符且不超过 6144 个 UTF-8 字节;只有 ValidatedEditorIconSpritesheetPrompt 能进入 prompt builderExternal v1 超限同步返回 400
  10. 已有静态图片的 POST /api/editor/images/pixel-art-snaps 是免费 inline 派生操作,不调用外部 provider、不创建 external_generation_job、不读写泥点 ledger,也不进入任务侧栏。免费不放宽 owner、稳定引用、输入上限、持久化或处理阶段零持久化门禁。
  11. 主站编辑器生成队列使用同一次前端请求稳定复用的 x-request-id,按 namespace + owner + job kind + request id 生成唯一 dedupe_key;首次请求已入队但响应丢失时,重试必须返回原任务。同一幂等键携带不同 payload 返回 409,不得创建第二个任务或串到旧结果。外部 v1 的 Idempotency-Key 使用独立 namespace,不能与主站请求标识碰撞。幂等 payload 比较只对本次已迁移 sanitizer 的图片生成、图片修改、去背景、图标图集和 UI 提取任务,兼容“升级前旧任务仍含客户端 generationInputs.references、当前请求已删除该字段”的单向形状;当前请求仍含 references,或 job kind 属于音频 / 视频 / 角色动作等未迁移任务时必须完整比较,其余请求字段始终完全一致。
  12. generationInputs.references 是最终资产的服务端权威行引用,不接受客户端自报 provenance。图片生成类请求入队、完美像素及直接创建资源 / 素材时删除客户端 referencesworker 和 inline 路径按本次真实参考图、当前 owner 的项目资源 / 素材记录重建 refType/refId 后再持久化。仅能证明 owned objectKey、但找不到对应资源或素材行时可以参与生成,不得制造虚假行引用;title/label 只作为展示快照,不提升为资源身份。完美像素为兼容升级前的未知结果重放,可继续用旧版 canonical 客户端输入计算 operation fingerprint;新操作持久化元数据只能使用服务端重建值。owner-scoped 项目快照发现同一 operation 的稳定 result resourceId 时,HTTP 路径必须在来源解析、OSS 下载、规整、preflight 和 PUT 前直接返回 409,携带 operationResultAlreadyExists=trueresultResourceId,客户端仅以 GET-only 项目对账判定权威结果,不复用既存 metadata 作 exact compare-and-return。

外部服务与资产

  • 已有图片完美像素化:登录态 POST /api/editor/images/pixel-art-snaps 使用 sourceImageSrc 承载 objectKey / resourceId / assetId 候选稳定引用,要求 projectId / canvasCompletioncanvasCompletion.dialogId 必须非空,并可携带 sourceResourceId / assetKind / generationInputs / assetFolderId / assetLabel;BFF 必须在下载前将候选解析为当前 owner 已登记的私有 OSS object key,并校验 project / resource / asset 归属,拒绝 data: / blob:、signed URL、普通外链和音频、视频、图片序列等非静态栅格输入。归属校验有两条等价路径:带 sourceResourceIdsourceImageSrc 能免查确认指向同一张图(本身即该 objectKey 或就是该 resourceId)时,来源资源已随 owner-scoped 项目读取完成鉴权,直接断言 resource.ownerUserIdresource.projectId 后取用其 objectKey,不再按注册 ID 做全账号项目与素材库扫描;两个字段指向不同图片必须直接拒绝而不是退回扫描。其余情况仍走完整解析。跨记录的 asset_kind 扫描随扫描一并省略,按 (bucket, objectKey) 的存储类型点查两条路径都保留,动图仍由下载后的静态编码门禁按实际字节拒绝。编码门禁只接受静态 PNG / JPEG / WebP,明确拒绝 GIF、带 acTL 的 APNG 及带动画标志 / ANIM / ANMF chunk 的 WebP。处理复用 platform-image 纯内存 snapper、单边 10000 与总像素 8294400 上限,并发控制分两层:端点级并发闸最大 4、等待队列上限 2048,在首次 IO 之前取得,队列满返回 503 并带 Retry-After,等待超预算返回 504;内层是与生成风格共享的进程级 CPU 并发 2。30 秒总预算从 handler 入口起算,覆盖归属校验读取、OSS 下载、两层排队与规整全过程。OSS 读写共用带 connect 10s / total 120s 的进程级 HTTP 客户端。strict 与生成风格使用完全相同的 legacy profile、峰值估算、单轴步长补全、walker、采样和编码,唯一差异是横纵两轴都未检测到步长时,不执行 min(width,height)/64 统一网格兜底而返回不适用。任一轴已检测到步长时,两条路径行为和输出必须一致。读取、解码、校验、排队、规整、PNG 编码任一步失败 / 超时 / 不适用时,在最终持久化前返回错误,OSS PUT、asset object、project resource、账号素材和画布 layer 增量都必须为零。成功结果保留源图,只对最终 PNG 做一次 OSS PUT,并至多各创建一个 editor_project_resource 和一个 editor_asset;源图已有正式 project resource 时,结果资源以 source_resource_id 关联该资源,再按 canvasCompletion 尝试写入一个右侧派生 layer。completion 读取的权威 dialog 已删除时沿用现有语义跳过画布写入,不得用请求中的旧 placeholder 复活图层;已经成功落库的 resource / asset 可以保留。客户端回包时若本地 dialog 已删除,不应用完成快照;现有布局 CAS 没有 deletion tombstonecompletion 先提交、删除保存后冲突的极端竞态仍按权威快照收口。客户端不得为该 unsafe POST 配置 EDITOR_REQUEST_RETRY_OPTIONS,请求字节可能已发送后不因 transport 异常或 408 / 425 / 429 / 502 / 503 / 504 自动重放;Bearer 中间件在 handler 前拒绝请求后的既有认证恢复继续保留。结果未知时先 GET 权威项目 / 素材快照。
  • 完美像素持久化边界:所有可判定的稳定引用、owner、项目、来源资源、素材类型、静态编码、元数据、网格适用性、排队、CPU、解码、规整和编码校验都必须在首个最终 PNG PUT 前完成。handler 先用纯 prepare 生成精确 object key 和候选 project resource,再调用只读 preflight_editor_pixel_art_result_and_return;preflight 校验自定义素材目录归属(尚未创建的默认目录允许通过)、复用权威 canvas completion planner,并对 legacy / structured 候选布局执行 2 MiB 总量和 512 KiB 单项门禁。preflight 与后续 PUT / HEAD / 原子 persist 共用同一份 60 秒绝对 deadlinepreflight 失败或超时不得发送 PUT,也不得附加 resultPersistenceStarted。最终 PNG 的 OSS PUT / HEAD 仍位于数据库事务外;确认上传结果后,asset_object + editor_project_resource + editor_asset + optional canvas completion 必须由 persist_editor_pixel_art_result_and_return 在一次 try_with_tx 中原子提交,handler 不得先调用 confirm_asset_object 或三个旧分段 helper。最终 procedure 必须重新校验目录、布局、幂等身份和 revision,不能把 preflight 结果当成提交凭证。preflight 不创建锁或 reservation,因此通过后若目录或画布被并发修改,最终事务仍可能在 PUT 后拒绝并留下无引用 OSS object;当前不做破坏性删除补偿或历史孤儿清理。该原子保证只覆盖本次结果事实;前置 owner-scoped 项目 / 素材读取仍可沿用既有默认 canvas / folder 懒建语义,不把整个请求声明为数据库只读。operation 以规范化 canvasCompletion.dialogId 表示并由 owner / project 限定作用域;task ID 可由前端直接推导,object / resource / asset ID 按同一 operation 稳定派生,object key 必须包含覆盖规范输入、来源 / 输出摘要与算法版本的 64 位 fingerprint。owner-scoped 项目快照发现同一 operation 的稳定 result resourceId 时,HTTP 路径必须在来源解析、OSS 下载、规整、preflight 和 PUT 前直接返回 409,携带 operationResultAlreadyExists=trueresultResourceId,并由客户端 GET-only 对账;本次请求不得附加 resultPersistenceStartedAlreadyApplied 仅在 early guard 与最终 procedure 并发相遇时作为底层幂等兜底,复用既有 commit 且不得再次执行 layout CAS 或推进 revision;同 operation 输入漂移、稳定 ID / object location 冲突或 object/resource/asset 只有部分存在时必须整笔失败关闭并映射 409,不得补写或覆盖第一次事实。权威 dialog 已删除时 object/resource/asset 仍在同一事务提交,canvas / revision 不变并返回 DialogMissing。HTTP timeout/drop 不能撤销已经发往远端的 procedure,因此首个 PUT 后仍设置 resultPersistenceStarted=true 并按稳定身份对账;该标记不再表示数据库可能部分提交。
  • 完美像素 unknown 与并发闸测试边界:上一条末句“结果未知时先 GET 权威项目 / 素材快照”的旧表述已撤回,项目 GET 才是唯一结果 verdict;素材刷新只允许在项目终态后 best-effort 触发,不能参与成功判断。无 dialog 只有同时存在匹配稳定 task 的唯一 resource 时才是 asset-only 成功,否则保持 unknown。过期预算用例只断言返回 504,不得读取进程级 EDITOR_PIXEL_ART_SNAP_QUEUE_DEPTH 的 before/afterqueue guard 的 Drop 归还由独立用例覆盖。不得用相对断言、--test-threads=1 或全局串行锁掩盖并行竞态。
  • LLM:通用 LLM 门面继续使用 GENARRATIVE_LLM_*platform-llm 文本请求默认走 Responses,旧 /api/llm/chat/completions 代理和少数旧运行态聊天显式保留 Chat Completions 兼容协议;创意 Agent gpt-5 Responses / Chat Completions 文本链路已于 2026-06 从 APIMart 迁移到 VectorEngine,使用 VECTOR_ENGINE_BASE_URL / VECTOR_ENGINE_API_KEY 构造 OpenAI-compatible clientapi-server 会把未带 /v1 的 VectorEngine base URL 规范化到 /v1 后请求 /responsesAPIMART_BASE_URL / APIMART_API_KEY 只作为历史残留,不再作为创意 Agent gpt-5 客户端来源;后续排障时优先确认 VectorEngine /v1/models/v1/chat/completions/v1/responses 可用性。
  • LLM:通用 LLM 门面继续使用 GENARRATIVE_LLM_*;创意 Agent gpt-5.4-mini Chat Completions 文本链路已于 2026-06 从 APIMart 迁移到 VectorEngine,使用 VECTOR_ENGINE_BASE_URL / VECTOR_ENGINE_API_KEY 构造 OpenAI-compatible clientapi-server 会把未带 /v1 的 VectorEngine base URL 规范化到 /v1 后请求 /chat/completions。通用 /api/llm/chat/completions 代理使用 GENARRATIVE_LLM_PROVIDER=openai-compatibleGENARRATIVE_LLM_BASE_URL=https://api.vectorengine.cn/v1GENARRATIVE_LLM_MODEL=gpt-5.4-mini;未单独配置 GENARRATIVE_LLM_API_KEY 时可复用 VECTOR_ENGINE_API_KEYAPIMART_BASE_URL / APIMART_API_KEY 只作为历史残留,不再作为创意 Agent gpt-5.4-mini 客户端来源;后续排障时优先确认 VectorEngine /v1/models/v1/chat/completions/v1/responses 可用性。

平台适配器:platform-llm 公共能力与三协议工具契约

platform-llm 的统一公共抽象为 LlmRunRequest / LlmRunResponseOpenAiChatOpenAiResponsesAnthropic 三种 API kind 都支持原生 function tools;工具调用统一从最终 LlmRunResponse.tool_calls 返回,不能再按 Anthropic 与否切换到提示词驱动的 text JSON wrapper。

API kind 请求工具形态 tool_choice 非流式响应 流式事件与 slot
OpenAiChat tools[].type=function,函数字段为 name / description / parameters / strict "auto" / "required" choices[0].message.tool_calls delta.tool_calls[].index
OpenAiResponses tools[].type=function,函数字段为 name / description / parameters / strict "auto" / "required" output[].type=function_call output_indexresponse.completed / response.incompleteresponse.output[] 可作为仅有终态事件时的兜底
Anthropic 顶层 tools[]name / description / input_schema,无 function 包装层和 strict { "type": "auto" } / { "type": "any" }Required → any content[].type=tool_useinput 序列化为 arguments content block indexcontent_block_start + input_json_delta

流式 on_delta 只发送文本增量、累计文本和完成原因,工具调用不进入回调。平台层按协议 slot 聚合并行工具片段:Chat 使用 delta.tool_calls[].indexResponses 使用 output_indexAnthropic 使用 content block index。Responses 的 function_call_arguments.doneresponse.completedresponse.incomplete 中的完整 arguments 覆盖此前分片;仅有终态事件时的恢复以 response.output[] 数组下标作为 slot。该聚合只负责解析,不表示工具执行并发。

工具调用归一只有一份策略,流式与非流式、三种协议共用:协议层只把各自 DTO 映射成统一中间形态,接受与否全部由归一层判定。被识别为工具调用(Chat 的 tool_calls[] 成员、Responses 的 type=function_call、Anthropic 的 type=tool_use)后,字段不全一律返回 Deserialize,不得静默丢弃。 缺少 id 或函数名报错;arguments 缺省或空白归一为 {}(零参函数合法)。

arguments 是否必须是完整 JSON 按流式与非流式区分,两者的“参数不完整”语义不同:流式意味着流被截断,是传输层事实,平台层必须返回 Deserialize;非流式的外层 body 已经完整,参数半截只说明模型输出有问题,属于内容层事实,必须原样透传给调用方。平台层不得在非流式路径拦截——调用方的工具计划格式修复循环要靠 call id、函数名和原始畸形参数把响应回灌给模型重写,这比硬报错再重跑整轮 Provider 有效得多;在平台层报错会把这三样信息一起丢掉。无论哪种,这都只是 JSON 语法完整性检查,不是按工具 parameters 执行 JSON Schema 校验。

静默丢弃是明确禁止的实现方式:它会把“上游给了工具调用但我们没解出来”伪装成“上游只回了正文”——响应同时带解说文本时更会被当作普通回复成功返回,而带 tool_choice=required 的请求随后退化为格式修复循环,审计里只能看到“模型没按协议调用工具”,看不出真正成因在解析层。非流式 DTO 为兼容流式分片把字段改成可选后尤其要注意:可选字段解除了 serde 的强制校验,缺失必须在归一层重新拦截。

工具调用还必须来自没有被上游宣告为未完成的响应。上游给出明确的截断 / 过滤 / 失败终态时,即使参数恰好闭合成合法 JSON 也必须返回 Deserialize:字节完整不代表模型把本轮工具计划表达完了,而下游拿到 tool_calls 就会真的去执行,格式修复循环对"参数合法但内容被砍断"完全无从察觉。已知终态为——Chat 的 length / content_filterResponses 的 incomplete / failed / cancelledAnthropic 的 max_tokens / pause_turn / refusal。这里必须用黑名单而非白名单,未知值与缺失一律放行,否则会误杀不发或自定义该字段的兼容网关。该检查只在存在工具调用时生效:正文被 max_tokens 截断仍是可用的降级结果,一并拒绝会打死所有触及输出上限的长文本回答。与非流式畸形参数透传同时成立时,本检查优先。

流式工具调用必须来自已收尾的流:只要聚合出过工具 slot,收尾时就必须已观察到本协议的完成信号,否则按截断返回 Deserialize。完成信号按协议判定——Chat 为非空 choices[].finish_reasondata: [DONE]Responses 为 response.completedresponse.incompleteAnthropic 为带 stop_reasonmessage_deltamessage_stop。不能用 data: [DONE] 作为统一判据:MiniMax 兼容层不发该标记,只发 finish_reason。也不能只用 “参数是合法 JSON” 当完成证明——顶层花括号闭合只说明单个参数对象字节完整,说明不了模型是否还要发下一个工具块,更说明不了上游随后会不会报 max_tokens 或 error;代理超时、网关自行掐断和 HTTP/2 提前 END_STREAM 都表现为干净 EOF,与正常收尾在字节层无法区分。该门禁当前只覆盖工具路径;纯文本响应缺完成信号仍按成功返回并打 warn,改动前必须先确认所有在用网关的文本收尾行为。流在任何工具分片到达前就断掉时槽位为空,门禁无从触发,这是已知残留缺口。

各协议的最终事件必须同时终止读取循环,不能只标记完成:Chat 的 data: [DONE]、Responses 的 response.completedresponse.incomplete、Anthropic 的 message_stop 都置终止位。Responses 的整体收尾信号有两个——撞到 max_output_tokens 时上游只发 response.incomplete、不发 response.completed(真实端点抓包确认),其载荷与 completed 同构,同样带完整 output[]item 上标 status=incompleteincomplete_details.reason 给出原因。漏掉它会同时造成三件事:不终止读取循环、流式 Responses 永远产生不出 incomplete 这个 finish_reason(上面那条截断拒绝规则对它形同虚设)、只在整体终态事件中携带的工具调用被静默丢掉。服务端在最终事件后保持连接(keep-alive、SSE 网关不主动关流)时,只标记完成会让读取一路等到调用方超时。

Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复源,两者必须对称。只恢复工具会造成三种后果:纯文本的 completed-only 响应退化成 EmptyResponse(上层白跑一轮重试或降级);response.incomplete 携带的截断正文本来是可用的降级结果,同样拿不回来;“正文 + 工具调用”的响应不报错,但模型的前置说明被静默丢掉,最隐蔽。正文提取必须复用非流式那条路径(output_text 优先、output[].content[] 回退、过滤 reasoning / reasoning_content / analysis / thinking 等隐藏 part),不得另写裸 JSON 提取器——漏掉过滤层会把思维链当正文吐给调用方。终态载荷反序列化失败时按“没有快照”静默降级、不报错:这是兜底恢复路径,网关发出未建模的形状时应当退回增量累加结果;这与槽位缺失必须失败关闭的口径不同,那里放过会造成静默的身份与参数错配,这里放过只是回到没有该恢复路径时的行为。

终态正文按快照覆盖而非追加合并,且要按累加状态分两条路:累加为空时(只发终态事件的网关)必须把快照当作一次增量发出去,只覆盖累加值会让调用方的流式通道全程收不到任何文本——Responses 的 finish-only 回调开关是关闭的,只有 Chat 打开,指望终态回调兜底并不成立;累加非空且与快照一致时不补发回调,否则正文在调用方侧翻倍。覆盖语义与工具参数的 arguments_complete 一致。

快照与增量拼接结果不一致时必须补发一次回调,只改累加值不够:调用方最后收到的累计正文会停在增量结果上,而 LlmRunResponse.text 已经是完整值,两者在同一次调用里分叉。按累计正文取值的消费者会因此拿到半截回复——流式请求返回 Err 时的抢救路径正是这样取值的,而“incomplete 且带工具调用”会被截断拒绝规则判为 Err,恰好走到那里。补发时的增量字段按三种情形取值:累加去空白后为空时给整个快照(这一支不能并进前缀相减,纯空白累加值匹配不上前缀会退化成空增量,反而让按增量累加的消费者丢内容);快照是增量的延长时给后缀;两者非前缀关系时无法表达成增量,只能给空增量、靠累计正文纠正。最后一种情形下按增量累加的消费者无法自愈,是已知残留,只能等调用方在最终回复落地时整体覆盖。该规则不改变“终态事件是否总是触发回调”——只有快照确实纠正了内容才补发,给 Responses 打开 finish-only 回调是另一个决定。

工具事件的协议槽位缺失时必须失败关闭,不得跳过也不得按事件内位置猜测:槽位是并行分片唯一的归并依据。跳过会静默丢掉整个调用——只剩一个调用时才可能被 StreamUnavailable 断言兜住,丢一半毫无察觉;Responses 的整体终态原因是 completed / incomplete,也不会触发只识别 tool_use / tool_calls 的那道断言。猜测则会把两个不同调用合并成一个混合体(后者的 id / name 覆盖前者,arguments 被拼接)。判定字段为 Chat 的 delta.tool_calls[].index、Responses 的 output_index、Anthropic 的 content block index。该约束只覆盖工具事件,纯文本增量不依赖槽位,不受影响。

槽位存在但被两个不同调用共用时同样必须失败关闭:同一槽位的 id 与函数名只允许从缺失变为已知重复同一个值,出现互不相同的非空值即返回 Deserialize。“覆盖身份、追加参数”并不自洽——前一个调用参数为空时拼接结果就是后一个调用的合法 JSON,参数完整性检查兜不住,调用方只会拿到后一个工具,前一个静默消失;Responses 的权威完整参数还会整段覆盖,产出“前一个调用的身份配后一个调用的参数”。两者都会原样交给 Runtime 执行。已知触发路径有两条:兼容网关把 Chat 的 index 恒置 0,以及 Responses 的 response.completed 回退按 output[] 下标重建槽位时与流式 output_index 基准错位(例如 completed 载荷省略 reasoning item)。id 必须与函数名一同参与判定——并行调用同一个工具是最常见的并行场景,此时函数名相同,只有 id 能区分。槽位是传输层的归并键,只在单次响应内有意义;call id 是跨轮次的关联身份。两者不可互换——不得一律按 id 归并。同一个非空 id 落到两个槽位有两种成因,处置相反:一是终态兜底造成的槽位基准错位(快照没有 output_index,只能按 output[] 数组下标重建槽位,网关若在快照里省掉此前占用过某个 output_index 的 reasoning / message 条目就会与增量事件错位),必须按 id 归位到已有槽位;二是上游自己重复使用了 call id,两次宣告本就是两次调用,必须原样保留。判据是该分片是否来自终态快照,而不是「是否跨事件」:只有快照是对已宣告调用的重述,才有重绑的正当性,增量宣告(output_item.added / content_block_start)永远是新调用。用「跨事件」当判据会漏掉成因二的跨事件形态——两次增量宣告用同一个 id 时,第二次被重绑走,它自己的参数事件随后落到没有身份的空槽位上,最终报出「缺少 id」这种指错方向的错误。快照内部自身重复的 id 同样要排除出归并。平台层不承担 call id 唯一性判定:重复 id 原样透传,与非流式路径一致,由调用方统一拒绝;在流式侧擅自合并会静默丢掉一次调用、绕过调用方的唯一性校验,并让两条路径的契约分叉。按新槽位新建会产出两条 id 完全相同的重复调用,而且因为落进的是空槽位、同槽位冲突检测根本不触发,全程无告警;下游按 id 唯一性校验的消费方会把这种合法响应误判成协议错误并空耗格式修复配额,不做该校验的消费方则会重复执行同一个工具。按 id 归位不放松身份校验——并进已有槽位后函数名不一致仍照常失败关闭。空白身份按缺失跳过、不算冲突:部分兼容网关在续传分片里回发完整 function 对象且 name / id 为空串,按“不等即冲突”会把它们整批误杀,这也与归一层的空白即缺失约定一致。

工具参数字段必须区分“缺失”与“类型非法”:字段不存在或为 null 是合法缺省(零参函数),存在但不是字符串一律返回 Deserialize。把两者混同的写法(as_str 遇到非字符串返回 None)会让参数被当成缺省,归一层再补成 {},于是一个身份完整、参数是合法 JSON 的调用直接交给下游执行,既有校验全都拦不住——零参函数的 {} 与“参数类型错了所以变成 {}”在归一层无法区分。涉及 Responses 的 response.function_call_arguments.deltadelta.donearguments、终态载荷 output[].arguments,以及 Anthropic input_json_deltapartial_json;Chat 走强类型 DTO,同样输入本就反序列化失败,本规则是把三协议口径拉齐。该约束只覆盖参数字段:id 与函数名即使类型不对也只会退化成缺失,随后被归一层按缺 id / 缺函数名拒绝,本来就是失败关闭。

反过来,已经收尾的流遇到尾部传输 / 解析错误时必须保留结果,不能重跑 Provider。判断“有没有值得保留的东西”要看正文或工具调用任一非空,不能只看正文——纯工具调用响应的正文本来就是空的(MiniMax 的 Anthropic 工具流恒定如此),只看正文会让这类响应每次都被丢弃,白白多跑一轮往返。保留的安全性由“协议完成信号已到 + finish_reason 已到 + 工具参数完整 + 错误属可容忍尾部错误”共同保证,与正常路径判据一致。Chat 的非空 finish_reason 与 Anthropic 带 stop_reasonmessage_delta 会标记完成但不直接终止读取,因此在后续终止事件缺失时仍可能进入这条尾部保留路径;Chat 的 [DONE]、Anthropic 的 message_stop 以及 Responses 的 response.completed / response.incomplete 已经直接终止读取。

错误边界固定如下:StreamUnavailable 只表示流式响应已给出 tool_use / tool_calls 完成原因但没有聚合出任何工具 slot,供调用方回退非流式,它不承担截断语义;EmptyResponse 表示最终文本和工具调用都为空,纯工具响应合法;Deserialize 覆盖 JSON / SSE / UTF-8 解析失败、缺少 choices[0]、流式工具身份缺失、流式工具槽位身份冲突、流式参数不完整,以及上述工具流未收尾截断。Anthropic 仍不支持 web_search、图片内容和纯 system 消息,必须至少有一条非 system 文本消息。

  • 图片生成:VectorEngine 图片 provider 归属 platform-image,密钥只在后端环境变量中;逻辑 SKU 与 provider 首选模型均固定为 gpt-image-2,只在明确模型不可用、408 / 非拒绝类 429 / 5xx、响应解析失败或非拒绝类缺图时切换兜底模型 gpt-image-2-c。401 / 403、普通参数或安全拒绝、本地配置 / 参考图错误、无法确认上游是否已受理的发送错误、request budget 耗尽和生成成功后的图片下载失败不得切模型。一次业务请求总发送上限仍为 5 次;切换兜底模型会消耗后续 attempt,不允许两个模型各重试 5 次。api-server 内的 openai_image_generation.rs 只是兼容调用面和外部失败审计桥接,不再承载 provider 协议实现。实际外部生成运行记录统一落 tracking_eventevent_key = external_generation_runmetadata 记录开始 / 结束时间、耗时、状态、成功标记、失败原因、provider task id、结果摘要和 recovered failure 数量;首选模型失败但兜底模型成功时,首选失败仍落 external_api_call_failure。DashScope 只按仍在使用的历史能力单独处理,不作为 GPT-image-2 兜底。VectorEngine /v1/images/generations/v1/images/edits 上游 POST 使用 libcurl 发送;reqwest 只保留给参考图 URL 下载和响应中图片 URL 下载。/v1/images/edits 的 multipart 参考图必须作为 libcurl 文件上传 part 发送,字段名为 image,实现上使用 Form::buffer(file_name, bytes) 并设置 Content-Type;不能只用 contents(...).filename(...),否则上游会把请求转码为缺少图片并返回 image is requiredrequest_send 阶段的 curl timeout / connect error 按可重试传输错误处理,最多尝试 5 次,并使用指数退避加短抖动;排障时优先看 attemptmax_attemptsretry_delay_msfallback_from_modelfallback_to_modelreference_image_bytes_totalrequest_params,不要把 SendRequest 当成上游业务错误。
  • 抠图输入以私有 OSS 作为内存生命周期边界:生成原图和角色动作抽取帧上传时消费图片字节所有权,上传完成后不保留原图缓冲;手动去背景直接解析并校验已有 OSS object key,不下载原图。BgFilter 必须为 object key 签发 600 秒 GET URL 并通过 multipart image_url 提交,不用 file 重传;flat 链路进入阿里云 fallback 时由 platform-matting URL 接口单独下载并上传 AuthorizeFileUpload 临时对象,在推理前释放下载缓冲,继续 fallback 到本地键色时再单独下载一次原图,本地产出后释放本次原图下载缓冲。签名 URL 不得写入日志、审计或持久化。
  • 角色动作抠图输入像素边界:仅图片画布角色动作链路在 FFmpeg 抽帧后、源帧上传 OSS 前,把帧解码为 RGB8,并按最终 frameWidth × frameHeight 的 contain 比例使用 Triangle 只缩放到内容尺寸;该阶段不得创建最终目标尺寸画布、不得引入 Alpha 通道,也不得插入任何 padding。BgFilter、阿里云通用抠图和本地键色降级共享这个无补边源帧 object key。抠图返回后才统一转为 RGBA8,按相同比例居中放入最终目标尺寸画布,并用 RGBA(0,0,0,0) 补齐透明 padding。以 560×752 → 323×480 为例,抠图输入固定为无 Alpha、无补边的 323×434 RGB8 PNG,最终输出为上下各 23px 透明补边的 323×480 RGBA8 PNG。旧 /api/assets/character-animation/* 动作发布链路继续保留原有帧 finalizer,不适用该输入规则。抽帧解码后若携带 Alpha 通道,必须先把像素按白底合成为不透明再转 RGB8,禁止直接丢弃 Alpha——全透明像素下未定义的 RGB 值会以杂色进入抠图输入,重新引入杂色边缘;共享 FFmpeg 抽帧命令保持不固定 -pix_fmt,白底合成只属于该链路的 BgFilter 输入准备阶段。
  • 阿里云通用抠图的非上海地域输入不得使用 viapiutils/GetOssStsToken、固定 viapi-customer-temp 或 OSS V1 PUT。platform-matting 必须按官方新版 SDK Advance 协议调用 AuthorizeFileUpload,使用动态返回的单对象 Policy 执行 multipart POST,再把临时上海 OSS URL 交给 SegmentCommonImage;输入归一化、结果下载与原尺寸 Alpha 回贴继续留在同一适配器内。该协议仍上传图片字节,不等同于阿里云服务端直接抓取任意公网 URL,也不改变上层 BgFilter → 阿里云 → 本地降级顺序。
  • 编辑器抠图服务:手动 POST /api/editor/images/background-removals 与角色形象生成、图标 spritesheet 生成、UI 设计图素材提取、角色动作抽帧后的透明化统一通过唯一 loopback bgfilter-worker 调用 BgFilter provider。provider 配置继续使用 GENARRATIVE_EDITOR_BGFILTER_BASE_URLGENARRATIVE_EDITOR_BGFILTER_TOKEN,默认 base URL 为 http://58.87.105.82/bgfilter;单次 provider attempt 上限不再独立配置,由公式 N × est × 2 运行时派生,其中 est = GENARRATIVE_EDITOR_BGFILTER_SINGLE_IMAGE_ESTIMATE_MS(默认 5000,依据为服务端高并发单图处理约 1-3s、网络约 3-5s),旧 GENARRATIVE_EDITOR_BGFILTER_REQUEST_TIMEOUT_MS 已删除;旧 GENARRATIVE_EDITOR_BACKGROUND_REMOVAL_TOKEN 只作为 provider token 的兼容回退别名,原手动去背景专用 base URL / timeout 配置已经删除。父流程先把候选 objectKeyresourceIdassetId 解析为当前 owner 已登记的私有 OSS object keyBFF 入队前统一拒绝 data: / blob:,底层 resolver 在解析引用前再次拒绝内联媒体并完成登记状态与 owner 校验。父流程只通过一次内部 HTTP RPC 传递 object key、排队预算 maxQueueWaitMs、调用预算 callBudgetMs 与模式参数,不传图片字节或签名 URL,并同步等待子 worker 返回的受限图片二进制 body。子 worker 在每次真实 provider attempt 前签发短期 OSS URL,承担 admission 保险丝 Q(默认 2048,仅防连接风暴)、provider 并发 N(生产 16);排队 deadline 从 Q admission 时刻起算,完成 JSON 校验并进入 provider permit 等待队列时再取得队长快照,按 min((队长+5)×est×2, maxQueueWaitMs) 约束排队等待。子 worker 还负责严格最多两次顺序 attempt、结果校验和按 flat / complex 隔离的进程级熔断;两种模式共享 GENARRATIVE_EDITOR_BGFILTER_CIRCUIT_FAILURE_THRESHOLD=3GENARRATIVE_EDITOR_BGFILTER_CIRCUIT_COOLDOWN_SECONDS=120 默认值,但失败和成功只更新当前模式,且只由子 worker 读写。手动去背景固定使用 background_mode=complexseg_model=birefnetcross_check=off,不传 filescreen_colorcomplex provider 失败累计自身熔断,任意失败或自身熔断都直接返回父流程失败,不接 flat fallback,也不影响 flat 熔断。标准纯色背景四条链路固定使用 background_mode=flatscreen_color=<screenColor>seg_model=<segModel>cross_check=<on|off>,其中角色形象生成、图标 spritesheet 生成和角色动作逐帧去背传 cross_check=onUI 设计图素材提取传 cross_check=off。角色、图标 spritesheet 和 UI 设计图素材提取的同源画布请求不展示抠图模型选择,但自动提交默认 segModel=birefnetapi-server 负责 allowlist 校验并在字段缺失时回落默认值,角色动作逐帧去背的 seg_model 则由后端固定。background_modecross_check 只存在于 api-server 到 worker 的内部 RPC;请求中的 segModel 不进入 generationInputs、普通用户响应、搜索、详情、导出或错误详情,External OpenAPI 是否接受该字段按独立契约决定。父侧不重试已建立连接的内部 RPC,仅对 TCP 连接从未建立的失败按父预算有界退避重试(跨过 worker 重启与开机排序窗口,收到任何 HTTP 响应即停止);flat 两次 provider attempt 失败、熔断、overload、内部 deadline 或断连后,只要父业务预算仍有效,父流程才继续“阿里云通用抠图 → 本地 editor_green_screen 键色扣除”,熔断期不得直接退化到本地兜底。角色动作视频生成的背景色已与生图链路统一:screenColor=auto 时由视觉 LLMgpt-5-miniResponses 协议、low 推理档)读源角色图自动决策,并经硬过滤器剔除与前景 / 皮肤撞色的候选,手动 hex 则尊重用户选择;透明源角色图在提交 Ark 图生视频前先合成到选定背景色实色,使视频背景等于抠图键色;抽帧后每帧先上传私有 OSS 并释放原帧缓冲,再以 object key 固定使用 seg_model=birefnetcross_check=on 进入上述三段式链路。阿里云通用抠图配置为 GENARRATIVE_ALIYUN_MATTING_ENABLEDGENARRATIVE_ALIYUN_MATTING_ENDPOINTGENARRATIVE_ALIYUN_MATTING_ACCESS_KEY_IDGENARRATIVE_ALIYUN_MATTING_ACCESS_KEY_SECRETGENARRATIVE_ALIYUN_MATTING_REQUEST_TIMEOUT_MS;未配置专用 AK/SK 时可复用 ALIBABA_CLOUD_ACCESS_KEY_ID / ALIBABA_CLOUD_ACCESS_KEY_SECRET,默认 endpoint 为 imageseg.cn-shanghai.aliyuncs.com。标准纯色背景链路中,子 worker 已发出的 BgFilter provider 失败(含被剩余预算截短后发生的 timeout 与 response 阶段超时,这类失败不计入熔断但仍是审计候选)由进程级 1024 个审计任务硬上限保护,获准任务写入共享 tracking outbox 根目录下独立的 bgfilter-worker/ 子目录并批量落库;满载、outbox 缺失、达到磁盘保护阈值或写盘失败时允许丢弃并记录指标,不回退逐条同步直写 SpacetimeDB。父侧阿里云抠图链路已开始后的失败(包括源 OSS GET 成功后的解码、尺寸校验和归一化失败)继续按通用外部 API 审计策略处理。真正开始外部调用前的本地预检不写该审计,并在 failureStage 中保留 source_decodesource_validate 等阶段。成功图片字节返回后,最终 Alpha / 尺寸恢复、OSS / asset object、画布写回、计费和父任务终态仍全部由父流程负责。
  • 请求与可见性边界:同源画布 BFF 的角色、图标 spritesheet 和 UI 设计图素材提取 request DTO 保留 segModel;前端只自动提交默认值,不提供用户选择控件。api-server 对该字段执行 allowlist 校验并提供缺省回退,再按链路固定 background_mode=flatcross_check 后调用 loopback worker。普通用户与 External API 的响应、资源 read model、generationInputs、搜索、详情、导出和错误文本均不返回该请求控制字段或实际抠图模型;后台管理与服务端 raw 审计仍保留实际执行信息。
  • BgFilter 连接复用、超时与动作帧流水线:AppState 分别复用父侧内部 worker HTTP Client 和子 worker 专用 BgFilter provider HTTP Client;父侧对一次逻辑调用至多让 worker 接收一次内部 RPC,不重试已建立连接后的失败;仅 TCP 连接从未建立时(worker 重启 / 开机排序窗口)按每轮重算 maxQueueWaitMs 的有界退避序列重连——增加的只是连接尝试次数,不产生第二次被接收的 RPC。重连配额按本进程是否已连通过 worker 分档:首连前(冷启动)flat 22.5s / complex 约 62.5s,首连后 flat ≤1.5s / complex 22.5s;每次重连计 bgfilter_internal_connect_retry_total 指标。子 worker 在同一个 N permit 内严格最多执行两次顺序 provider attempt。唯一子 worker 使用 GENARRATIVE_BGFILTER_WORKER_CONCURRENCY=N(生产 16)限制真实 provider 在途数;GENARRATIVE_BGFILTER_WORKER_MAX_REQUESTS=Q 降级为可选 admission 保险丝(默认 2048,仅防连接风暴,显式配置时必须 >= N)。超时全部由 Nest 运行时派生:单 attempt 上限 N × est × 2、调用预算 callBudgetMs = 2 × attempt + 1s(自取得 N permit 起算)、排队等待受 min((provider 等待队列队长+5)×est×2, maxQueueWaitMs) 双重上界(动态项充当自适应过载探测,超时带 bound = estimate | parent 标记),排队不侵蚀调用预算;Nest 必须同放共享 API 基础环境;请求携带的 callBudgetMs 只是父侧配置指纹,worker 比对后不一致只告警并计 bgfilter_internal_call_budget_drift_total 指标、始终以本进程公式值执行——发布调优 N / est 的新旧进程共存窗口不得误伤在途任务,持久漂移由部署脚本共享 env 对齐校验在启动前拦截。角色动作不再增加 2000ms × 本次实际帧数32 / 40 / 48 帧使用相同公式。父侧按剩余绝对预算派生 maxQueueWaitMsflat 扣除 39s 父侧预留(37s fallback + 2s 传输窗),complex 只留 2s 传输窗;<= 0 时不发请求直接降级 / 失败),client timeout 取 maxQueueWaitMs + callBudgetMs + 2s;每次 attempt 前重新签发短期 OSS URL,剩余时间不足时不开始新的 attempt。父侧成功响应解码槽 P = 8。角色动作继续以 buffer_unordered(frame_count.max(1)) 将全部单帧逻辑调用加入无序在途集合;返回结果携带原始帧序并在最终 collect / drain 全部已提交 Future 后排序,任一帧最终失败时必须先排空全部已启动 Future,再让整个动作任务失败退款,不能发布缺帧动画。单帧按“绿幕源图 owned 上传 OSS 并释放原帧 → 以 object key 调内部 worker / 按 object key 由父侧降级 → 父侧处理透明帧并落 OSS”流水化。角色动画源帧 PUT、透明帧 PUT 和最终帧 HEAD 仍统一复用 AppState 内初始化一次的 OSS HTTP Client(连接池参数为 connect 30 秒、request 60 秒、idle 300 秒、每 host 8 个 idle 连接、TCP keepalive 60 秒),并受进程级 8 路 OSS semaphore 限制;BgFilter provider 的 N 不占该 OSS permit,阿里云和本地处理既不占 OSS permit,也不受 N / Q 限制。每个 OSS 网络 attempt 单独获取 permit,退避期间释放;PUT/HEAD 动画帧请求最多 3 次(250ms、500ms 退避),只重试无 HTTP 响应的传输错误、timeout、OSS PutObject 的 400 + RequestTimeout、PUT 400 错误体读取失败(未解析出 Code,按 timeout/transport 归类)、408、429 和 5xx。动作帧 PUT 只在 400 响应中有界读取最多 16 KiB OSS 错误 XML,并保留 Code 与响应头优先的 x-oss-request-id;错误体读取超时/断流时保留已读字节,已解析出的 Code 优先生效,未解析出 Code 则按 timeout/transport 归类重试;除 RequestTimeout 与该错误体读取失败情形外的其他 400、401/403/404、配置、URL/签名和空请求体错误不重试。最终帧 HEAD 失败只重试 HEAD,不重复 PUT。
  • Match3D 物品 sheet:关卡整图完成后走 VectorEngine /v1/images/edits multipart image,模型为 gpt-image-22K 1:1 输出 10*10 spritesheet;物品 sheet prompt 固定要求单一纯绿色 #00FF00 / RGB(0,255,0) 绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG,并把透明整图写入 itemSpritesheetImageSrc/itemSpritesheetImageObjectKey。后端优先按透明 alpha 连通域从该 sheet 识别真实素材矩形并持久化 20 个物品、每个 5 个形态;识别数量不足时才回退 10*10 固定网格。通用系列素材图集的行列索引按每行 2 个物品计算,必须落在 1..=10,难度只决定运行态加载 3 / 9 / 15 / 20 种。
  • Match3D UI spritesheet 和背景派生图:关卡整图作为参考图并发生成 1K 1:1 UI spritesheet 与 1K 9:16 背景图,模型均为 gpt-image-2。UI spritesheet prompt 固定要求单一纯绿色 #00FF00 / RGB(0,255,0) 绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;背景图必须合成为全画幅不透明 PNG。
  • Match3D 1:1 容器 UIVectorEngine /v1/images/edits multipart 参考图。该容器参考图是后端生图协议输入,必须通过 include_bytes!api-server 编译进二进制,避免 API 单独发布或运行目录缺少 public/ 时生成失败。
  • 敲木鱼敲击物和背景环境图:VectorEngine /v1/images/edits,模型固定 gpt-image-2。敲击物支持 multipart 多参考图,第一张固定为后端内嵌默认木鱼图,用户上传图只作为新主题参考;prompt 必须要求 1:1 单一纯绿色 #00FF00 / RGB(0,255,0) 绿幕背景主体图,并禁止黑底、白底、棋盘格和任何实底背景。当前敲击物和返回按钮上传 OSS 前只做服务端绿幕去背后处理,避免泛抠图误伤玉米等主体像素。背景环境图只使用第一步抠图完成后的透明敲击物图作为参考,prompt 必须要求中央主体预留区保持干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围,且必须显式声明不继承任何绿色底色、绿幕底色或纯绿色画布。
  • Hyper3D / Rodin:只保留后端安全代理和旧数据兼容;Rodin 提交、状态、下载和响应解析归属 platform-hyper3dapi-server/src/hyper3d_generation.rs 只做路由、配置和错误 envelope 映射;新 Match3D 草稿和批量新增不再生成 GLB。
  • 音频:视觉小说专用音频路由保留;VectorEngine Suno/Vidu provider 协议、任务提交/查询、音频 URL 提取、下载、MIME/extension 归一,以及 ElevenLabs SFX 单次同步二进制请求、40 MiB 有界读取、MP3 验证和 600s 技术异常上限内的实际时长探测均归属 platform-audio。ElevenLabs 直接 adapter 不进入 Suno/Vidu 的 submit + poll 枚举,OSS put 请求准备以显式 provider / file stem 描述来源。api-server/src/vector_engine_audio_generation.rs 只做路由、配置、计费、asset object confirm、entity binding 和错误 envelope 映射;SFX Worker 在该模块内以同一编排函数串联计费、翻译、ElevenLabs、OSS、项目资源 / 账号素材 / 画布写回,生产 adapter 复用正式边界、测试 adapter 只注入 mock。内部失败分类固定为 translation_invalid / translation_upstream_failed / translation_budget_exhausted / elevenlabs_http_failed / invalid_audio / duration_probe_failed / oss_failed / writeback_failed,普通用户读取边界继续返回稳定短文案。拼图、抓大鹅和敲木鱼提示词生成音效入口暂时关闭,通用 /api/creation/audio/* 对这些目标返回 410 Gone。敲木鱼创作只接收上传 / 录音音频资产;前端选择或录音阶段只在浏览器本地处理待提交音频,统一限制裁切后最长 1 秒、裁掉前后声音过小片段,并用浏览器端近似响度算法平衡到 -15 LKFS 后做峰值保护。点击生成时才直传 OSS 并确认 asset_object,创作 JSON 只提交轻量 WoodenFishAudioAsset,不得继续上传 Data URL 音频;未提供时由 api-server 写回内置默认木鱼音 /wooden-fish/default-hit-sound.mp3
  • OSS:私有 generated path 进入浏览器前必须通过 /api/assets/read-url 换签;不要裸请求 /generated-*。请求参数的安全语义不能混用:legacyPublicPath 是历史公开作品兼容口,只允许 platform_oss::LEGACY_PUBLIC_PREFIXES 中的 curated 前缀匿名换签;objectKey 是正式对象引用,绝不能复用该前缀旁路,必须查询 asset_object 并校验配置 bucket、精确 key、PublicRead 或当前 owner。External OpenAPI 的 /api/external/v1/assets/read-url 还必须有 editor:asset scope,并始终以 API Key 绑定的 owner_user_id 执行同一 owner 校验;后台跨账号预览只能走管理员鉴权后的 /admin/api/assets/read-url/api/assets/read-bytes 与主站 read-url 共用完全相同的授权,默认仍应由浏览器使用 signed URL 直读,bytes 只作跨域字节读取 fallback。前端如果收到同一 OSS bucket 的完整 https://*.oss-*.aliyuncs.com/generated-* 地址,也必须先归一为 legacy path 后走同一换签链路,避免裸连私有 bucket 403 或绕过签名缓存。OSS 签名、读签名、HEAD 和 PUT 的结构化日志由 platform-oss 输出,排查资产写入 / 确认失败时优先按 operationobject_key / key_prefixstatus_classerror_kindelapsed_ms 下钻。新上传 generated 私有对象默认写入 Cache-Control: public, max-age=31536000, immutable;旧对象若缺该头,只能依赖 ETag / Last-Modified 协商缓存,应通过 OSS 元数据刷新或 CDN 配置补齐,不要恢复 api-server 静态代理。editor-agent/ 前缀只用于服务端内部读写画布 Agent 会话消息文档,不属于浏览器直传 legacy public prefix/api/assets/direct-upload-tickets 必须拒绝 legacyPrefix=editor-agent,内部读取只允许 editor-agent/{conversationId}.json 形态。
  • 外部 API 失败审计:外部供应商调用未成功时,api-server 必须发送 OTLP 失败事件并写入 tracking_event。VectorEngine 图片 provider 在 platform-image 内输出结构化日志和 PlatformImageFailureAudit,覆盖 request_sendresponse_bodyupstream_statusresponse_parsemissing_imageimage_download 阶段;编辑器 screenColor=auto 的 gpt-5-mini 背景色决策同样必须审计每次已发出的 LLM 调用失败,包括传输 / 超时、上游拒绝、响应体解析、空响应和返回候选外颜色;即使随后降级默认背景色并继续主流程也不得只记 warning。api-server 将这些失败映射成 external_api_call_failurescope_kind = modulescope_id = providermodule_key = external-api。metadata 固定包含 provider、endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、latencyMs、promptChars、referenceImageCount、imageModel、rawExcerpt,以及在调用方可获得上下文时补充的 userId(触发者)和 profileId(草稿 / 作品 / 场景作用域)。图片生成入口应优先把 owner user id 和 profile id 透传到失败审计,不要只保留 provider 级聚合,否则很难按“谁触发、哪个作品触发”定位问题。普通调用入库优先复用 tracking outboxoutbox 不可写或保护阈值拒绝时回退同步写 SpacetimeDB;不得新增前端兜底或在 SpacetimeDB reducer 内做外部 I/O。bgfilter-worker 是受限资源例外:它使用共享 tracking outbox 基础目录下独立的 bgfilter-worker/ 子目录,provider 失败审计在 spawn 前受进程级 1024 硬上限保护并由 shutdown tracker 跟踪;满载、outbox 缺失、保护阈值拒绝或写盘失败时直接丢弃并观测,不回退同步直写 SpacetimeDB。优雅退出先排空已获准任务的 enqueue,再封存并尽力 flush;进程被强杀时只有已 enqueue 记录可在下次启动重放。
  • 外部生成运行记录:所有外部生成编排的完成态统一写入 tracking_eventevent_key = external_generation_runscope_kind = modulescope_id = providermodule_key = external-generation。metadata 固定包含 runIdprovideroperationrequestLabelrequestPayloadstatussuccessfailureReasonproviderRequestIdresultPayloadstartedAtMicroscompletedAtMicrosdurationMs。这类记录只用于运行审计和排障,不再走 ai_task 旧表。

SpacetimeDB 表目录

下列 ### 标题是 schema guard 的机器可读表目录。新增、删除或改名表时必须同步这里。

ai_result_reference

  • Rust 结构体:AiResultReference
  • 源码:server-rs/crates/spacetime-module/src/ai/stages.rs

ai_task

  • Rust 结构体:AiTask
  • 源码:server-rs/crates/spacetime-module/src/ai/tasks.rs
  • module-ai 的进程内热状态不是持久化真相:文本增量按阶段有序聚合并受单阶段 512 KiB、每阶段 8192 个 chunk 上限约束;terminal task 立即释放增量明细,内存工作集最多保留 1024 个任务。需要长期查询时必须读取 SpacetimeDB 的 ai_task / ai_task_stage 投影,不得依赖进程重启后仍存在的内存快照。
  • SpacetimeDB 的 AI 写入 procedure 必须复用同一组任务元数据、payload、文本、结构化输出、warning、失败消息和结果引用上限;流式聚合超过 512 KiB 或 8192 个 chunk 时在事务内拒绝,terminal task 收口后分批删除 ai_text_chunk 明细,只保留阶段最终快照和结果引用。

ai_task_event

  • Rust 结构体:AiTaskEvent
  • 源码:server-rs/crates/spacetime-module/src/ai/events.rs

ai_task_stage

  • Rust 结构体:AiTaskStage
  • 源码:server-rs/crates/spacetime-module/src/ai/stages.rs

external_generation_job

  • Rust 结构体:ExternalGenerationJob

  • 源码:server-rs/crates/spacetime-module/src/external_generation.rs

  • 现役覆盖:worker claim 只允许 source_module = editor-canvas;下述逐玩法生成和写回描述均为退役前历史。历史 pending / running 行继续保留原状态,不得领取、失败收口或改写 payload。

  • 用途:外部生成 worker 的内部持久任务队列;GENARRATIVE_EXTERNAL_GENERATION_MODE=queue 时,api-server HTTP 角色只入队,external-generation-worker 角色通过 claim lease 领取、续租、执行,并用 lease_token 栅栏回写阶段、完成 / 失败。队列行继续保存 worker 执行、计费与滚动发布兼容所需字段,末尾可选 phase 只取 generating / processingclaim 写 generating,真实进入抠图处理时由受 job_id + worker_id + lease_token 保护的 procedure 写 processing。phase procedure 以结构化结果区分 LeaseFencingRejectedOtherRejectedLeaseFencingRejected 立即终止,OtherRejected 以及 SDK 的 Procedure / Runtime 错误不重试,只有 Build / ConnectDropped / Timeout 在同一个 job attempt 内重试一次。该重试只重新上报 phase,不把任务写回 pending,也不重新调用 provider;编辑器 job 入队固定 max_attempts=1,第二次传输失败后任务进入 failed,不会回到 pending 或从 provider 生成起点重跑。用户可见任务列表、价格、状态、阶段、未确认终态数量和通知确认时间的正式读取事实源已经迁到 external_generation_job_summary;BFF 不得再为列表 / 详情 / acknowledge 读取该大表。拼图 compile_puzzle_draft 的前置 compile_puzzle_agent_draftgenerate_puzzle_imagesgenerate_puzzle_ui_background 的业务写回也在对应 SpacetimeDB transaction 内校验 job_id + worker_id + lease_token、job kind、owner 和 source entity,避免过期 worker 写 session / work profile;图片画布编辑器的 editor_image_generationeditor_image_editeditor_background_removaleditor_icon_spritesheet_generationeditor_ui_design_asset_extractioneditor_character_animation_generationeditor_video_generationeditor_sound_effect_generationeditor_background_music_generation 复用同一队列表。结构化 canvas 激活后,当前 worker completion 先以读取时 canvas revision 执行 CAS,并发冲突时拒绝覆盖并保留可诊断失败;目标是进一步收口为受 lease 栅栏保护的单事务幂等写入 editor_project_resource、结果 editor_canvas_layereditor_canvas_generation_dialog 终态和 canvas revision。未激活 canvas 在 2 MiB 上限内继续走 legacy editor_canvas.layers_json 兼容写回。前端只通过 BFF job 状态轮询和项目快照读取恢复完成态。GENARRATIVE_EXTERNAL_GENERATION_MODE=inline 时不创建该队列行,三个 external generation guard 字段必须同时为空才允许 api-server 受控同步写回,半空 guard 仍会拒绝。worker 成功写回业务事实后才能 complete job;业务失败态写回成功后才能 fail job,失败态未写回时保留租约等待后续重领。

  • 2026-08-06 收口覆盖:上一条用途描述中“当前先 CAS、单事务仍是目标”的旧句已作废。现役编辑器生成不再组合调用 object confirm、resource create、asset create、canvas save 和 job complete。api-server 只准备稳定候选,再经 spacetime-client 调用 persist_editor_generation_result_and_returnprocedure 在同一 try_with_tx 内写入可选 asset_object、全部 editor_project_resourceeditor_asset、可选 asset_entity_binding、可选 canvas V2 CAS、queue job 终态和 editor_generation_operation receipt。结构化 canvas 的 layer / dialog / revision 与未激活 canvas 的 legacy layers_json 仍经既有 V2 布局验证分流,前端不直接发明正式完成态。queue 首次提交在同一快照验证 owner、job kind、request fingerprint 和有效 job_id + worker_id + lease_token;统一 procedure 已完成 job 后 worker 不得再单独 complete。

  • 载荷约束:本次先对 source_module = editor-canvasrequest_payload_json / result_payload_json 实施有限大小合法 JSON、任意层级禁止 data: / blob: 的双层门禁,只保存 worker 执行必需的普通参数和已登记媒体引用。画布 Agent 来源的任务可在 result_payload_json.editor-agent-tool-call-result 中保存有界的轻量结果和已登记媒体引用,供后端按已有 externalJobId + owner_user_id 定向懒回填;其它编辑器任务保持元数据结果,并可保存有界的 warning.code/reason。其它玩法在完成各自参考图资源化之前不由本次门禁静默改变既有请求契约。该主表只供 worker claim / 执行、受控维护以及画布 Agent 的定向结果回填读取;正式用户任务列表、单任务状态、队列概览与 acknowledge 不得返回或解析这两个 payload。画布 Agent 懒回填必须经对应工具 formatter 归一为有界轻量媒体引用后写入 OSS 会话,不能把原始 payload 直接透传前端。

  • 非阻断告警:角色形象、图标图集和 UI 素材提取已保存 provider 原图、但透明背景处理最终失败时,以原图唯一主图完成任务;透明图和切片不写入画布。这个 source-only 降级只包住透明背景处理的最终失败,phase 上报、provider 原图持久化、透明处理图持久化或画布写回失败仍按任务错误传播。图标 / UI 透明图集成功但自动拆分降级时仍保留透明图集;通用 warningsliceWarning 只在「透明背景最终失败」这一条上互斥,风格归一化或像素规整产生的通用 warning 可与 sliceWarning 并存。两类成功降级都以既有 completed 状态收口,不新增状态值:source-only 的 inline / external v1 响应使用结构化 warning.code/reason,仅拆分失败的 inline / external v1 响应继续使用既有 sliceWarning.code/reason,其 reason 保留原始诊断;queue worker 才把两者归一为有界的 result_payload_json.warning:只有一条时原样保留完整 reason,两条并存时按“通用在前、拆分在后”拼接且 code 收敛为 multiple-generation-warnings(两条 code 相同则沿用原 code),不允许任何一条被丢弃;sliceWarning.reason 无论是否并存都由 worker 添加“图集已生成,但自动拆分未完成:”前缀,拼接结果最后统一做长度上界收敛。除上述画布 Agent 定向回填的轻量结果外,队列结果不保存图片、切片列表或媒体 URL。

  • 2026-07-29 收口补充:上条 source-only 的“透明背景处理最终失败”同时包含 Alpha 比例漂移超过 5%、provider 原图修复性回读失败、Alpha 回贴失败和透明图完整解码失败;三条链路共用 helper,只写已保存 provider 原图画布层,图标 / UI 固定 iconImageSrcs=[]sliceWarning=null,不得写透明图、派生资源或切片。provider 原图本身解码失败时在首次持久化前失败,不允许 512×512 元数据兜底。图标自动拆分、手动拆分与 UI 提取先在受 2 路 CPU semaphore、30 秒 / 请求 deadline 保护的 blocking prepare 中完成解码、透明化、连通域和 bounds 排序;platform 对全部原始连通域设置 4096 硬上限、用空间网格查询邻近辅助候选,并在首片 PNG 编码前同时执行 maxOutputSlices=64 与全部 padding crop 总像素预算。prepare 返回共享 RGBA + bounds 计划,api-server 再以容量 2 的有界管线按需编码、共享 HTTP client 并发 OSS PUT + HEADOSS 连接 / 单请求超时固定为 10s / 60s;手动入口在下载最大 32 MiB 来源对象前取得独立内存 admission,同一 admission 覆盖下载、prepare 到最后一片上传结束并在数据库调用前释放,CPU permit 只覆盖实际 CPU 阶段。全部对象上传验证成功后,切片的 asset_object + editor_project_resource + editor_asset + editor_asset_group_cohort 由单个受 editor generation runtime service identity 保护的 SpacetimeDB procedure 在一次 try_with_tx 中原子写入;resource / asset ID 由 owner + task + 序号稳定派生,已有同 ID 素材仅在内容完全一致时幂等复用,来源资源必须存在且与派生资源同 owner / project;上传中途失败不得写部分资源、素材或 cohort,不确定结果重放不得复制整批素材。自动超限只保留整张可信透明图并返回稳定 sliceWarning,不写切片;手动超限在首次持久化前返回 422

  • 2026-08-06 原子提交对上述 source-only / slice 条款的修正:“provider 原图已持久化”只能解释为 OSS 对象已上传并验证,不再表示 project resource / account asset 已先行入库。source-only、透明整图和成功切片必须先完成最终选择,再作为一份 prepared commit 连同 canvas/job/receipt 一次提交;未选中或失败分支不得留下正式 resource / asset 部分记录。

external_generation_job_summary

  • Rust 结构体:ExternalGenerationJobSummary
  • 源码:server-rs/crates/spacetime-module/src/external_generation.rs
  • 用途:外部生成正式任务列表的轻量投影,按 job_id 保存 owner、来源、状态、可选 phase、价格、有界错误摘要、通知确认时间、各阶段时间和入队时提取的 request_prompt,不包含 request/result payload、worker lease 或 dedupe 内部字段。错误摘要统一拒绝内联媒体并限制为 2048 字符;列表在单次 owner 扫描中同时计数并只保留请求 limit 的固定大小 top-N,不得先收集全量历史再截断。enqueue、claim、renew、phase update、complete、fail 事务同步投影;acknowledge 只更新该轻量表并写审计事件,后续主任务同步必须保留已有确认时间,禁止为了写确认时间加载 / 重写大 payload 行。BFF 的列表、状态和确认只调用 summary procedurerunning + processing 映射为“正在处理”,其它 running(含旧行 phase=None)映射为“正在生成”。历史终态任务由迁移操作员的游标分批 maintenance procedure 在压缩 payload 时同步回填摘要,正式列表不得为兼容旧数据回扫完整主表。
  • 非阻断告警:摘要字段 warning_message 是展示投影,由完成任务的轻量 result_payload_json.warning.reason 原样提取,不等同于公开 inline / external v1 的原始结构化诊断字段。complete 和历史 backfill 共用同一构建路径;历史任务按其结果载荷中已写入的 reason 快照投影,不为格式升级重写或补前缀。单 job 状态和任务列表 BFF 以 warning: string 返回该可直接展示的完整文案,不再返回结构化 code,Web 不得再次补前缀或按字符串推断告警类型。错误与告警摘要都不复制内联媒体并限制为 2048 字符。phasewarning_message 分别表示当前执行阶段和成功降级提示,不得混用;worker / BFF / Web 必须同版本协调发布,不保证滚动混部或旧 Web 缓存下的字符串语义兼容。
  • 正式读取 procedure 为 get_external_generation_job_summary_and_returnlist_external_generation_job_summaries_and_returnacknowledge_external_generation_job_summaries_and_return。历史维护 procedure 为 compact_external_generation_job_payloads_and_returnbackfill_external_generation_job_summaries_and_returnprune_external_generation_job_history_and_return,仅 migration operator 可调用;运维入口统一使用 npm run spacetime:external-generation:maintain -- ...,默认 dry-run、单批最多 25 条。B-tree cursor 选择阶段最多反序列化 limit + 1 行,apply 再按主键逐条读取选中行;怀疑存在单行异常巨型 JSON 时必须先使用 --limit 1。payload 压缩额外固定使用 source_module = editor-canvas 的复合 cursor 索引,不得静默改写其它玩法历史任务。历史清理默认使用 --prune-historysource_module = editor-canvas 和 30 天保留期;只有主任务与摘要状态一致且属于 completed / failed / cancelled、摘要已有 notification_acknowledged_at、终态时间不晚于 cutoff 的记录才是候选。apply 在同一事务内按事件 → 摘要 → 主任务顺序删除,事件不得独立清理;每次事务最多删除 256 条事件,若同一任务仍有事件则保留任务与摘要并返回同一个 next_cursor_job_id,下一次继续该任务,避免单个任务形成无界事务写集;pending / running、未确认通知、摘要缺失或状态不一致的记录永不删除。清理不触碰资产对象或钱包流水,其他 source module 必须显式指定并单独评估。

external_generation_job_event

  • Rust 结构体:ExternalGenerationJobEvent
  • 源码:server-rs/crates/spacetime-module/src/external_generation.rs
  • 用途:外部生成任务审计事件表,按 job_idowner_user_id 记录 enqueuedclaimedlease_renewedcompletedfailedacknowledged 等状态转换事实。状态转换只能由 SpacetimeDB procedure 写入,不由前端或 worker 直接改表;该表用于追溯任务生命周期和排障,不替代 external_generation_job 当前状态。
  • 保留策略:事件只会随已确认通知的终态任务由 prune_external_generation_job_history_and_return 原子删除,不支持按事件单独清理,以保持任务、摘要和审计链一致。单次事务最多删除 256 条事件;若事件未删完,任务和摘要暂不删除,维护脚本用同一个 job cursor 重试剩余事件。

ai_text_chunk

  • Rust 结构体:AiTextChunk
  • 源码:server-rs/crates/spacetime-module/src/ai/stages.rs
  • 单阶段最多保留 8192 个 chunk;聚合和终态清理均按有界批次处理,避免小 delta 堆积为无界行数或一次性 ID 列表。

analytics_date_dimension

  • Rust 结构体:AnalyticsDateDimension
  • 源码:server-rs/crates/spacetime-module/src/runtime/analytics_date_dimension.rs

asset_entity_binding

  • Rust 结构体:AssetEntityBinding
  • 源码:server-rs/crates/spacetime-module/src/asset_metadata/bindings.rs
  • 说明:已确认 asset_object 到具体业务实体槽位的正式绑定。编辑器生成结果的可选 binding 必须与同 slot 的 object、resource / asset 实体、owner 和 asset_kind 一致,并与 object/resource/asset/canvas/job/durable receipt 在 persist_editor_generation_result_and_return 的同一事务中写入。重放必须读回原 binding 做完整比较,不得触发第二次 binding changed 事件或替换既有槽位。

asset_event

  • Rust 结构体:AssetEvent
  • 源码:server-rs/crates/spacetime-module/src/asset_metadata/objects.rs

asset_object

  • Rust 结构体:AssetObject
  • 源码:server-rs/crates/spacetime-module/src/asset_metadata/objects.rs
  • 说明:对象 metadata 以 bucket / key 标识正式对象及其 owner、访问策略。确认接口的 owner 必须来自登录会话、后台管理员会话或 External API Key 绑定的认证主体,不接受请求体指定 owner;同 bucket / key 首次登记后,重复 confirm 不得改变 owner。已登记对象默认并继续保持 private。API 通过 get_asset_object_by_location_and_return / get_asset_object_by_id_and_return 在服务端索引上权威查询 private table;资产读取 ACL 使用 get_asset_read_access_by_location_and_return 在同一事务快照内同时返回位置查询、现役 editor_showcase_asset 精选素材派生授权和当前已启用 editor_showcase_campaign_config 的活动卡专用目录 exact-key 派生授权,procedure 只允许 runtime service identity 调用。旧创作模板作品授权 view 和逐玩法资产采集器已经退出 module;spacetime-client 不订阅全量 asset_object,也不把连接级 cache 当成安全判断。位置查询发现重复 bucket / key 时失败关闭,不能任选一条继续授权。普通对象只有权威位置查询返回不存在且显式使用 curated legacyPublicPath 时才能进入白名单兼容;历史活动卡上传缺少 metadata 时,可由“global 配置已启用 + image_object_key 精确匹配 + key 位于 generated-character-drafts/editor/showcase-campaign/”这一窄授权继续换签,禁用或替换活动卡后旧 key 立即失效。新上传活动卡仍必须完成 asset_object confirm,不能把该历史兼容当作跳过正式登记的常规路径。
  • 编辑器生成结果例外不允许先单独 confirm 再分段创建业务记录:OSS PUT / HEAD 仍在事务外,但 AssetObjectUpsertInput 必须交由统一结果 procedure 在同一事务内与 resource、asset、binding、canvas、job 和 receipt 原子写入。候选省略 object 时,procedure 必须在同一事务快照中验证已登记 object 的 owner、位置和媒体身份;HEAD 成功不能代替正式登记。

SpacetimeDB viewpublic_work_asset_read_grant

  • 状态:已退役,不再注册到 spacetime-module,也不再生成客户端绑定。
  • 历史源码:server-rs/crates/spacetime-module/src/public_asset_access.rs,仅供追溯,不参与现役 crate 根。
  • 现役替代:资产读取 procedure 只计算 editor_showcase_asset 精选素材的精确授权;历史作品表继续作为数据壳保留,但不再导出公开作品资产授权。

auth_identity

  • Rust 结构体:AuthIdentity
  • 源码:server-rs/crates/spacetime-module/src/auth/tables.rs
  • 职责:只表达登录入口身份键到 user_account.user_id 的绑定;provider_uid 保存手机号 E.164 或微信 openidprovider_union_id 保存微信 unionid。账户资料以 user_account.phone_number_e164user_account.display_nameuser_account.avatar_url 为准,auth_identity.phone_e164 / display_name / avatar_url 仅保留旧行兼容,不再作为写入或恢复真相。

auth_store_projection_meta

启动投影恢复会对过滤后的 retained refresh session 重新计数;超过 8192 条时直接失败关闭并继续重试,不得把超限快照一次性灌入内存。

  • Rust 结构体:AuthStoreProjectionMeta
  • 源码:server-rs/crates/spacetime-module/src/auth/tables.rs
  • 职责:保存 typed 认证投影的单调版本,以及短期手机号验证码和微信 OAuth state 的序列化投影;phone_codes_json / wechat_states_json 只承载短期认证状态,不替代 user_accountauth_identityrefresh_session 的正式表语义。

认证恢复策略:api-server 启动时从 SpacetimeDB 正式认证表(user_account / auth_identity / refresh_session)以及 auth_store_projection_meta 中的短期状态投影导出 typed AuthStoreProjectionView,再恢复 module-auth 的进程内认证工作集;生产 Bearer 中间件不再从 InMemoryAuthStore 读取用户或会话,而是每次通过 typed validate_auth_session procedure 在 SpacetimeDB 事务内校验 token_version、会话归属、撤销时间和过期时间,SpacetimeDB 不可用时 fail closed 返回服务错误。validate_auth_session、投影导出和投影同步均从 ctx.sender() 派生调用方,并复用现役 runtime service identity 白名单;启动恢复先完成该服务身份初始化,普通 SpacetimeDB identity 不能读取或改写私有认证表。测试构建仍可使用显式的内存测试夹具。所有会读取或变更本机认证工作集的认证主链路(登录、刷新、/me、会话管理、密码、绑定和微信 state)在领域操作前先从正式投影做一次受 CAS 保护的只读刷新,刷新失败时 fail closedrefresh cookie 仍只按正式 refresh_session 校验,其他认证数据也不得绕过正式同步。module-auth 只保留内存工作集和 projection 导入 / 导出能力,不再保留 JSON 快照导入 / 导出能力,也不写本地持久化文件;auth-store.json / GENARRATIVE_AUTH_STORE_PATH 不再是兼容恢复源。认证创建、登录会话、刷新、退出、改密、重置密码、绑定和资料变更等写操作仍必须在返回客户端前通过 sync_auth_store_projection 成功同步 SpacetimeDB 正式认证表;同步失败时接口返回错误,不允许把只存在于当前进程内存的账号、会话、短信验证码或微信 state 当成成功结果。每个 API 工作集绑定启动恢复或上次成功同步得到的 auth_store_projection_meta.updated_at 版本作为 base_updated_at_microsSpacetimeDB 在同一事务内执行基线 CAS,并要求新的 updated_at_micros 严格递增;基线不一致或版本不晚于当前值时整包写入失败,冲突节点只有在确认本次同步尝试期间没有新的本地认证变更后,才可丢弃失败工作集并从正式表恢复,不能用陈旧工作集删除、恢复或覆盖另一节点的新状态;若同期仍有本地变更则保留 pending revision,并由后续认证请求先重试同步,不把临时数据库故障变成永久卡死;同步成功但期间又出现新本地变更时最多连续补同步三轮,仍未稳定则失败关闭。这只是迁移期并发保护,不改变正式认证表的权威地位。新用户注册奖励、邀请码绑定和登录埋点必须排在认证同步成功之后,避免认证没落库时先写出钱包或邀请关系。若启动恢复阶段 SpacetimeDB 不可连接或超时,api-server 会按固定间隔持续重试认证工作集恢复,恢复成功后才开始监听 HTTP,避免一次短超时让进程永久停留在依赖不可用状态。 认证工作集容量限制:refresh session 最多保留 8192 条,短信验证码最多保留 4096 条;写入前清理过期项,达到上限时拒绝新增而不继续膨胀。

auth_store_snapshot 表和旧 import_auth_store_snapshot_json / export_auth_store_snapshot_from_tables procedure 已删除。认证投影同步只读写 user_accountauth_identityrefresh_sessionauth_store_projection_metaauth_identity 不再写 phone_e164display_nameavatar_url,这些账号资料只以 user_account 为准。api-server 多节点必须使用相同的部署级验证码哈希盐(当前复用 GENARRATIVE_JWT_SECRET);轮换该 secret 会使尚未消费的短信验证码失效,但不会改变已持久化账号或 session。

短期状态的并发保护:发短信前先从正式投影刷新工作集,再写入不可消费的占位验证码并通过 sync_auth_store_projection 的基线 CAS 占用手机号 / 场景冷却窗口;只有占用成功后才调用外部短信 provider,provider 成功后再同步真实验证码哈希。微信 OAuth state 在 module-auth 工作集内限制活动数量,超过上限直接拒绝创建,避免单行 JSON 投影无界增长;过期 state 仍由投影导出时清理。

bark_battle_draft_config

  • Rust 结构体:BarkBattleDraftConfigRow
  • 源码:server-rs/crates/spacetime-module/src/bark_battle/tables.rs

bark_battle_leaderboard_entry

  • Rust 结构体:BarkBattleLeaderboardEntryRow
  • 源码:server-rs/crates/spacetime-module/src/bark_battle/tables.rs

bark_battle_personal_best_projection

  • Rust 结构体:BarkBattlePersonalBestProjectionRow
  • 源码:server-rs/crates/spacetime-module/src/bark_battle/tables.rs

bark_battle_published_config

  • Rust 结构体:BarkBattlePublishedConfigRow
  • 源码:server-rs/crates/spacetime-module/src/bark_battle/tables.rs
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

编辑器角色动作素材持久化契约(2026-07-28)

  • asset_kind 是资源 / 素材可选的权威语义类别,不新增或返回并列的 media_type / mediaType。普通静态图片的 asset_kind 为空;角色动作预览 MP4 使用 asset_kind = video,最终透明帧集使用 asset_kind = character-animation;前端据此派生具体渲染器。
  • editor_project_resourceeditor_asset 表尾只追加 image_sequence_frames_json: Option<String>image_sequence_duration_ms: Option<u64>editor_showcase_asset 作为提交时冻结的审核与公开快照,也在表尾追加并从账号素材复制相同两字段。前者保存完整有效帧数组,数组位置是唯一播放顺序,正式帧对象不保存或返回 frameIndex;后者只表示该图片序列完整播放一次的毫秒时长。帧数始终取数组长度,FPS 在播放或导出时即时推导,不持久化 frame_countfps
  • 图片序列时长不能复用音频 / 视频生成请求的 durationSeconds,资源和素材也不保存通用 duration_seconds。角色动作与视频生成响应保留各自既有的请求 / 结果级秒数;普通音频 / 视频的用户可见时长只作为字符串展示项写入 generation_inputs_json.fields[],上传媒体使用本次上传探测值,带时长选项的生成任务使用用户提交值,不再复制到 EditorAssetCanvasLayer 或画布 layout,也不在素材放置 / 工程恢复时探测或从 layout、resource 做双来源回退。素材详情和画布 ZIP 用户可见元数据只透传实际存在的 fields[] 时长项;缺少该项时省略时长,不生成 --:-- 等占位值。音频播放控件只信任 <audio>loadedmetadata.duration,展示字段不得参与播放、裁切或变速。SFX V2 是唯一例外:完成响应的 durationSeconds 必须来自 MP3 探测,且同一实际值写入 generation_inputs_json.soundEffect.actualDurationSeconds;该例外仍不得新增资源 / 素材正式时长列、写入 EditorAsset / CanvasLayer / layout,或复用图片序列字段。
  • 角色动作 worker 在透明帧全部持久化后创建最终项目资源与账号素材,并写入帧数组与 image_sequence_duration_ms;生成响应可继续返回请求 / 结果层面的 frameCountfpsdurationSeconds,但它们不是持久化真相。预览视频仍作为独立 asset_kind = video 中间素材保留并承担生成成本,最终派生素材成本为 0。
  • create_editor_project_resourcecreate_editor_asset 和同源资源回填必须在 procedure/storage 边界验证最终状态:asset_kind = character-animation 必须同时包含至少两帧的有效数组和大于 0 的 image_sequence_duration_ms;其他类别不得携带任一图片序列字段。同源资源只允许从 None 单调补齐字段,非空冲突失败关闭,补写后的 updated_at 不得早于既有时间。
  • generation_inputs_json 只保存用户生成 / 重放输入,例如 fieldsreferencesartSpec;动作输出和内部背景决策色不得再写入该 JSON。背景决策审计继续走独立审计链路。
  • 存量 generation_inputs_json.characterAnimation、已确认动作行的 helper 顶层 frames/previewVideoPath/frameCount/fps/durationSecondsscreenColorHex、正式帧中的 frameIndex 和误标动作预览 MP4,统一由 migration operator procedure normalize_editor_character_animation_metadata_and_returnasset → project-resource → showcase → canvas 四个 scope 迁移。顶层旧字段只有在 asset_kind=character-animation、正式序列字段、嵌套 characterAnimation 或权威 asset_object 已证明该行属于角色动作时才解释,普通图片 / 视频任意 JSON 中同名字段保持原样。迁移在计划态先把同 task 的已验证预览 MP4 重分类为 video,只有能形成正式图片序列的候选才可用于后续 scopeproject-resource 自身缺帧但能用同 owner、同 task、同首帧 assetObjectId/objectKey 精确定位唯一最终账号素材时,dry-run 可消费 asset 的计划态正式序列,apply 仍要求 asset scope 已物理完成。canvas 同样按 project-resource 的计划态类型分类:旧库误标为动作、但规划后为 video 且 layout 未声明动作的 preview 图层不参与动作副本清理;layout 明确声明动作却指向该视频,或原始动作资源的规划存在 blocker,继续失败关闭。每一帧必须按首帧 bucket 下的精确对象路径匹配同 owner、同 task、editor_character_animation 且为图片的 asset_object,并从登记对象补齐 objectKey/assetObjectId。canvas layout 复制的 sourceResourceId 不参与迁移血缘校验,补建资源只采用最终账号素材的 DB 血缘;新生成链路仍严格校验直接来源。正式与旧版帧或时长冲突、最终候选为零或多于一个、帧对象无法精确验证和缺失正式帧集均作为 blocker,整批拒绝 applyprocedure 同时返回不含生成输入正文的 blocker 完整诊断,包含 scope、ID、原因、owner、project、task、对象身份和来源资源。
  • 运维入口固定为 node scripts/spacetime-normalize-editor-character-actions.mjs --database <database> --server-url <url>;默认全量 dry-run--apply 时每批先 dry-run,再用返回的 batch SHA-256 写入,四个 scope 完成后从头执行零匹配 / 零 blocker 复核。迁移完成后 api-server、后台、前端和外部 helper 只读取正式字段,不再包含 legacy fallback。新建动作资源 / 素材若在 generationInputs 提交旧运行字段,或正式帧包含 frameIndexapi-server 与 SpacetimeDB storage 均失败关闭;其它素材的任意生成输入不受动作专属门禁影响。
  • 2026-08-10 前短期错误版本写入的 asset_kind = "image" 不走永久兼容,统一由 migration operator procedure clean_editor_image_asset_kind_and_return 清理。scope 固定为 asset → project-resource → showcase → canvas:前三者只把精确旧值改为 Nonecanvas 同时清理 editor_canvas / editor_project 双份 legacy layout 顶层 assetKind / assetKindOverride、结构化 editor_canvas_layer 的 typed override / item_json,以及 generation-dialog 权威 editor_canvas_generation_dialog.dialog_json 中的同名顶层扩展字段,但不递归修改 generationInputs.references[*].mediaType,也不修改 MIME、CanvasMediaTypeasset_object.asset_kind。dialog 权威行必须进入 dry-run 命中计数、批次 SHA-256 和同一事务内的 patch,清理后以重建的完整 structured layout 复核并更新 migration 摘要。纯分类修复保留原 updated_at、画布 revision 和迁移状态。双份 layout 清理后仍不一致、缺工程、owner 不一致或命中字段的画布项缺少稳定 layer / resource 身份时形成 blocker,整批拒绝 applyproject-resource scope 只允许 layout version 0 的 legacy 画布没有 migration,任一 structured 画布缺 migration 必须在资源行写入前失败关闭,并把该状态绑定进 dry-run / apply 批次 hash;诊断只返回 ID SHA-256、scope 和原因。
  • 普通画布 layer 的持久化和前端响应不包含顶层 mediaType;渲染类型只由资源 / 素材 assetKind 在前端派生。generationInputs.references[*].mediaType 是生成参考输入契约,不属于画布 layer legacy 字段,迁移和响应清洗不得递归删除。

新增编辑器 assetKind 接入清单(2026-08-032026-08-08 增补)

新增前先确定稳定字符串、主媒体含义、专属元数据、来源血缘、下载产物、复用规则和公开边界。只修改新类别实际经过的链路,不机械改动全部结构。

数据结构 / 投影 位置 需要修改的情况与内容
EditorProjectResourceEditorAsset server-rs/crates/spacetime-module/src/editor_project_storage.rs 新类别需要跨刷新或跨项目复用时保存 asset_kind;只有现有字段无法表达正式媒体结果时,才追加类别专属字段并在 storage / procedure 边界校验
EditorShowcaseAsset 同上 新类别允许投稿精选时冻结 asset_kind、完整正式媒体字段、owner 和稳定对象引用
create / upsert 参数、snapshot、procedure result 同上 写入、列表、详情、后台和公开读取需要使用新字段时同步强类型输入输出,不能绕过投影读取私有表
migration 与表目录 server-rs/crates/spacetime-module/src/migration.rs、本文件表目录 已有持久表新增字段时在结构体末尾追加明确默认值并更新迁移;删除、改名、重排或改类型前必须确认迁移计划
生成 bindings server-rs/crates/spacetime-client/src/module_bindings/ SpacetimeDB schema 或返回类型变化后重新生成 bindings,不能只手改一份
client record 与 mapper server-rs/crates/spacetime-client/src/active/mapper/src/mapper/ 两套 mapper 同步解析新字段,并贯通 facade 返回类型
Rust BFF 契约 server-rs/crates/shared-contracts/api-server/src/editor_project.rs 创建、生成响应、资源 / 素材读取和公开 read model 返回同一 assetKind 语义;后端决定正式类别
后台契约 server-rs/crates/shared-contracts/src/admin.rsapi-server/src/admin.rs 后台素材查询或精选审核需要展示该类别时返回完整正式字段,不能只返回封面或首帧
TypeScript 客户端类型 src/services/image-editor/editorProjectClient.tsapps/admin-web/src/api/adminApiTypes.ts 接收后端 camelCase 字段,不定义第二套业务真相
画布 layer / layout 映射 src/components/image-editor/ 新类别能进入画布时贯通资源加载、素材点击 / 拖放、保存恢复、复制、撤销、删除和导出;layout 只保留恢复副本
用户标签覆盖白名单 src/components/image-editor/ImageCanvasWorldView.tsxserver-rs/crates/spacetime-module/src/editor_project_storage.rs 显式决定用户能否把同媒体族图层覆盖为该类别;允许时同步前端 CANVAS_ASSET_KIND_TAG_OPTIONS 与后端 EDITOR_CANVAS_ASSET_KINDS,并分别覆盖菜单选择和结构化 assetKindOverride 解析测试;不允许时补拒绝测试,不能只改一端
快速编辑正向白名单 src/components/image-editor/ImageCanvasGenerationModel.tsserver-rs/crates/api-server/src/editor_project.rs 显式决定该类别是否支持快速编辑;允许时同步前端 QUICK_EDIT_SUPPORTED_ASSET_KINDS 与后端 ensure_editor_image_edit_source_kind_allowed,并覆盖入口可见性、提交门禁、权威来源类型和未知类型失败关闭;不允许时保持默认关闭并补反例,不能仅因属于图片媒体族自动放行
“改造” capability 与 V2 配方 src/components/image-editor/ImageCanvasGenerationInputsModel.tsImageCanvasGenerationModel.tsImageCanvasGenerationDialogModel.ts 及生成提交链 新类别接入时必须显式判断是否允许恢复原生成器。可改造的生成类别必须定义稳定 V2 actionfields[].idreferences[].id、action 级 decoder、面板恢复路径、改造 allowlist 和往返测试;确定性或不可重放类别即使保存 action 也不得进入改造 allowlist。assetKind 本身不能授予 capability;legacy 兼容只能按类别和旧配方特征窄化,并覆盖正反例,禁止把显示标题扩成全局路由键
素材库与 renderer src/components/image-editor/ assetKind 派生图片、视频、音频或序列 renderer,并实现正确缩略图、预览和下载行为
后台媒体 renderer apps/admin-web/src/components/AdminEditorAssetMedia.tsx 后台需要预览时复用现有组件;列表只加载最小媒体,弹窗再按需加载完整媒体
精选 read model 与 renderer api-serversrc/components/creation-home/ 新类别允许公开时贯通正式字段、卡片、弹窗和损坏数据行为
公开资产 grant server-rs/crates/spacetime-module/src/editor_project_storage.rs 私有对象公开展示时按 owner、当前展示状态和精确 assetObjectId / objectKey 授权;多对象媒体先验证完整快照再形成 grant
External OpenAPI / helper docs/openapi/.codex/skills/genarrative-external-editor-api/ 只有外部调用方需要创建或读取该类别时更新,并直接复用后端已创建的正式 resource / asset

其它注意事项:

  • assetKind 是数据库、Rust DTO 和对外 JSON 的可选语义类别真相;普通静态图片必须为空。不要新增或返回并列的 mediaType;前端 renderer 可以保留内部派生媒体类型,但不得回写后端。
  • 每次新增 assetKind 都必须分别完成“用户标签覆盖”和“快速编辑”两项资格评估;两者互不推导。可被用户选择不等于可快速编辑,属于图片媒体族也不等于自动进入快速编辑正向白名单。
  • assetKind 只描述素材类别,不代表生成配方可执行或允许“改造”;新增类别必须单独完成上表的 capability 决策和验收。
  • 只是新增分类或 renderer 且现有媒体字段足够时,不改 schema。只有必须跨刷新、复用、审核或公开保留的数据才新增类别专属字段。
  • legacy 数据必须先通过有界、可审计、带 dry-run/hash/apply 门禁的数据库迁移收口;迁移后的 api-server、mapper、主站、后台和画布只读取正式字段,不保留运行时 fallback。

bark_battle_runtime_run

  • Rust 结构体:BarkBattleRuntimeRunRow
  • 源码:server-rs/crates/spacetime-module/src/bark_battle/tables.rs

bark_battle_score_record

  • Rust 结构体:BarkBattleScoreRecordRow
  • 源码:server-rs/crates/spacetime-module/src/bark_battle/tables.rs

bark_battle_work_stats_projection

  • Rust 结构体:BarkBattleWorkStatsProjectionRow
  • 源码:server-rs/crates/spacetime-module/src/bark_battle/tables.rs

battle_state

  • Rust 结构体:BattleState
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

big_fish_agent_message

  • Rust 结构体:BigFishAgentMessage
  • 源码:server-rs/crates/spacetime-module/src/big_fish/tables.rs

big_fish_asset_slot

  • Rust 结构体:BigFishAssetSlot
  • 源码:server-rs/crates/spacetime-module/src/big_fish/tables.rs

big_fish_creation_session

  • Rust 结构体:BigFishCreationSession
  • 源码:server-rs/crates/spacetime-module/src/big_fish/tables.rs
  • 索引:by_big_fish_session_owner_user_idby_big_fish_session_stage。公开广场 view 使用 by_big_fish_session_stage 读取已发布会话,避免扫整表。
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

big_fish_event

  • Rust 结构体:BigFishEvent
  • 源码:server-rs/crates/spacetime-module/src/big_fish/events.rs

big_fish_runtime_run

  • Rust 结构体:BigFishRuntimeRun
  • 源码:server-rs/crates/spacetime-module/src/big_fish/tables.rs
  • Rust viewbig_fish_gallery_view
  • 返回类型:Vec<BigFishWorkSummarySnapshot>
  • 源码:server-rs/crates/spacetime-module/src/big_fish/session.rs
  • 说明:大鱼吃小鱼公开 source 投影,只从 Published creation session 组装公开卡片字段;统一公开列表 / 详情主路径通过 public_work_gallery_entry / public_work_detail_entry 消费该 view 并映射成跨玩法契约。玩法旧 gallery 路径保留兼容 shape;个人作品列表、详情、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。

chapter_progression

  • Rust 结构体:ChapterProgression
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

creation_entry_config

  • Rust 结构体:CreationEntryConfig
  • 源码:server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs
  • 字段:config_idstart_titlestart_descriptionstart_idle_badgestart_busy_badgemodal_titlemodal_descriptionupdated_atevent_titleevent_descriptionevent_cover_image_srcevent_prize_pool_mud_pointsevent_starts_at_textevent_ends_at_textevent_banners_jsonpublic_work_interactions_json
  • 迁移兼容:旧迁移包缺少活动横幅字段时,由 migration.rs 写入 None / 58000 默认值;旧库缺少 event_banners_json 时写入 None,运行态读取层再按 module-runtime 默认公告数组归一,不覆盖后台已保存配置,也不把旧结构化 eventBanner 升格为前端优先数组。旧库缺少 public_work_interactions_json 时写入 None,读取层按 module-runtime 默认作品互动矩阵补齐 publicWorkInteractions,不覆盖后台已保存开关。HTTP 响应同时返回 eventBanners 数组、旧 eventBanner 单条兼容字段和 publicWorkInteractions 互动矩阵;前端优先消费数组与矩阵。后台新公告配置主格式为 HTML 公告字符串数组或 {title, htmlCode} 对象数组,旧结构化 banner 字段仅保留兼容。默认公告背景和旧结构化默认 coverImageSrc 必须引用 public/ 下真实存在的静态资源,当前为 /creation-type-references/puzzle.webp

creation_entry_type_config

  • Rust 结构体:CreationEntryTypeConfig
  • 源码:server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs
  • 字段:idtitlesubtitlebadgeimage_srcvisibleopensort_orderupdated_atcategory_idcategory_labelcategory_sort_orderunified_creation_spec_json
  • 迁移兼容:旧迁移包缺少入口分类字段或统一创作契约字段时,由 migration.rs 写入 None / 0 / None 默认值;入口分组展示由 module-runtime 和前端展示派生消费,统一创作契约由 module-runtime 解析为 creationTypes[].unifiedCreationSpec,为空时按 shared-contracts 中当前支持的统一创作默认 spec 回退。unifiedCreationSpec.title 是统一创作页表头契约内容,读取和保存时不按入口 title 自动覆盖。

feature_gate_config

  • Rust 结构体:FeatureGateConfig
  • 源码:server-rs/crates/spacetime-module/src/runtime/feature_gate_config.rs
  • 字段:gate_keyenabledrollout_percentallow_user_idsallow_user_tagsdeny_user_idsdescriptionupdated_at
  • 用途:通用功能灰度事实源。当前创作入口使用 creation-entry:<id> 约定关联入口 IDapi-server 按当前可选登录用户、用户标签和稳定百分比判定后,只把过滤后的入口配置返回普通前端,不下发灰度规则或用户标签。
  • 迁移兼容:新增表不改已有入口表字段;未配置 gate 或 enabled=false 时不限制功能,黑名单用户 ID 优先于白名单和百分比命中。

custom_world_agent_message

  • Rust 结构体:CustomWorldAgentMessage
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/custom_world.rs

custom_world_agent_operation

  • Rust 结构体:CustomWorldAgentOperation
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/custom_world.rs

custom_world_agent_session

  • Rust 结构体:CustomWorldAgentSession
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/custom_world.rs
  • 发布约束:publish_world 的 action payload 不要求携带 settingTextspacetime-module 调用 module-custom-world::resolve_custom_world_publish_setting_text(...),优先从当前 draft_profile_json 草稿真相派生正式 setting_text,避免旧会话 seed_text 为空时在最终 compile / publish 阶段触发 custom_world.setting_text 不能为空

custom_world_draft_card

  • Rust 结构体:CustomWorldDraftCard
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/custom_world.rs

custom_world_gallery_entry

  • Rust 结构体:CustomWorldGalleryEntry
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/custom_world.rs
  • 作用:自定义世界公开 source 读模型。统一公开列表 / 详情主路径通过 public_work_gallery_entry / public_work_detail_entry 消费该投影并映射成跨玩法契约;/api/runtime/custom-world-gallery 保留旧 HTTP shape,并从统一 public cache 映射回旧 DTO。旧 procedure 只用于兼容旧库缺少 gallery 读模型行时的一次性同步兜底。
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

custom_world_profile

  • Rust 结构体:CustomWorldProfile
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/custom_world.rs
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true
  • 兼容约束:历史公开 RPG / 自定义世界 profile 可能存在 publication_status=Publishedpublished_at=None。公开详情、点赞、游玩、Remix 和 custom_world_gallery_entry 同步都以 Published + deleted_at=None + visible=true 判断作品可公开互动;展示和 gallery 同步时间在 published_at 缺失时回退 updated_at,不得仅因 published_at 为空返回“已发布作品不存在”。

custom_world_session

  • Rust 结构体:CustomWorldSession
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/custom_world.rs

database_migration_import_chunk

  • Rust 结构体:DatabaseMigrationImportChunk
  • 源码:server-rs/crates/spacetime-module/src/migration.rs

database_migration_operator

  • Rust 结构体:DatabaseMigrationOperator
  • 源码:server-rs/crates/spacetime-module/src/migration.rs
  • 说明:migration operator 与在线 runtime writer 必须身份互斥。当前 runtime writer 不能被授权为 operator,任何已登记 operator 也不能成为 runtime writer;一旦已有 operatorbootstrap secret 不得新增或接管 operator,后续授权只能由既有 operator 完成。

external_api_key

  • Rust 结构体:ExternalApiKey
  • 源码:server-rs/crates/spacetime-module/src/external_api_key_storage.rs
  • 说明:本表只承载普通外部 OpenAPI/MCP API Key。明文只在 /api/profile/api-keys 创建接口返回一次,服务端保存 key_hashkey_prefix、作用域和撤销状态;本需求不向该表增加 Router 字段,也不写入 Router 账号数据。v1 默认作用域为 editor:projecteditor:canvaseditor:image-generateeditor:asset
  • 索引:by_external_api_key_owner_user_id 用于登录态 API Key 列表;key_hash 唯一索引用于外部 API 鉴权。
  • 2026-08-31 修订:Router 账号状态、API Key 核心字段、账号元数据和加密凭据统一写入 llm_router_accountexternal_api_key 保持普通外部 OpenAPI/MCP Key 的原有链路不变。公共 Router、独立数据库的部署必须共享同一版本 provisioning secret,数据库缺行时才能恢复同一远端账号。LLM 计费的当前权威口径是下方 2026-09-05 累计额度结算规则。
  • 2026-09-01 修订:Router provisioning 允许所有环境使用官方固定 Router 控制面,以便独立开发数据库通过完整 owner user_id 的稳定派生凭据和 agc_auto_generate Token 复用同一远端账号/Key。由于 New API 的 usernamepassworddisplay_name 均限制 20 个字符,用户名固定为 agc_user_ 加 11 位 URL-safe SHA-256 短码,密码为基于完整 owner user_id 与 provisioning secret 派生的 20 位 hex;完整 owner user_id 通过 New API 用户 remark 字段保存,并在本地 llm_router_account.owner_user_id 保留权威映射。非官方公网地址仍拒绝,loopback 仅用于本地 fixture。使用共享官方 Router 的非生产环境启动时告警,提醒会触及线上账号与额度。api-server 不再提供任何 fallback Key 路径;没有已完成 provisioning 的账号行时,LLM 请求必须在本地解析阶段失败关闭。
  • 2026-09-01 修订:每次账号认证、Router Key 准备或 LLM 请求解析既有账号时,api-server 都会在同一 owner 级 provisioning 锁内检查固定套餐 plan_id=1。没有 active 订阅、订阅已过期或 end_time 距当前 Unix 秒不超过 24 小时时,使用管理员接口新建一条订阅;超过 24 小时则复用现有订阅。订阅检查不进入 Responses 流式 chunk,管理员 Token 缺失时仅保留启动告警并跳过续期检查。
  • LLM Router 计费:读取 Router 用户 used_quota,以 50000 quota = 1 泥点 结算(500000 quota = 1 USD,美元数值直接乘 10,不乘汇率)。上游调用前建立首次基线并补结算,成功响应后同步;不足整数部分、扣费失败和余额不足未支付部分留到后续累计同步。扣钱包、写 llm_router_consume 流水与推进 checkpoint 在同一事务完成;流水展示“LLM 调用消耗”。历史费用不追扣。详细契约见 technical/【技术方案】LLM累计额度结算-2026-09-05.md
  • 2026-09-05 修订:/api/llm/responses/api/llm/chat/completions 仍在 Router provisioning 前用钱包总余额阻止零余额账号创建或续期;解析凭据后、上游调用前再执行累计额度同步,以扣除退款占用后的剩余可消费余额为准。余额为 0 时返回 409 MUD_POINTS_INSUFFICIENT;余额或额度同步失败时失败关闭。上游已成功时,后置同步失败只记录错误并留待下次调用前补结算,不把成功模型响应改写为失败。
  • Windows 私有文件准备:AGC 自有 AppData、凭据目录和 .agent 运行态继续使用 managed 范围;用户通过原生选择器明确选中的项目根或文件,若 owner/DACL 仅因权限不足无法读取,则由一次性 UAC helper 在严格复核普通文件/目录、非 reparse/symlink、路径类型和目标 TokenUser 后接管并收紧为当前用户私有 DACL。项目放在当前 profile 之外(例如其他磁盘)不再因为路径位置被拒绝;未经过原生选择器或 AGC 项目根入口的内部路径仍不获得任意提权资格。

llm_router_account

  • 当前 AGC Router 需求由 llm_router_account 表单独承载:API Key 核心字段、加密凭据、Router 账号元数据、生命周期与 provisioning 状态均在该表;不依赖 external_api_key。api-server 每次上游调用都读取权威 llm_router_account active/revoked 状态并即时解密当前密文,不做 TTL 凭据缓存,避免任意实例轮换或撤销后继续使用旧 Key。

  • Rust 结构体:LlmRouterAccount

  • 源码:server-rs/crates/spacetime-module/src/llm_router_account.rs

  • 作用:记录用户与官方 LLM Router 账号的稳定映射、API Key 核心字段、账号生命周期、远端账号元数据、订阅检查状态、重试 / 对账状态和加密凭据版本。Router 明文 Key 只在 api-server 进程内短暂存在,数据库仅保存加密凭据;账号读取和写入统一通过对应 procedure 与 spacetime-client facade 完成。

  • 索引:by_llm_router_account_owner_route 用于按 owner 与路由来源读取账号;by_llm_router_account_status_next_retry 用于按状态和下次重试时间扫描待对账账号。

llm_router_billing_checkpoint

  • Rust 结构体:LlmRouterBillingCheckpoint
  • 源码:server-rs/crates/spacetime-module/src/runtime/active/profile.rs
  • 私有表,主键 account_key 关联 llm_router_account,保存 router_user_idsettled_quotaupdated_at
  • settle_llm_router_quota_and_return 仅允许服务身份,验证 owner/route/Router ID;首次同步完整记录当前额度而不扣历史,后续仅按实际扣款推进额度,与钱包和流水同事务提交。旧快照不回退;账号更换拒绝自动重建。

agc_model_catalog

  • 私有单例表,主键 id=0,保存 catalog_jsonrevisionupdated_at;不存凭据。
  • read_agc_model_catalog / save_agc_model_catalog 只接受已登记的 runtime service identity,保存使用 revision 乐观锁。
  • 后台 owner 通过 GET/PUT /admin/api/agc-models 管理稳定标识、必填别名、实际模型名、启用状态和默认项;客户端 GET /api/llm/models 仅返回启用项的稳定标识和别名。
  • Responses / Chat 请求按目录解析模型;未知或停用项拒绝。AGC 的 platform-default 请求标识使用目录默认项。详细契约见 technical/【技术方案】AGC后台模型别名与对话选择-2026-09-05.md

error_report

  • Rust 结构体:ErrorReport
  • 源码:server-rs/crates/spacetime-module/src/error_report.rs
  • 说明:错误报告只在请求内存中构建 ZIP 并上传私有 OSSSpacetimeDB 仅保存 batch、幂等、对象键、摘要、计数和管理员审核元数据。管理员列表/筛选/更新走 spacetime-client facade,详情和下载再从 OSS 读取 ZIP。OSS key 固定为 agc/error-reports/v1/{batchId}.zip,不含时间戳;上传失败不写表。
  • 索引:by_error_report_user_submission 用于用户提交幂等;by_error_report_created_atby_error_report_review_status 用于后台查询与清理。

admin_account

  • Rust 结构体:AdminAccount
  • 源码:server-rs/crates/spacetime-module/src/admin_account_storage.rs
  • 说明:后台 member 私有账号表,保存规范化用户名、展示名、Argon2id 密码摘要、一级 Tab 权限 JSON、独立操作权限 JSON、启停状态、会话版本和创建 / 更新审计字段。action_permissions_json 是既有表末尾新增的可选字段,旧行默认空数组语义;当前唯一独立操作权限为 profile-wallet-consumption-reconcile。原环境变量管理员作为虚拟 owner,不写入该表。账号查询与写入 procedure 只接受 runtime service identityHTTP 列表和写响应不返回密码摘要。
  • 索引:account_id 为主键,username 为唯一登录名;Tab 权限、独立操作权限、密码或启停状态发生变化时在同一事务递增 token_version,使旧 JWT 下一次请求立即失效。

editor_agent_conversation

  • Rust 结构体:EditorAgentConversation
  • 源码:server-rs/crates/spacetime-module/src/editor_agent_storage.rs
  • 说明:画布Agent对话会话元数据表,归属单个 editor_project;只保存会话 ID、project、owner、标题、消息 OSS 对象键(editor-agent/{conversationId}.json)、软删标记和时间戳。消息正文整体存 OSS,按会话粒度整体读写,不进 SpacetimeDB、不进画布工程快照 payload。删除为软删(deleted = true,OSS 对象保留)。领域校验(标题截取、附件上限、归属 / 软删规则)沉在 module-editor-agent
  • 过程:只通过 create_editor_agent_conversation_and_returnlist_editor_agent_conversations_and_returnget_editor_agent_conversation_and_returntouch_editor_agent_conversation_and_returndelete_editor_agent_conversation_and_return procedure 操作;api-server 不直接绕过 spacetime-client facade 读写表。消息文档当前由 api-server 做单会话串行锁和 2 MiB 上限保护,避免同一 SSE 回合并发重写同一个 OSS JSON 文档。
  • 索引:by_editor_agent_conversation_project_id 用于会话列表;by_editor_agent_conversation_owner_user_id 用于账号级归属校验。

editor_project

  • Rust 结构体:EditorProject
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:图片画布工程真相表,保存 owner、标题和工程时间戳;viewport 与画布数据已拆到 editor_canvas 及其结构化子表,旧 project layout columns 暂作为兼容列保留,不再作为权威数据源。只通过 /api/editor/projects*/api/editor/projects/{projectId}/agent-conversations/api/editor/agent-conversations/{conversationId}* BFF 和 spacetime-client facade 读写;项目页列表、重命名和删除也使用该能力,删除工程时级联清理默认画布、结构化画布行、迁移状态和资源元数据。
  • 索引:by_editor_project_owner_user_id 用于读取当前用户最近编辑工程和项目页工程列表。

editor_canvas

  • Rust 结构体:EditorCanvas
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:图片画布根表,归属于 editor_project,保存默认画布的 viewport typed columns、owner、时间戳与当前 revision;当前编辑器读取 / 保存 project 的默认 canvas,后续支持一个工程多个 canvas。layers_json 继续保留,legacy canvas 在统一 2 MiB 上限内以它作为布局真相;editor_canvas_layout_migration 激活 structured 后,图层和生成对话框分别以 editor_canvas_layereditor_canvas_generation_dialog 为权威,layers_json 只作为存量迁移输入和受限回滚载体。所有结构化 mutation 必须以 expected_revision 做 CAS,成功事务只递增一次 revision。
  • 索引:by_editor_canvas_project_idby_editor_canvas_owner_user_id

editor_canvas_layer

  • Rust 结构体:EditorCanvasLayer
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:图片画布结构化图层表,一行保存一个 layer 的 canvas / project / owner 归属、几何、层级顺序、分组、hidden / locked / flip 状态、resource_id 与可空 asset_kind_overrideasset_kind_override 是布局实例的素材类型覆盖,资源默认值仍由 editor_project_resource.asset_kind 持有,有效类型统一按 override ?? resource default 计算;修改图层标签不得创建资源或替换 resource_id。覆盖值只能在资源默认类型的同一媒体族内变化:character-animation 为动作族,video 为视频族,audio/sound-effect/background-music 为音频族,其余值和空默认值为图片族。结构化保存必须对每个已解析资源完整校验该规则;跨族覆盖不拒绝整次保存,而是清除 asset_kind_override 并回退资源默认类型。若正式动作资源因该回退路径携带了 layout 媒体副本,同时丢弃这些副本并继续以资源行序列字段为权威;其他动作图层复制资源结果字段仍然失败关闭。V1 的 item_json 只保留未结构化扩展字段,单行最大 512 KiB;快照以 typed 列重组,不得把完整图层 JSON 当作平行真相。历史 layout 的 sourceResourceId == resourceId 属于无意义自引用,迁移时按资源表真相剥离;其他资源字段冲突继续 fail-closed。唯一存量缺资源例外是已缺资源行、但帧与预览均为稳定站内对象路径的 local-* + generated + image-sequence 历史角色动作图层:迁移保留其有界媒体扩展并纳入 canonical hashactive 后只能续存同一行且扩展不可变,不能新增或篡改。其他缺资源图层继续 fail-closed。当前站内写入通过 revision CAS 把兼容布局事务性拆成行;后续再将新增、移动、缩放、删除、重排和分组收窄为有界 batch mutation。
  • 索引:按 canvas 读取完整结构化快照,按 project 做级联清理;owner 保留在行内用于归属校验。同一 canvas 的 layer id 必须稳定且唯一。

editor_canvas_generation_dialog

  • Rust 结构体:EditorCanvasGenerationDialog
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:图片画布生成对话框表,保存 canvas / project / owner、生成模式、状态、可选 source / generated layer、占位几何和有界扩展字段;typed 列为快照真相,dialog_json 只保留未结构化参数。编辑器生成完成使用候选布局的 expected_revision 执行 CAS,冲突时 object/resource/asset/binding/canvas/job/receipt 整笔回滚;queue 路径同时受 job_id + worker_id + lease_token 栅栏保护。调用方只能刷新权威项目后重算布局候选,不得重跑 provider 或更换原 operation。
  • 完美像素 completion:同步处理成功后按当前 revision 重新读取权威 dialog;目标 dialog 存在时只写入一个派生 layer 并关联结果,目标 dialog 的删除已先持久化时跳过 layer / dialog 写回,不得按请求快照重建占位。该分支不属于 external job completion,允许此前已成功创建的 resource / asset 保留。
  • 索引:按 canvas 和 project 读取结构化行;当前未建立 external job 二级索引。

editor_canvas_layout_migration

  • Rust 结构体:EditorCanvasLayoutMigration
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:每个 canvas 一行的结构化持久化控制与迁移审计表,保存 schema version、backfilled / active / rolled_back 状态、最后校验 revision、legacy / structured canonical hash、layer / dialog 数量、资源引用集合 hash 和激活 / 回滚时间。存量迁移固定执行幂等 backfill → hash / 数量 / 资源引用核对 → revision CAS activateactive 重入和 rollback 同样重新核对,不只按状态早返。回滚结果还必须不超过 2 MiB。三个 procedure 只允许 database migration operator 调用;npm run spacetime:editor-canvas-layout:migrate 默认 dry-run,显式 --apply 才写入。
  • 索引:canvas_id 唯一,一切模式切换仅允许受限迁移 procedure 执行;前端不得提交或推断存储模式。

editor_project_resource

  • Rust 结构体:EditorProjectResource
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:图片画布工程资源元数据表,保存已经放入某个 project 画布的上传 / 生成媒体资源快照、OSS 引用、尺寸、来源类型、prompt、provider、task、源资源关系、asset_kindgeneration_inputs_json、序列媒体结果字段和历史 public_showcase_enabledasset_kind 是跨布局共享的资源默认素材类型;单个结构化图层的差异只写 editor_canvas_layer.asset_kind_override,有效类型按 override ?? resource default 计算,不能通过新增资源行模拟标签修改。image_sequence_frames_jsonimage_sequence_duration_ms 保存角色动作正式结果,前者数组顺序是唯一帧序;角色动作预览视频是独立视频资源,不在最终序列资源行重复保存路径。“动作(原始视频)”只作为 asset_kind = video 的 provider 中间产物保存,其项目资源与账号素材都必须保持 generation_inputs_json = NULL;完整生成 / 重放输入只属于最终 asset_kind = character-animation 的序列资源和素材。generation_inputs_json 只保存用户可见生成 / 重放输入。public_showcase_enabled 只保留旧接口兼容,不再作为 /creation陶泥儿精选 事实源;精选公开改由账号级生成素材提交 editor_showcase_asset 审核决定。图片 / 图标 / UI 提取等生成 BFF 在请求携带 project_id 时负责创建该表记录并把 resource 快照返回前端;前端只保存稳定 resource_id 布局引用,不能把同一生成结果再次作为正式业务真相写入。项目封面快照也落在该表,使用 asset_kind = project-cover-snapshotsource_type = uploaded 和私有 OSS / asset object 引用,代表画布当前视口栅格化后的静态封面;项目列表和创作主页最近项目只读取最新封面快照资源,不在列表页根据 layout 临时拼画布。从账号级素材库把同一生成素材拖回同一项目画布时,后端优先复用同项目内同源同媒体资源,避免每个图层实例都插入新的资源行。账号级素材删除不级联删除该表,避免历史画布丢图。结构化 canvas 的几何、层级、分组、类型覆盖和资源引用以 editor_canvas_layer 为权威,媒体业务真相仍由资源表持有;生成器对象以 editor_canvas_generation_dialog 为权威。legacy canvas 才在 2 MiB 上限内从 editor_canvas.layers_json 兼容读取;唯一缺资源例外是经过稳定站内路径校验的历史 local-* + generated + image-sequence 自包含图层,active 后只能续存同一不可变扩展。新写入不再把素材生成输入快照或正式序列帧结果作为图层布局真相保存。历史普通图层缺资源只能由 migration operator 调用 repair_editor_canvas_resources_and_return 定向修复:procedure 每次只处理一个尚无迁移记录的 legacy canvas,校验 owner/project、revision、canvas/project 两份 raw layout SHA-256、精确 layer/resource/sourceResourceId、同工程替换资源与 private asset_object 谱系;图片只替换引用,音频只恢复经核验的 420x120 项目资源行。运维入口 npm run spacetime:editor-canvas-resources:repair 默认 dry-runapply 必须绑定 plan SHA-256 并在成功后自动复核 already-repaired,禁止手工 SQL 绕过事务 guard。
  • 生成链路的新 resource ID 必须按 owner + operation kind + operation ID + stable slot 派生,只能在统一结果 procedure 中创建或完整比较;普通“同媒体复用”不得把它替换成另一随机 ID。
  • 普通静态图片的默认 asset_kindNULL;登录态 API 与 SpacetimeDB storage 的 editor_project_resource / editor_asset 创建边界,以及 legacy 画布保存提取元数据和项目资源落表边界,都必须将 trim 后精确等于旧值 image 的输入归一为 NULL。legacy 布局即使该字段归一后为空也必须删除原字段并持久化清理结果,其它非空值保持既有校验语义。存量清理由受限 migration procedure 执行;project-resource 清行前先验证同工程迁移并把迁移状态纳入批次 hash,清行后若两侧仍满足原 status 不变量就立即刷新摘要;必须等待显式 canvas 字段一并删除的受控过渡态保留旧凭证。canvas scope 的任何布局写入前再按原 migration status 校验旧凭证:active 同时核对双份 legacy shadow 与当前 structured revision/hash/integritybackfilled 还要证明两侧与原凭证一致且语义等价,rolled_back 沿用重复 rollback 的完整复检。只有旧凭证有效且差异仅由本次 project-resource 清零与精确 image 字段删除构成时才允许写入、复核新状态并受控重算 legacy / structured hash、图层 / 对话框 / 资源引用完整性和 verified revisionmigration status 与全部时间戳保持不变。
  • 完美像素资源:处理成功时只允许一个整数倍放大后的最终 PNG 对象对应一个新 project resource;源图已有正式 project resource 时,source_resource_id 指向该资源。resource 与同源 editor_asset 的尺寸都取最终 PNG 实际值,宽高比等于逻辑图,允许与源图、交付尺寸和占位尺寸不同;不得另存未放大逻辑图、输入尺寸恢复版、诊断图或前后对比图。请求省略素材文件夹时结果落入默认素材文件夹;completion 因权威 dialog 的删除已先持久化而跳过画布写回时,这两类已确认资源无需回滚。
  • generation_inputs_json 包络契约:fields / references 是图片信息读取的用户可见生成输入快照;顶层允许保存后端内部结果扩展。现有 screenColorHex 保存实际背景色,角色、图标图集和 UI 图集抠图派生资产使用 mattingProvider / mattingModel 保存实际成功的处理后端与模型。BgFilter 保存本次 seg_model,阿里云通用抠图保存 Aliyun Matting / segment-common-image,本地键色保存 Genarrative Local / screen-color-keying。同源画布 BFF 的角色、图标和 UI 请求由前端自动提交 screenColor=auto 与默认 segModel=birefnet,其中 segModel 是不可由用户选择的请求控制字段,不进入 generationInputsbackground_modecross_check 只属于 api-server 到 worker 的内部 RPC。External OpenAPI 不开放 segModel。上述内部结果字段不写入 fields,普通用户(包括素材 owner)与匿名公开读取均不得取得;普通用户响应还必须省略素材顶层 provider 和内部处理 model,但保留正常用户可见 model 与其他合法的顶层功能字段。后台管理和服务端审计可读取原始值。过滤只作用于普通用户 / 公开响应边界,不修改素材或精选快照,因此历史数据无需迁移。
  • generation_inputs_json V2 可执行改造契约沿用现有 JSON 列,无 SpacetimeDB schema 迁移或存量回填:顶层 version=2 和稳定 action 确定生成器,fields[].id 确定参数,references[].id/refType/refId 确定引用参数与稳定指针;fields[].value 保持 string | number | boolean 类型,title / label 只作展示。前端改造只在当前画布图层中匹配引用并取得运行时媒体类型,不新增 owner-only 工程资源 / 素材库 resolver;面板直接上传引用和已移出画布的引用均不恢复。可重新选择的引用由前端留空槽位、提示并交给提交门禁校验;必须依赖原 source 图层才能构造面板的 action 仍按 capability 保留改造按钮,source 缺失时在点击恢复路径显示明确错误并拒绝,运行期来源变化时再次校验。有效 V2 的引用缺失不得触发 legacy adapter。Owner resource / asset payload 在普通用户元数据清理后保留这些执行字段;匿名公开素材 payload 暂不返回 generationInputs,避免公开接口沿用 owner 可执行配方 DTO。独立裁扩、手动去背景和手动图集拆分是确定性派生操作,新结果 generation_inputs_json = null;原生成任务内的自动透明化 / 拆分后处理可保留同任务的原生成输入。
  • 普通用户生成结果契约:图片、图标图集、视频、音频和角色动画的完成响应与新建画布图层均不返回或写入生成 provider;项目资源、素材、精选和 Agent 紧凑结果使用同一读取边界。真实 provider 只保留在持久化、tracking / tracing 和后台管理原始审计中。该规则针对生成供应商元数据,不改变直传票据等必须由客户端执行的存储协议字段。
  • 普通用户错误契约:手动去背景和角色动作透明化的原始服务端错误可能包含 BgFilter、分割模型或 provider 细节;Owner HTTP 响应与外部任务状态必须按 job kind 返回稳定业务文案,原始错误只保留在任务记录、tracing 与后台审计。
  • 索引:by_editor_project_resource_project_idby_editor_project_resource_owner_user_id

editor_asset_folder

  • Rust 结构体:EditorAssetFolder
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:图片画布账号级素材文件夹表,归属于用户账号而不是 project;首次读取素材库时自动创建系统默认“项目素材”文件夹。文件夹支持重命名、折叠和删除,系统默认文件夹不能删除。
  • 索引:by_editor_asset_folder_owner_user_id

editor_asset

  • Rust 结构体:EditorAsset
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:图片画布账号级素材表,保存用户上传 / 生成素材的名称、文件夹、媒体读取地址、可选封面 thumbnail_src、OSS 引用、尺寸、来源类型、prompt、provider、真实操作 task_id、可选后台归组 group_task_id、拆分批次预期数量 group_task_expected_asset_countasset_kindgeneration_inputs_json、可选 source_resource_idgeneration_cost_mud_points 和序列媒体结果字段。asset_kind 是唯一权威媒体类别;image_sequence_frames_jsonimage_sequence_duration_ms 保存角色动作正式结果,数组长度派生帧数、数组顺序决定播放顺序、FPS 按需推导,generation_inputs_json 只保存用户可见生成 / 重放输入。prompt 固定表示规范化后的用户原始意图,供跨资源搜索和用户侧元数据使用;provider 实际返回的改写只写 actual_prompt,提交给 provider 的系统 / 工程化 prompt 不得写入 prompt。角色透明图、图标透明图和自动切片等派生产物继承源用户 prompt,并用 source_resource_idgeneration_inputs_json、provider / asset kind 表达处理来源。归组字段只用于稳定派生任务的后台分组,不替代 task_id;批次是否初始完整以独立完成事实为准,不按当前剩余素材数反推。素材在同一账号的所有项目中可见;图片 / 图标 / UI 提取等生成 BFF 在请求携带 asset_folder_id 时负责创建账号级生成素材并返回 asset 快照,若同次生成也创建了 editor_project_resource,则把该 resource_id 写入 source_resource_id。角色动作生成把原始绿幕预览视频作为独立 asset_kind = video 素材保存,再把最终帧序列作为一条 asset_kind = character-animation 素材入库:首帧写入 image_src / thumbnail_src,完整帧数组与图片序列毫秒时长写入两个正式媒体结果字段;生成端确认每个最终帧对象后,必须把该帧的 objectKeyassetObjectId 同时写入正式帧 payload,不能只在内部处理中暂存或仅保留首帧引用;存在项目上下文时,最终动作的 source_resource_id 指向预览视频资源。不把每帧拆成独立素材,也不重复保存预览路径。生成视频会抽取首帧封面写入 thumbnail_src,素材库和再次放入画布时用它作为 video poster。素材库快照通过 asset_id 回查对应 editor_showcase_asset,供左侧素材菜单展示 pending / approved / rejected 审核状态;公开事实不落在账号素材表,素材库只发起提交审核。素材放入画布时复制为 editor_project_resource 并由图层引用 resourceId,画布从 resource / asset 级元数据恢复素材类别、媒体结果和用户可见生成输入快照。
  • 生成链路的新 asset ID 与 project resource 使用同一 operation/slot 身份但独立 domain 派生;首次提交和完整重放都必须保持 folder、object、source resource、task、媒体字段和生成输入逐字段一致。
  • 正式序列帧写入边界:写入端必须逐帧取得非空 objectKeyassetObjectId,并始终按 objectKey 重建 imageSrc 的持久站内路径。只有临时签名 URL 而没有这两项稳定引用的 External v1 请求返回 400,不能进入正式素材或项目资源。
  • 索引:by_editor_asset_owner_user_idby_editor_asset_folder_id

editor_asset_group_source_provenance

  • Rust 结构体:EditorAssetGroupSourceProvenance
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:手动图集拆分的私有可信来源索引。仅当账号素材由后端生成链路写入且具有正式 source_resource_id 时,按 owner + source resource、asset object 和 Object Key 记录真实来源 task_id;普通素材 / 资源创建请求不能直接写该表。跨项目复用素材后可通过稳定媒体引用找回原任务,历史行首次扫描命中后补写索引。该持久表纳入 migration_tables! 导入导出清单,数据库迁移与恢复不得遗漏来源归组事实。
  • 主键:lookup_key

editor_asset_group_cohort

  • Rust 结构体:EditorAssetGroupCohort
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:手动拆分批次的私有完成事实。api-server 只有在预期的全部 asset ID 已成功落库并由 runtime service identity 逐行校验 owner、真实 task、归组 task 和预期数量一致后才能插入;事实写入后不随用户删除单片素材而撤销。没有完成事实的现代批次不能并入来源根任务。该持久表纳入 migration_tables! 导入导出清单,数据库迁移与恢复必须保留不可逆批次完成事实。
  • 主键:cohort_id

editor_showcase_asset

  • Rust 结构体:EditorShowcaseAsset
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:陶泥儿精选 的独立审核与公开快照表。用户从账号级生成素材提交后,后端把素材媒体、提示词、生成输入、素材类型、generation_cost_mud_points 以及角色动作的 image_sequence_frames_json / image_sequence_duration_ms 快照到该表,初始 review_status = pendingdisplay_enabled = falseshowcase_category = null。公开 read model 只读取正式序列快照并返回既有 imageSequenceFrames / imageSequenceDurationMs 可选字段;旧快照必须先经 showcase scope 规范化,不能在公开 mapper 回读 generation_inputs_json。公开角色动作帧只从当前有效展示快照中的逐帧 assetObjectId / objectKey 派生 exact read grant,完整数组无效或任一公开条件撤销后不再授权。后台审核通过后写入 approved,但仍保持不展示;运营可在后台按前台具体 Tab 手动设置 showcase_categorycharactersuimusicmarketing)并开启展示。未设置分类的素材不归入具体 Tab,但展示开启后仍进入前台“全部”。审核通过时生成确定性返还流水 editor-showcase-refund:{showcase_id},BFF 按 50% 生成成本返还泥点后回写 refund_completed_at;拒绝后写入 rejected。公开精选 GET /api/editor/showcase/resources 读取 review_status = approveddisplay_enabled = true 且媒体非空的记录,按通过时间 / showcase_id 倒序 cursor 分页。素材删除时,待审核记录标记 asset_deleted_while_pending,已拒绝记录删除,已通过记录保留快照继续展示。
  • 索引:by_editor_showcase_asset_owner_user_idby_editor_showcase_asset_review_status

editor_showcase_asset_like

  • Rust 结构体:EditorShowcaseAssetLike
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:陶泥儿精选 点赞去重表,主键由 showcase_id:user_id 组成,是“某用户是否点赞”的唯一业务真相;editor_showcase_asset.like_count 只是全局聚合,不能反推出当前浏览者状态。点赞 / 取消点赞通过登录接口在同一事务中幂等更新去重行与聚合计数,并返回权威 viewer_liked + like_count;写路径只保留返回该 viewer 权威状态的 set_editor_showcase_asset_like_for_viewer_and_return,不保留只返回素材聚合快照的旧 toggle procedure。未通过审核或未展示的精选素材不能点赞。公开 GET /api/editor/showcase/resources 接受可选 Bearer:匿名不读取私有 like 表并返回 viewerLiked=false,有效登录态由 api-server 从 claims 派生 viewer_user_id,通过 runtime service identity 调用 viewer procedure,在公开排序 / 截断后按确定性主键读取每项状态。viewer 读取与所有 like 写 procedure 都先在 ProcedureContext 捕获 ctx.sender(),再在事务内校验现有 runtime service identity;不得信任 procedure 输入中的 user_id 作为调用方授权。无效 Bearer 或个性化读取失败不降级匿名。
  • 索引:by_editor_showcase_asset_like_showcase_idby_editor_showcase_asset_like_user_id

editor_showcase_campaign_config

  • Rust 结构体:EditorShowcaseCampaignConfig
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:陶泥儿精选 首位固定活动卡配置表,当前使用固定 config_id = global。后台可配置启用状态、标题、图片地址、提示词、作者和成本文案;上传按钮先通过后台受控上传票据把 private 图片写入 OSS,再调用 /admin/api/editor-showcase/campaign/image-upload-confirm 复用统一 OSS HEAD 校验并登记正式 asset_object,确认成功后才把 image_src、内部图片 OSS image_object_keyimage_widthimage_height 写回表单。公开精选接口只在启用时返回该配置,前端优先用 image_object_key 走签名读地址展示,并按记录的图片宽高决定活动卡比例。匿名读权限只对当前启用配置中、位于活动卡专用目录且精确匹配的 key 派生;禁用或换图后旧 key 自动失效,不能公开整个 generated 前缀。
  • 索引:主键 config_id

editor_generation_pricing_config

  • Rust 结构体:EditorGenerationPricingConfig
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:图片画布生成类模型定价全局配置表,当前使用固定 config_id = globalmodels: Vec<EditorGenerationModelPricing> 强类型保存模型、定价单位、单价或档位列表,procedure / spacetime-client 边界不传递不透明 JSON;模块事务会再次校验完整正式模型矩阵、必需档位、单位、正价格和重复键。writer_identity 只保留在 private 表内,记录首次初始化的真实 ctx.sender(),不进入 procedure 返回快照;后续 upsert_editor_generation_pricing_config_and_return 只允许同一 identity 或已授权迁移操作员修改价格,但始终保留原 writer。表为空时只有 HTTP 角色通过 initialize_editor_generation_pricing_config_if_missing_and_return 在单事务内仅缺失时种子入库;worker / controller 启动只调用受鉴权的 queue-stats procedure 做只读身份预检。后台保存请求携带 AppConfig 中的受保护 bootstrap secret,以便配置行意外缺失时原子恢复;表存在时 bootstrap secret 不能接管 writer。原始 bootstrap secret 固定为 64 位十六进制;WASM 只嵌入其 SHA-256procedure 对入参原文重新计算摘要并做常量时间比较。runtime queue / 钱包 procedure 只接受精确 writer,当前生产 API / worker / controller 因此继承同一 runtime token;迁移操作员不自动获得在线运行权限,且 operator / writer 身份必须互斥。SpacetimeDB 不可达时仅使用默认 JSON 或旧 override 缓存兜底。
  • 索引:主键 config_id

editor_generation_operation

  • Rust 结构体:EditorGenerationOperation
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:编辑器生成结果的私有 durable commit receiptqueue 与受控 inline 路径共用。主键 operation_key 由 owner 与 operation ID 稳定派生,同 owner 不得跨 kind 复用 operation IDoperation_fingerprint 绑定规范用户请求,commit_sha256 绑定全部 slot 候选、object/resource/asset/binding、可选 canvas 候选与 job completion。表内另固化 owner/kind/ID、可选 project_id、queue 的 job_id / job_worker_id / job_lease_token / job_result_payload_sha256 和首次提交时间;重放从已完成 job 回读权威 compact result 并核对摘要,不在 receipt 复制正文。它只证明一笔业务结果已原子提交,不是第二套 job 状态或 resource/asset/canvas read model,不保存大快照。结果提交的 operation kind 白名单必须覆盖所有进入统一持久化的正式 job kind,其中包括独立图标规范任务 editor_icon_spec_generation;新增 job kind 时必须在同一变更中同步白名单和模块回归。
  • 重放:receipt 存在时必须核对全部绑定和权威记录,完全一致才返回 AlreadyApplied;同 operation 的请求或提交摘要漂移、project/job 绑定漂移、receipt 缺失但稳定 resource/asset/binding 等业务记录已存在均失败关闭。事务前单独确认的 asset object 只在全部字段与稳定候选完全一致时允许复用,不能据此补造 receipt。重放不更新 receipt 时间,不重复 job/binding 事件,不推进 canvas revision。
  • 时间:completed_at_micros 必须为正数并固化为 receipt 完成时间;object/resource/asset/binding/canvas 候选的原时间字段与它一起进入 commit SHA-256,重放必须复用原 prepared commit 而不得重新取时。queue job 完成时间与完成事件仍使用 SpacetimeDB ctx.timestamp,不信任调用方时钟。
  • 索引:主键 operation_keyby_editor_generation_operation_owner(owner_user_id, operation_id) 仅用于受控定位和诊断,不允许同 owner 跨 operation kind 复用同一 operation ID。

editor_idempotent_create_receipt

  • Rust 结构体:EditorIdempotentCreateReceipt
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:图片画布工程、素材文件夹和工程资源首次创建的私有 durable receipt。主键 receipt_key 由认证 owner、接口 namespace 与 API 根据 Idempotency-Key 派生的请求记录 ID 做 domain-separated SHA-256 得到;request_sha256 覆盖完整规范化 create payload,但不包含每次重试都会变化的请求时间。首次业务行、副作用与 receipt 必须在同一 SpacetimeDB 事务中提交,主键唯一约束负责并发仲裁。
  • 重放:同 owner、namespace、key 与同一原始规范化正文重放时,按 receipt 的 result_record_id 返回当前业务行,因此工程改名、目录更新或资源元数据后仍不会把当前可变行误判为请求正文漂移;同键异正文返回 409。receipt 不随业务行删除,首次结果已删除时重放统一返回 409 并拒绝重建;receipt 缺失但请求稳定 ID 已存在同样失败关闭,不能补造 receipt 或重复首次副作用。
  • 索引:主键 receipt_keyby_editor_idempotent_create_receipt_owner(owner_user_id, namespace, request_record_id) 仅用于受控诊断。表为 private,不作为工程、目录或资源 read model。
  • 真实事务门禁:运行 npm run check:editor-idempotency-procedures,在隔离的 SpacetimeDB 2.8.3 standalone 中发布当前模块,验证工程、素材文件夹和工程资源的同正文重放、异正文冲突、并发仲裁、删除后失败关闭,以及业务行与 private receipt 一一对应且无孤儿;源码字符串断言不能替代该门禁。

editor_generation_runtime_identity_rotation

  • Rust 结构体:EditorGenerationRuntimeIdentityRotation
  • 源码:server-rs/crates/spacetime-module/src/editor_project_storage.rs
  • 说明:模型生成运行时服务 identity 显式轮换审计表。只有已授权迁移操作员可调用 rotate_editor_generation_runtime_service_identity_and_return,且新 writer 不能等于当前 writer,也不能是任一已登记 migration operator。每次记录旧 writer、新 writer、迁移操作员 identity、操作人、原因和服务端时间;轮换只修改 writer,不覆盖已有模型价格。生产人工入口为 scripts/deploy/production-runtime-writer-identity-rotate.mjsCLI 会核对当前登录 operator identity 并要求双录新 identity。
  • 索引:自增主键 rotation_id

inventory_slot

  • Rust 结构体:InventorySlot
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

jump_hop_agent_session

  • Rust 结构体:JumpHopAgentSessionRow
  • 源码:server-rs/crates/spacetime-module/src/jump_hop/tables.rs

jump_hop_event

  • Rust 结构体:JumpHopEventRow
  • 源码:server-rs/crates/spacetime-module/src/jump_hop/tables.rs

jump_hop_leaderboard_entry

  • Rust 结构体:JumpHopLeaderboardEntryRow
  • 源码:server-rs/crates/spacetime-module/src/jump_hop/tables.rs
  • 说明:跳一跳作品维度排行榜 read model,每个 profile_id + player_id 只保留 1 条最佳记录;排序口径为成功跳跃次数降序、游戏时长升序、更新时间升序,草稿试玩不作为公开排行榜语义。
  • 展示契约:player_id 只作为后端去重和 viewerBest 匹配身份键,不得直接进入 HTTP/UI 展示字段;/api/runtime/jump-hop/works/{profile_id}/leaderboard 必须补齐 displayName,已登录玩家读取账号显示名,匿名游客展示“游客玩家”,失效账号展示“失效玩家”。

jump_hop_runtime_run

  • Rust 结构体:JumpHopRuntimeRunRow
  • 源码:server-rs/crates/spacetime-module/src/jump_hop/tables.rs
  • 说明:运行记录持久化 runtime_mode,取值为 draft / published;草稿试玩只允许作品所有者启动,不累计公开游玩次数,也不写入公开排行榜。

jump_hop_work_profile

  • Rust 结构体:JumpHopWorkProfileRow
  • 源码:server-rs/crates/spacetime-module/src/jump_hop/tables.rs
  • 说明:作品投影持久化独立 theme_text,用于生成主题和公开卡片主题展示;历史行为空时按 work_title 兜底。back_button_asset_json 保存 image2 单独生成并去绿后的 1:1 左上角返回按钮资产快照;旧迁移数据按 None 兼容,运行态缺失该字段时使用同尺寸 CSS 主题按钮兜底。
  • Rust viewjump_hop_gallery_card_view
  • 返回类型:Vec<JumpHopGalleryCardViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/jump_hop.rs
  • 说明:跳一跳公开列表 source 投影,只暴露 publication_status = Published 的作品卡片字段;统一公开列表主路径通过 public_work_gallery_entry 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布和运行态仍按 procedure 路径处理。
  • Rust viewjump_hop_gallery_view
  • 返回类型:Vec<JumpHopGalleryViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/jump_hop.rs
  • 说明:跳一跳公开详情兼容投影,包含作品、路径和素材字段;统一公开详情主路径通过 public_work_detail_entry 消费该 view,只保留平台详情页展示摘要。
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

wooden_fish_agent_session

  • Rust 结构体:WoodenFishAgentSessionRow
  • 源码:server-rs/crates/spacetime-module/src/wooden_fish/tables.rs

wooden_fish_event

  • Rust 结构体:WoodenFishEventRow
  • 源码:server-rs/crates/spacetime-module/src/wooden_fish/tables.rs

wooden_fish_runtime_run

  • Rust 结构体:WoodenFishRuntimeRunRow
  • 源码:server-rs/crates/spacetime-module/src/wooden_fish/tables.rs

wooden_fish_work_profile

  • Rust 结构体:WoodenFishWorkProfileRow
  • 源码:server-rs/crates/spacetime-module/src/wooden_fish/tables.rs
  • 说明:敲木鱼作品 profile 真相,包含敲击物图案、背景环境图、主题返回按钮图、敲击音效、飘字配置、发布状态和公开计数;background_asset_json 保存 image2 生成的 9:16 背景环境图资产快照,back_button_asset_json 保存 image2 生成并去绿后的 1:1 返回按钮图资产快照,旧迁移数据按 None 兼容。
  • Rust viewwooden_fish_gallery_card_view
  • 返回类型:Vec<WoodenFishGalleryCardViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/wooden_fish.rs
  • 说明:敲木鱼公开列表 source 投影,只暴露 publication_status = published 的作品卡片字段;统一公开列表主路径通过 public_work_gallery_entry 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布和运行态仍按 procedure 路径处理。
  • Rust viewwooden_fish_gallery_view
  • 返回类型:Vec<WoodenFishGalleryViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/wooden_fish.rs
  • 说明:敲木鱼公开详情兼容投影,包含敲击物图案、背景环境图、主题返回按钮图、敲击音效和飘字配置;统一公开详情主路径通过 public_work_detail_entry 消费该 view,只保留平台详情页展示摘要。
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

match3d_agent_message

  • Rust 结构体:Match3DAgentMessageRow
  • 源码:server-rs/crates/spacetime-module/src/match3d/tables.rs

match3d_agent_session

  • Rust 结构体:Match3DAgentSessionRow
  • 源码:server-rs/crates/spacetime-module/src/match3d/tables.rs

match3d_runtime_run

  • Rust 结构体:Match3DRuntimeRunRow
  • 源码:server-rs/crates/spacetime-module/src/match3d/tables.rs

match_3_d_work_profile

  • Rust 结构体:Match3DWorkProfileRow
  • Rust accessormatch_3_d_work_profile
  • 源码:server-rs/crates/spacetime-module/src/match3d/tables.rs
  • 兼容说明:dev 现有 SpacetimeDB 元数据中的真实表名 / 索引名为 match_3_d_work_profilematch_3_d_work_profile_*_idx_btree。module 内部 accessor 必须与该 canonical name 对齐,避免 Rust SDK 在 index_id_from_name 初始化二级索引时查找 match3d_work_profile_* 并触发 No such index panic。migration.rs 仍兼容旧迁移包中的 match3d_work_profile 表名补默认字段。
  • Rust viewmatch3d_gallery_view
  • 返回类型:Vec<Match3DGalleryViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/match3d.rs
  • 说明:抓大鹅公开 source 投影,只暴露 publication_status = published 的作品卡片字段;统一公开列表 / 详情主路径通过 public_work_gallery_entry / public_work_detail_entry 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

npc_state

  • Rust 结构体:NpcState
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

player_progression

  • Rust 结构体:PlayerProgression
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

profile_dashboard_state

  • Rust 结构体:ProfileDashboardState
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

profile_daily_free_points

  • Rust 结构体:ProfileDailyFreePoints
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:每日免费泥点事实源。day_key 使用北京时间业务日,基础发放量读取 profile_wallet_config.daily_free_points_per_day(未配置时默认 20),remaining_points 保存当日剩余额度;跨业务日退款的每日免费消费部分会叠加到退款当日,使 granted_pointsremaining_points 可暂时超过当前基础发放量,下一业务日首次触达时旧余额与叠加量一并失效并按当时最新配置重置。

profile_feedback_submission

  • Rust 结构体:ProfileFeedbackSubmission
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

profile_invite_code

  • Rust 结构体:ProfileInviteCode
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 生效时间:starts_at / expires_at 均可为空;两者同时存在时必须满足 starts_at < expires_at。开始时刻计入有效区间,截止时刻不计入有效区间。

profile_code_operation

  • Rust 结构体:ProfileCodeOperation
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:后台兑换码 / 邀请码的持久操作记录,记录新增、更新、停用的码值、操作人和操作时间。

profile_membership

  • Rust 结构体:ProfileMembership
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:会员有效期和当前周期限时泥点事实源。started_at/expires_at 表示会员有效期,cycle_started_at/cycle_resets_at/cycle_period_days 表示当前周期,cycle_granted_points/cycle_remaining_points 表示当前周期已发放和剩余限时泥点。

profile_recharge_product_config

  • Rust 结构体:ProfileRechargeProductConfig
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:泥点和会员充值商品配置真相源,供充值中心展示、下单校验、支付确认和后台“充值商品”页维护;当前公开充值中心只展示四档泥点商品,会员商品配置留存但不公开购买或升级入口。
  • 字段补充:会员商品追加 membership_period_pointsmembership_period_daysmembership_queue_limitmembership_discount_bps;泥点商品这些字段必须为 0

profile_played_world

  • Rust 结构体:ProfilePlayedWorld
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

profile_recharge_order

  • Rust 结构体:ProfileRechargeOrder
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:账户充值订单事实源。status 包含 pendingpaidfailedclosedrefundedexpired;过期补偿字段 expired_atexpiration_checked_atexpiration_provider_stateexpiration_last_error 用于记录本地过期和微信查单结果。

profile_recharge_refund

  • Rust 结构体:ProfileRechargeRefund
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:普通微信支付 V3 退款单聚合。以 out_refund_no 为主键、provider_refund_id 唯一,保存原订单/微信支付单、四个分金额、微信状态、最近观察、权益目标/已回收/未回收量和人工处理错误码。管理员读取契约同步暴露微信交易号、订单总额和人工复核获批错误码。交易号或订单总额冲突经管理员确认归属后,追加保存处理管理员、非空原因、处理时间和本次获批的错误码;四字段只写一次,作为重新执行正式结算的审计事实。
  • 索引:by_profile_recharge_refund_order_idby_profile_recharge_refund_status_updated_at

profile_recharge_refund_observation

  • Rust 结构体:ProfileRechargeRefundObservation
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:退款事实的追加观察记录。保存 callback / api_request / query / trade_bill 来源、按“来源 + 事实指纹”稳定生成的 observation ID、金额、状态、脱敏通知引用、事实指纹和 resolution code;同一事实从不同来源进入时使用不同 ID,不保存回调密文、签名、密钥、原始 CSV 或短时下载 URL。
  • 索引:by_profile_recharge_refund_observation_out_refund_noby_profile_recharge_refund_observation_order_id

profile_recharge_order_refund_settlement

  • Rust 结构体:ProfileRechargeOrderRefundSettlement
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:原充值订单维度的退款与权益结算摘要,保存累计成功退款金额、目标/已回收/未回收泥点、权益状态和钱包冻结事实。部分退款不改订单终态;累计全额才把订单改为 refunded
  • 索引:主键 order_idby_profile_recharge_order_refund_settlement_user_id 用于钱包消费前检查退款欠款。

profile_recharge_refund_hold

  • Rust 结构体:ProfileRechargeRefundHold
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:后台主动退款调用微信前的永久泥点占用。活动占用保护退款所需泥点但不改钱包总额;匹配退款成功后结算,关闭或确认未创建的退款释放。相同 out_refund_no 只允许内容完全一致的幂等重放。
  • 索引:主键 out_refund_noby_profile_recharge_refund_hold_order_idby_profile_recharge_refund_hold_user_idby_profile_recharge_refund_hold_status

profile_wallet_manual_restriction

  • Rust 结构体:ProfileWalletManualRestriction
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:后台人工钱包冻结当前态,保存冻结原因、创建/更新管理员和时间。它不承载退款欠账;退款欠账仍来自订单退款 settlement,两个来源任一有效都阻断普通消费。
  • 索引:主键 user_id

profile_recharge_refund_bill_checkpoint

  • Rust 结构体:ProfileRechargeRefundBillCheckpoint
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:按交易账单日期记录已完成的退款 reconciliation,保存账单 SHA1(确认稳定无账单时保存 NO_STATEMENT_EXIST)、处理退款行数和完成时间。任一退款行失败时不写完成 checkpoint,成功前缀依赖 observation 幂等重放;重复下载、多实例执行或进程重启不会重复回收权益。

profile_recharge_order_expiration_schedule

  • Rust 结构体:ProfileRechargeOrderExpirationSchedule
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:旧普通微信充值订单到期查单调度表,保留 schema 兼容但当前不再写入;当前过期路径改由原生 scheduled 表 profile_recharge_order_expiration_timer 触发。

profile_recharge_order_expiration_timer

  • Rust 结构体:ProfileRechargeOrderExpirationTimer
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:充值订单原生 scheduled 表,scheduled_at 到点触发 expire_profile_recharge_order_timerorder_id 唯一,支付成功、本地关闭或 scheduled reducer 执行后删除对应行。

profile_redeem_code

  • Rust 结构体:ProfileRedeemCode
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 生效时间:复用邀请码时间窗口语义,starts_at / expires_at 均可为空;两者同时存在时必须满足 starts_at < expires_at。开始时刻计入有效区间,截止时刻不计入有效区间。用户兑换时以后端接收的 redeemed_at_micros 判定:未到开始时间拒绝为“兑换码未生效”,到达或超过截止时间拒绝为“兑换码已过期”。
  • 后台契约:AdminUpsertProfileRedeemCodeRequest 通过可空 startsAt / expiresAt 接收 RFC3339 时间,列表与保存响应同步返回这两个字段;后台页只负责输入、回填和显示,真正兑换判定留在后端事务路径。

profile_redeem_code_usage

  • Rust 结构体:ProfileRedeemCodeUsage
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

profile_referral_relation

  • Rust 结构体:ProfileReferralRelation
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

profile_save_archive

  • Rust 结构体:ProfileSaveArchive
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

profile_task_config

  • Rust 结构体:ProfileTaskConfig
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

profile_task_progress

  • Rust 结构体:ProfileTaskProgress
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

profile_task_reward_claim

  • Rust 结构体:ProfileTaskRewardClaim
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

profile_wallet_ledger

  • Rust 结构体:ProfileWalletLedger
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 说明:账号钱包流水表。created_at 表示钱包事务实际结算时间,列表先按当前余额反向校验 balance_after - amount_delta 的结算链,再以该时间倒序兜底,避免支付回调或退款重放延迟时出现余额顺序倒置;支付平台确认时间继续保存在充值订单 paid_atmetadata_json 为可选 JSON 对象字符串,旧行缺失时读取层按 {} 归一;外部生成扣费 / 退款写入 externalGenerationJobId,使退款记录可以追溯到对应 external_generation_job

profile_wallet_consumption_total

  • Rust 结构体:ProfileWalletConsumptionTotal
  • 源码:server-rs/crates/spacetime-module/src/runtime/active/profile.rs
  • 作用:用户历史消费泥点累计投影。已有投影时,只在 asset_operation_consume 负向流水成功落账时同事务按主键 O(1) 累加,退款不回减;用户详情按 user_id 主键读取。首次上线在停写维护窗口由 owner 管理接口调用 admin_initialize_profile_wallet_consumption_projections_and_return,全表扫描一次,为所有已有钱包流水的用户建立存量投影。维护遗漏或新用户缺行时,首次消费与管理员钱包详情读取都可按 profile_wallet_ledger.user_id 索引兜底重建一次;消费事务中的重建结果已包含当前流水,不再额外叠加当前金额。持有独立操作权限的管理员显式触发手动对账时,admin_reconcile_profile_wallet_consumption_and_return 扫描该用户权威流水并覆盖校准投影,记录 last_reconciled_by_admin_user_idlast_reconciled_at

asset_operation_wallet_settlement

  • Rust 结构体:AssetOperationWalletSettlement
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 说明:资产操作 consume/refund 配对结算事实表,主键为 consume ledger ID,并保存配对 refund ledger、用户、金额和结算时间。退款先到且 consume 尚不可见时,该表作为持久化取消 intent;迟到 consume 必须检测该行并拒绝扣费,避免 worker 崩溃重领期间双扣。
  • 索引:主键 consume_ledger_id

profile_wallet_refund_outbox

  • Rust 结构体:ProfileWalletRefundOutbox
  • 源码:server-rs/crates/spacetime-module/src/runtime/active/profile.rs
  • 说明:跨节点资产退款的正式 pending 队列。主键为 refund ledger ID,保存 consume/refund 配对、用户、金额、资源、生成任务 attempt、失败原因和重试时间;失败事务先写入该表,worker 在 SpacetimeDB 事务内幂等执行钱包退款并删除成功行。只有数据库不可达时,api-server 才使用本机 wallet-refund-outbox emergency spool;本机 MAX_BYTES 达到阈值时改写入同目录 refund-overflow-* 溢出文件,保持可恢复而不静默丢弃。
  • 索引:(status, available_at)

profile_wallet_config

  • Rust 结构体:ProfileWalletConfig
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 作用:账号钱包全局配置真相源,当前维护新账号注册初始泥点数;表为空时业务回退 100 泥点。

public_work_like

  • Rust 结构体:PublicWorkLike
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

public_work_play_daily_stat

  • Rust 结构体:PublicWorkPlayDailyStat
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs

puzzle_agent_message

  • Rust 结构体:PuzzleAgentMessageRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle.rs

puzzle_agent_session

  • Rust 结构体:PuzzleAgentSessionRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle.rs

puzzle_background_compile_task

  • Rust 结构体:PuzzleBackgroundCompileTaskRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle.rs
  • 说明:拼图首图后台生成的跨 api-server 实例互斥 claim 表,只保存活动任务租约,不表达最终生成结果;task_id 为主键,claim_id 用于释放时防止误删新租约,租约超时时间为 30 分钟。

puzzle_event

  • Rust 结构体:PuzzleEvent
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle.rs

puzzle_leaderboard_entry

  • Rust 结构体:PuzzleLeaderboardEntryRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle.rs

puzzle_runtime_run

  • Rust 结构体:PuzzleRuntimeRunRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle.rs

puzzle_work_profile

  • Rust 结构体:PuzzleWorkProfileRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle.rs
  • 说明:拼图作品 profile 表,保存草稿 / 已发布作品的标题、作者、关卡、封面、发布状态、可见性、基础游玩数、点赞数、改造数和积分激励领取状态。
  • 字段变更:visible 控制是否进入公开列表 / 详情、通关后的推荐下一作品候选、公开点赞 / Remix 和正式公开 runtime;新作品默认 false,旧迁移数据按历史公开默认补 true。后台隐藏后作品可保留 publication_status = Published,但公开消费路径必须按 Published + visible=true 判断。

puzzle_clear_agent_session

  • Rust 结构体:PuzzleClearAgentSessionRow
  • 源码:server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs
  • 说明:拼消消创作会话表,保存轻表单草稿、生成状态、已发布 profile 关联和更新时间;只由拼消消 procedure 读写。

puzzle_clear_work_profile

  • Rust 结构体:PuzzleClearWorkProfileRow
  • 源码:server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs
  • 说明:拼消消作品 profile 表,保存中央底图资产、4 张素材工作表切片后合成的最终 atlas、35 个复合图案组、95 个 1x1 卡牌切片、卡背占位图、发布状态、可见性和基础 play count;公开列表 / 详情只通过 read model 消费,不让前端直接订阅源表。
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

puzzle_clear_runtime_run

  • Rust 结构体:PuzzleClearRuntimeRunRow
  • 源码:server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs
  • 说明:拼消消正式 runtime run 表,保存当前关卡、已消除次数、棋盘 snapshot、开始 / 完成时间和 run 状态;正式胜负、重试、完成、超时和交换结果以后端 procedure 裁决为准。

puzzle_clear_event

  • Rust 结构体:PuzzleClearEventRow
  • 源码:server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs
  • 说明:拼消消基础 runtime 事件表,记录 published run 的开局、关卡完成、全局完成、失败、超时和消除统计来源;首版不做排行榜。
  • Rust viewpuzzle_clear_gallery_view
  • 返回类型:Vec<PuzzleClearGalleryViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle_clear.rs
  • 说明:拼消消公开详情 source 投影,只暴露 publication_status = publishedvisible = true 的作品,包含 atlas、底图、图案组和卡牌切片等详情级字段;统一公开详情主路径通过 public_work_detail_entry 消费该 view,只保留平台详情页展示摘要。
  • Rust viewpuzzle_clear_gallery_card_view
  • 返回类型:Vec<PuzzleClearGalleryCardViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle_clear.rs
  • 说明:拼消消公开列表 source 投影,只暴露平台卡片需要的公开字段;统一公开列表主路径通过 public_work_gallery_entry 消费该 view/api/runtime/puzzle-clear/gallery 保留玩法专属 HTTP shape。
  • Rust viewpuzzle_gallery_view
  • 返回类型:Vec<PuzzleWorkProfile>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle.rs
  • 说明:拼图广场公开详情 source / 兼容投影,只暴露 publication_status = Publishedvisible = true 的作品,但返回完整 PuzzleWorkProfile,包含 levels / anchor_pack 等详情级字段;统一公开详情主路径通过 public_work_detail_entry 消费该 view,只保留平台详情页展示摘要。
  • Rust viewpuzzle_gallery_card_view
  • 返回类型:Vec<PuzzleGalleryCardViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/puzzle.rs
  • 说明:拼图公开列表 source 投影,只暴露 publication_status = Publishedvisible = true 的公开字段,不携带 levels / anchor_pack 等详情级载荷;统一公开列表主路径通过 public_work_gallery_entry 消费该 view/api/runtime/puzzle/gallery 保留旧 HTTP shape,并从统一 public cache 映射回 PuzzleGalleryResponse

拼图公开列表 HTTP 窗口缓存

  • 接口:GET /api/runtime/puzzle/gallery
  • 响应契约:保留 items 字段兼容旧前端;当前 items 只返回前 10 个完整卡片,新增 previewRefs 返回后 10 个 workId/profileId 引用,并返回 hasMorenextCursortotalCount
  • 缓存策略:api-serverPuzzleGalleryCache 中缓存最终 PuzzleGalleryResponse 的预序列化 data JSON。缓存 miss / 过期时单飞重建,避免并发请求重复排序、映射、DTO 深拷贝和 serde_json::Value 树构造;开启响应 envelope 时只按请求拼接轻量 meta,缓存短 TTL 刷新 recentPlayCount7d,后台 cleanup task 周期清理超过最大空闲窗口的旧响应。OTLP 通过 genarrative.puzzle_gallery.cache.*genarrative.spacetime.read.*genarrative.http.server.response_bodies.in_flightgenarrative.http.server.request_permits.available 区分缓存重建、SpacetimeDB 本地订阅读、响应 body 生命周期和 HTTP 背压状态。
  • 详情路径:公开详情、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理;前端拿到 previewRefs 后如果需要展开更多内容,应优先使用后续列表窗口能力或详情 cache,不要把自动详情预取变成新的 procedure 热点。

api-server 长期订阅读模型

现役覆盖:spacetime-client 不再把旧创作入口配置、公开作品聚合表或逐玩法 gallery view 作为连接池必需订阅。REQUIRED_CACHED_READ_MODEL_QUERIES 当前为空,user_account 仅作为认证兼容所需的可选缓存。新版 /creation 的公开内容通过可选鉴权的 GET /api/editor/showcase/resources 读取编辑器精选 read model;登录态的 viewer like 投影在请求事务内读取,不进入共享长期订阅 cache,HTTP 响应固定 private, no-store 并按 Authorization 区分。旧作品、入口配置和玩法统计表只保留 schema 数据壳,不再形成订阅 cache、BFF 路由或前端契约。

退役前历史订阅清单

spacetime-client 建立每个池连接时会等待下列订阅初始同步:

  • SELECT * FROM public_work_gallery_entry
  • SELECT * FROM public_work_detail_entry
  • SELECT * FROM bark_battle_gallery_view
  • SELECT * FROM puzzle_gallery_card_view
  • SELECT * FROM puzzle_clear_gallery_card_view
  • SELECT * FROM jump_hop_gallery_card_view
  • SELECT * FROM wooden_fish_gallery_card_view
  • SELECT * FROM custom_world_gallery_entry
  • SELECT * FROM match_3_d_gallery_view
  • SELECT * FROM square_hole_gallery_view
  • SELECT * FROM visual_novel_gallery_view
  • SELECT * FROM big_fish_gallery_view
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle'
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'jump-hop'
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'wooden-fish'
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'custom-world'
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'match3d'
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'square-hole'
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'visual-novel'
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'big-fish'
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'bark-battle'
  • SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle-clear'
  • SELECT * FROM creation_entry_config
  • SELECT * FROM creation_entry_type_config

private asset_object 不进入长期订阅;安全判断通过受限 procedure 在服务端按主键或 (bucket, object_key) 索引读取事务内真相,避免全表复制、订阅失败和增量同步延迟把已登记私有对象误判为 legacy 未登记对象。

跨玩法公开作品列表 / 详情主读模型是 public_work_gallery_entrypublic_work_detail_entry。拼图、自定义世界等旧玩法公开列表 HTTP 路由保留原响应 shape,由 BFF mapper 从统一 public cache 映射回当前 DTO;旧 *_gallery_card_view / *_gallery_view / custom_world_gallery_entry 继续作为 source view 和兼容缓存。各玩法的个人作品列表、详情、发布、点赞、游玩记录、Remix 和其它需要鉴权或写入副作用的路径继续走 procedure / reducer;不要为了公开列表性能把这些 owner-specific 或 mutation 语义混进 public view。

GET /api/creation-entry/config、入口熔断和公开作品互动熔断优先从订阅 cache 读取创作入口配置;cache 缺失时使用最近一次成功读取的内存快照,再兜底调用 get_creation_entry_config procedure 完成空库种子或旧库兼容。 入口配置快照包含 start card、类型弹窗、公告位兼容字段、入口类型列表和 publicWorkInteractions 作品互动矩阵;入口类型列表新增 category_idcategory_labelcategory_sort_order 后,后台 upsert、shared-contractsmodule-runtimespacetime-client binding 必须同步,旧迁移 JSON 通过 migration.rs 默认值兼容。作品互动矩阵是全局公开作品详情能力配置,不属于单个 creation_entry_type_config;后台通过 /admin/api/creation-entry/config/interactions 保存,前端据此隐藏或拦截已接入的点赞 / Remix 入口,api-server 同时对已接入后端动作执行 public_work_interaction_disabled 熔断。

RPG 创作入口的配置 ID 是 rpg,当前 visible=trueopen=true;历史 custom-world 路由仍是 RPG 的工程域与运行态源类型。入口熔断把 /api/runtime/custom-world*/api/story/*/api/runtime/chat/* 统一映射到 rpg,不要新增平行 airp 路由或用 airp 接管当前文字冒险链路。

旧结构化创作和 RPG 的 LLM JSON 链路及其 Responses web_search 配置已退出 api-server 编译目标;不得为历史源码恢复 GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLEDGENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED

统一公开作品 BFF 的历史契约曾使用 GET /api/public-worksGET /api/public-works/{publicWorkCode};当前是否挂载以 api-server/src/app.rs 为准,当前平台入口不再恢复旧公开作品页面。若后续重新开放公开 read model,只能订阅 public_work_gallery_entry / public_work_detail_entry 这类稳定投影,不能订阅领域源表后自行拼装列表。

  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

quest_log

  • Rust 结构体:QuestLog
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

quest_record

  • Rust 结构体:QuestRecord
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

refresh_session

  • Rust 结构体:RefreshSession
  • 源码:server-rs/crates/spacetime-module/src/auth/tables.rs
  • 认证工作集只保留 active 会话以及最近 24 小时内的 revoked / expired 会话;超过宽限期的失效会话在 refresh session 写路径和 projection 导出前从内存索引移除,并随下一次 typed projection 同步从正式表清理。该清理不改变 active 多端登录、单端登出或全端登出语义。

runtime_setting

  • Rust 结构体:RuntimeSetting
  • 源码:server-rs/crates/spacetime-module/src/runtime/active/settings.rs
  • 说明:这是现役账号级公共设置表,保存 music_volumeplatform_theme,不是旧玩法运行态数据壳。表结构与历史数据保持不变;鉴权后的 GET/PUT /api/runtime/settings 只能经 spacetime-client facade 调用 get_runtime_setting_or_defaultupsert_runtime_setting_and_return procedure 读写,不从连接订阅 cache 或前端本地状态伪造正式设置事实。

runtime_snapshot

  • Rust 结构体:RuntimeSnapshotRow
  • 源码:server-rs/crates/spacetime-module/src/runtime/snapshots.rs

square_hole_agent_message

  • Rust 结构体:SquareHoleAgentMessageRow
  • 源码:server-rs/crates/spacetime-module/src/square_hole/tables.rs

square_hole_agent_session

  • Rust 结构体:SquareHoleAgentSessionRow
  • 源码:server-rs/crates/spacetime-module/src/square_hole/tables.rs

square_hole_runtime_run

  • Rust 结构体:SquareHoleRuntimeRunRow
  • 源码:server-rs/crates/spacetime-module/src/square_hole/tables.rs

square_hole_work_profile

  • Rust 结构体:SquareHoleWorkProfileRow
  • 源码:server-rs/crates/spacetime-module/src/square_hole/tables.rs
  • Rust viewsquare_hole_gallery_view
  • 返回类型:Vec<SquareHoleGalleryViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/square_hole.rs
  • 说明:方洞挑战公开 source 投影,只暴露 publication_status = published 的作品卡片字段;统一公开列表 / 详情主路径通过 public_work_gallery_entry / public_work_detail_entry 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

story_event

  • Rust 结构体:StoryEvent
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

story_session

  • Rust 结构体:StorySession
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

tracking_daily_stat

  • Rust 结构体:TrackingDailyStat
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 写入:由单条或批量 tracking procedure 在同一事务中随 tracking_event 更新,作为运营查询和个人任务进度的聚合投影。

tracking_event

  • Rust 结构体:TrackingEvent
  • 源码:server-rs/crates/spacetime-module/src/runtime/profile.rs
  • 写入:关键业务埋点同步调用单条 procedure;普通 HTTP route tracking 由 api-server 本机 outbox 批量调用 record_tracking_events_and_return。outbox 到达批量阈值时先封存 active 文件并切新 active,后台 worker 异步 flush sealed 文件,HTTP 请求线程不等待 SpacetimeDB。FLUSH_INTERVAL_MS 只负责兜底封存长时间未满批的 active 文件,MAX_BYTES 只做磁盘保护阈值。event_id 必须稳定且全局唯一,批量重试时用唯一索引做幂等跳过。
  • 外部 API 失败:event_key = external_api_call_failure 使用同一张表落库;它是供应商失败审计事实,不新增 SpacetimeDB 表,查询时按 module_key = 'external-api'scope_kind = module AND scope_id = '<provider>' 过滤。

treasure_record

  • Rust 结构体:TreasureRecord
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/gameplay.rs

user_account

  • Rust 结构体:UserAccount
  • 源码:server-rs/crates/spacetime-module/src/auth/tables.rs
  • 职责:账号资料真相源,保存 phone_number_e164display_nameavatar_urlcreated_at、登录状态、密码 hash、token version 和账号标签。module-auth 进程内工作集必须通过 typed projection 与该表同步,不得再经 auth JSON 快照回灌。

user_browse_history

  • Rust 结构体:UserBrowseHistory
  • 源码:server-rs/crates/spacetime-module/src/runtime/browse_history.rs

visual_novel_agent_message

  • Rust 结构体:VisualNovelAgentMessageRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/visual_novel.rs

visual_novel_agent_session

  • Rust 结构体:VisualNovelAgentSessionRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/visual_novel.rs

visual_novel_runtime_event

  • Rust 结构体:VisualNovelRuntimeEvent
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/visual_novel.rs

visual_novel_runtime_history_entry

  • Rust 结构体:VisualNovelRuntimeHistoryEntryRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/visual_novel.rs

visual_novel_runtime_run

  • Rust 结构体:VisualNovelRuntimeRunRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/visual_novel.rs

visual_novel_work_profile

  • Rust 结构体:VisualNovelWorkProfileRow
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/visual_novel.rs
  • Rust viewvisual_novel_gallery_view
  • 返回类型:Vec<VisualNovelGalleryViewRow>
  • 源码:server-rs/crates/spacetime-module/src/legacy_schema/visual_novel.rs
  • 说明:视觉小说公开 source 投影,只暴露 publication_status = published 的作品卡片字段,不把完整 draft 暴露给公开列表订阅;统一公开列表 / 详情主路径通过 public_work_gallery_entry / public_work_detail_entry 消费该 view 并映射成跨玩法契约。个人历史、详情、运行态和发布仍按原有 procedure / reducer 路径处理。
  • 字段变更:visible 控制是否进入公开列表 / 详情,新作品默认 false;旧迁移数据由 migration.rs 按历史公开默认补 true

2026-09-03 修订:认证成功后的 Router provisioning 改为异步尽力修复,不再阻塞主站登录;LLM 热路径只读取本地已完成的账号密钥,控制面不可用时请求失败关闭。provisioning secret 仅从部署侧受保护环境变量或 secret file 读取,不再内置源码常量。