snapper 在采样后、编码前按单一整数 N 做 nearest 放大,保持逻辑图宽高比 生成 pixelArt 与手动完美像素共用该输出,禁止非整数拉回精确源尺寸 手动入口算法指纹升级为 perfect-pixel-v3 同步现役文档与决策记录,并更新被新尺寸带崩的旧断言
231 KiB
server-rs 与 SpacetimeDB 数据契约
2026-07-18 状态更新:旧创作入口、全部模板业务 API/worker/运行态及 SpacetimeDB 业务逻辑已退役。本文逐玩法路由、流程和 DTO 章节仅作为历史设计记录;相关持久化表仍按原结构作为最小 schema 数据壳编译,当前编译与运行边界以
server-rs/Cargo.toml、server-rs/crates/api-server/src/app.rs和docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md为准。
更新时间:2026-07-23
后端主线
当前后端固定为:
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 spacetimedb、spacetimedb-sdk、spacetimedb-lib 统一锁定 2.7.0;本地 spacetime CLI / standalone、生成的 spacetime-client bindings 和容器压测镜像也必须与 server-rs/Cargo.toml 锁定版本对齐,避免 BSATN / procedure result 反序列化错配。2.7.0 官方 CLI / standalone 发行包与容器镜像使用 v2.7.0-hotfix3 资产标签,二进制版本仍为 2.7.0;不得回退使用缺少后续 backing-view 迁移修复的裸 tag 构建。遇到版本不匹配时,不继续沿着业务超时排查,先把 CLI / standalone 直接升级到锁定版本并重启后再重试。2.6.1 还修复了 procedure context 中调用者 Identity / ConnectionId 丢失问题,因此依赖调用者身份的 procedure 不得继续运行在 2.6.0 standalone 上。
当前主要 crate:
- HTTP 与运维入口:
api-server、pingora-gateway、server-manager-panel。 - 现役领域模块:
module-ai、module-assets、module-auth、module-editor-agent、module-runtime。module-runtime继续承载账号、钱包、公共设置、追踪、功能门禁等现役平台领域能力;该 crate 名称不代表旧玩法 runtime 路由仍在运行。 - 平台副作用:
platform-agent-harness、platform-editor-agent、platform-auth、platform-audio、platform-hyper3d、platform-image、platform-llm、platform-matting、platform-oss、platform-speech、platform-wechat。已退役 Creative Agent 的旧platform-agent只保留历史源码,不属于现役 workspace 或依赖图。 - 共享层:
shared-contracts、shared-kernel、shared-logging。 - SpacetimeDB:
spacetime-client、spacetime-module。 - 测试支撑:
tests-support。
server-rs/Cargo.toml 的 exclude 中所列旧玩法纯业务 crate 仅保留源码,不是 workspace member、default member 或现役依赖。
DDD 边界
module-*不直接依赖 Axum、SpacetimeDB table/reducer/procedure、reqwest、OSS、LLM、spacetime-client、tokio或文件系统。- 业务规则、命令校验、领域错误和可纯测逻辑优先沉到
module-*。 - SpacetimeDB 表结构、reducer、procedure 和持久化 row shape 留在
spacetime-module。 - 后端访问 SpacetimeDB 必须经
spacetime-clientfacade。 - HTTP 鉴权、BFF 聚合、SSE、外部模型编排、OSS 上传和第三方回调在
api-server。 - 前端共享 DTO 通过
shared-contracts和packages/shared对齐,不在页面内重新发明旧接口;packages/shared还可复用无业务真相的 UI 组件和纯工具,领域规则、后端副作用与正式状态仍留在各自分层。 - 微信能力按两层收口:
server-rs/crates/platform-wechat承载现役微信支付 V3 / 虚拟支付查单和发货确认的 HTTP header、签名、验签、解密、mock 响应和协议 payload 解析;server-rs/crates/api-server/src/wechat.rs与wechat/*承载 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;原 src/mapper.rs 及旧玩法 mapper 源码仅供历史追溯,不参与 crate 编译。
当前 mapper 按现役调用领域拆分为 admin_account.rs、admin_dashboard.rs、ai.rs、assets.rs、auth.rs、editor_agent.rs、editor_project.rs、external_api_key.rs、external_generation.rs、runtime.rs 和 runtime_profile.rs;story.rs 只承接必要的历史资产记录兼容映射,不恢复旧故事业务 facade。跨领域轻量 helper 和共享 record 放在 common.rs;不得重新引入旧玩法 mutation 或跨层 JSON 兼容结构。
API 路由分组
路由树由 server-rs/crates/api-server/src/app.rs 统一构造。当前主要分组:
- 健康检查:
GET /healthz、GET /readyz。 - 后台管理:
/admin/api/*,现役路由包括登录与账号管理、Dashboard / 概览、HTTP debug、埋点、表查询、通用 feature gate、编辑器定价与素材 / 精选管理,以及账号侧兑换码、邀请码、任务、钱包、充值与退款管理;不再挂载旧创作入口配置、旧作品互动或旧玩法运营路由。环境变量管理员固定作为 owner,持久化 member 每次请求按当前enabled、token_version和一级 Tab 权限实时校验;账号管理仅 owner 可访问,未登记权限映射的新后台路由对 member 默认拒绝。完整权限矩阵见docs/technical/【后台管理】多账号与Tab访问权限方案-2026-07-14.md,Dashboard 指标口径见docs/technical/【后台管理】Dashboard运营看板方案-2026-06-23.md。 - 通用灰度控制面固定为后台
#gray-release与GET/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逐对象授权,不从imageSrc或generated-*前缀推导宽泛权限;动作快照损坏、隐藏、拒绝或不再满足返还条件时不形成帧授权。只有同 bucket / key 的权威查询确认未登记时,才允许显式legacyPublicPath命中platform_oss::LEGACY_PUBLIC_PREFIXEScurated 白名单后匿名兼容。已登记资产继续保持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-config由api-server从运行时环境变量下发非敏感 UI 开关;画板右侧 Agent 入口由GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR控制,默认关闭。 - 公共设置数据链:
GET/PUT /api/runtime/settings从 access token 取得user_id,经spacetime-clientfacade 调用get_runtime_setting_or_default/upsert_runtime_setting_and_return,读写现役runtime_setting表的music_volume和platform_theme。该表不是历史数据壳,也不得为保留它而恢复任何旧存档、游玩历史或玩法 settings 路由。 - 后台素材查询:
GET /admin/api/editor-assets通过admin_list_editor_assets_and_return后台只读 procedure 读取私有账号级editor_asset中source_type = 'generated'的素材,支持ownerUserId、keyword、createdAfter、createdBefore、cursor和limit;ownerUserId接受内部用户 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_kind;procedure 只有当前 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 映射为character、icon-spritesheet或character-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_url的tracking_event,记录管理员 subject、Object Key / legacy path 和有效期,不记录 signed URL。 - 后台精选审核:
GET /admin/api/editor-showcase/assets的素材 payload 与素材查询共同返回稳定媒体引用,并只透传底层正式thumbnailSrc、imageSequenceFrames与imageSequenceDurationMs;缺少正式动作字段的存量数据必须先经角色动作规范化 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 和支付回调为准。modules/中仍保留但未被app.rs声明 / 合并的 RPG、拼图、Match3D、敲木鱼、方洞、视觉小说、大鱼、跳一跳、汪汪声浪、儿童向、旧公开作品与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负责发送消息并返回普通 JSONEditorAgentMessageResponse,画布 Agent 不提供/messages/streamSSE 路由。消息请求必须携带最长 128 字符的clientMessageId;前端对该 POST 显式启用 1 次瞬时 transport 重试,并复用同一个序列化 body、clientMessageId和x-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显式携带失败前已产生的助手文本、成功工具和结构化失败工具输出。ToolFailure的kind、retryable、fatal及工具原始output必须对 harness 调用方可见,不得在公共层压成字符串或擅自丢弃。- prompt runner 对每个调用通过
AgentMemory::begin_staged创建行为等价、写入隔离的StagedAgentMemory事务:限长、摘要、脱敏或持久化 memory 的 append 语义必须在本轮模型请求前生效,不得统一降级成VecMemory。成功结束或已发生工具活动时必须显式调用 stagedcommit(),直接 drop 表示回滚。无工具活动的 completion、hook、解析或max_turns失败丢弃 staged transaction;已有成功或失败工具活动时在末尾追加 terminal error closure 后提交。外部 future drop / abort 若发生在工具完成后,必须提交工具结果与取消闭环;若工具仍在执行,则提交“已启动、结果未知”事实与取消闭环,供后续 reconcile,不能假装工具没有发生。 - 画布 handler 的 18 分钟总 deadline 通过 runtime 提供的 deadline future 下沉到公共 runner:completion await 可被 deadline 终止;effectful tool 在开始前检查 deadline,开始后不被中途 drop,返回后再携带结果收口为
PromptRunError。禁止用外层 timeout 直接 drop 整个 prompt future 并伪造空partial_outputs。 spacetime-module的editor_agent_conversation只保存元数据;创建、列表、读取、更新时间和软删通过create_editor_agent_conversation_and_return、list_editor_agent_conversations_and_return、get_editor_agent_conversation_and_return、touch_editor_agent_conversation_and_return、delete_editor_agent_conversation_and_returnprocedure 完成,api-server只能经spacetime-clientfacade 访问。- 完整消息文档存 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-miniChat 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、连接失败、408、429与5xx读取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 + referenceId经spacetime-client重新读取当前工程资源或账号素材库,只把权威asset_kind放入服务端内存ImageMetadata。图标 spritesheet 的主参考必须精确为icon-spec,普通图片或风格参考图只能作为额外参考;主参考类型缺失、已删除或不是icon-spec时必须在生成任务入队和用户确认生效前失败关闭,并提示重新选择图标规范。 edit-image只接受当前图片上下文中的object_image_id;source_image_id不是现役 schema 字段,prompt、tool args、确认执行和测试中都不得生成或兼容该字段。edit-image在工具准备阶段生成的 directEditorImageEditRequest只用于待确认消息内部状态,不是 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的待确认消息;再在同一会话增量末尾追加ERRORterminal 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_resource、editor_asset、结构化画布表或external_generation_job的业务真相。未激活结构化存储的 canvas 才继续以editor_canvas.layers_json作为 legacy 布局真相。
创作 / 游玩统一流程主干
历史设计记录:本节所述
play_flow和逐玩法 Adapter 已退役,不代表当前app.rs路由树。
modules/play_flow.rs 是后端创作与游玩流程的统一入口。现有外部 URL、DTO、错误 envelope、鉴权方式、入口开关语义和 SpacetimeDB schema 默认不变,但路由组织必须遵循:
app.rs只合并modules::play_flow::router(state),不直接合并 RPG、拼图、抓大鹅、跳一跳、敲木鱼、拼消消、汪汪声浪、视觉小说或儿童向创作等逐玩法模块。play_flow统一注册每个玩法的playId、领域模块 key、创作路由前缀和运行态路由前缀;后续新增玩法或迁移旧玩法时,先补这个注册表,再挂具体领域模块路由。- 新建创作、首次生成和 Remix 成草稿等会产生新创作的入口开关匹配规则同样归
play_flow管理;creation_entry_config.rs只复用该规则执行open=false熔断,不再维护第二份路径判断。 play_flow在进入领域 handler 前先解析并挂载PlayFlowRequestContext,统一标记请求处于Creation、Runtime、CreationEntryConfig、CreationSupport、RuntimeSupport、AiTask、PublicReadModel或RuntimeInventory阶段,并记录目标playId/ 领域模块 key;领域 handler 可以读取该上下文做后续收口,但不能绕过主干自建平行流程。play_flow只做平台共性编排和领域 Adapter 组合,不下沉玩法规则;最后一步的草稿编译、资产生成、发布、运行态 start/action/finish、计分和排行榜仍交给对应module-*、spacetime-moduleprocedure 和玩法 HTTP handler 处理。- 公开作品聚合、作品详情、运行态库存、运行态设置 / 存档、游玩历史、存档归档、游玩统计、历史素材、AI task、runtime chat、文档解析、角色资产工坊、角色图像 / 动画生成和 Hyper3D 代理属于跨玩法或玩法支撑流程,也从
play_flow主干挂入;modules/platform.rs只保留通用 LLM / 语音代理,不再承接创作 / 游玩支撑路由。 - 如果某个旧玩法仍使用历史
/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,显式值必须使用微信同口径的无加号国家码并且当前只允许86。module-auth先校验国家码,再复用纯手机号规范化规则,最终统一以+86E.164 写入认证投影。微信小程序getPhoneNumber链路仍只接收客户端wechatPhoneCode,后端必须要求微信 provider 成功响应中的phoneNumber、purePhoneNumber与countryCode均存在且非空,并只使用后两项执行国家码校验和 E.164 构造;腾讯未承诺phoneNumber为 E.164,不得依赖其前缀格式,也不得在微信链路默认86。AuthUserPayload/AuthUser只保留前端当前会用到的身份与绑定展示字段:id、publicUserCode、displayName、avatarUrl、phoneNumber、phoneNumberMasked、loginMethod、bindingStatus、wechatBound、wechatDisplayName、wechatAccount。账号信息面板展示微信绑定时优先使用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只保留设备卡片与撤销需要的摘要字段:sessionId、sessionIds、sessionCount、clientLabel、ipMasked、isCurrent、createdAt、lastSeenAt、expiresAt。- 设备诊断信息(例如原始
clientType/clientRuntime/clientPlatform/userAgent/miniProgramAppId/miniProgramEnv/deviceDisplayName)不再默认下发到前端;若未来确需展示,优先单独加窄 DTO,而不是把账号 / 会话快照恢复为全量对象。 - 多端登录语义以
refresh_session为粒度:同一账号可保留多个 active session,普通登录不会撤销旧设备;POST /api/auth/logout只撤销当前 refresh session,不提升token_version;POST /api/auth/logout-all、改密、重置密码才吊销全端 session 并提升token_version。鉴权中间件仍校验 Bearersid对应的 refresh session 是否 active,单独踢下线或当前设备退出可以让目标设备立即失效而不误伤其它设备。
api-server 模块化演进规则
api-server 的长期方向是从超大 app.rs 和超大 handler 文件收敛为按能力组织的 HTTP/BFF Module。后续改造必须保持 HTTP route、DTO、error envelope、SpacetimeDB schema、前端行为和计费语义默认不变;任何 contract 或 schema 变化都要先在当前文档中写清影响范围和迁移计划。
路由模块化规则:
- 每个能力 Module 只暴露
router(state) -> Router<AppState>;平台创作 / 游玩相关 Module 和支撑能力由modules/play_flow.rs统一.merge(...)或在支撑 router 内挂载,其它账号、资产基础、后台和平台基础能力再由app.rs直接合并。 app.rs只保留全局 middleware、TraceLayer、request context、tracking middleware、入口开关和少量顶层 glue;不得重新恢复逐玩法 creation/runtime merge 列表。- 能力 Module 可在路由内部用
FromRef<AppState>派生自己的 Feature State,例如PuzzleApiState。全局AppState仍作为进程组合根、鉴权层和全局中间件状态,但业务 handler 优先只提取对应 Feature State,不直接暴露完整AppState。 - Feature State 只暴露该能力实际需要的 facade / adapter / 配置快照;若必须复用仍要求
AppState的横切 helper(例如计费、外部失败审计或通用 tracking),应通过 Feature State 的窄方法或显式root_state()过渡,并在后续继续收窄。 - 路由迁移和业务重构分阶段处理;先移动路由装配,再拆 handler 内部实现,再收窄 handler 可见状态。
- 大 handler 拆分时优先按
router.rs、handlers.rs、application.rs、assets.rs、mapper.rs、errors.rs分层。handlers.rs只做 Axum extract、鉴权和 request/response,业务规则继续下沉到module-*。 - 手写 Rust 模块入口统一使用同名
.rs文件,例如puzzle.rs+puzzle/*.rs、match3d.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,集中暴露SpacetimeClient、PuzzleGalleryCache、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_requirements 与 api-server::puzzle::tags::is_puzzle_session_snapshot_publish_ready 使用同一资产语言,不得只凭 cover、标题、描述和标签把半成品标为 publishReady 或 ready_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保留运行态轻量归一 helper;mappers.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 规则:
- 稳定单图链路可收敛到
api-server内部生成资产 Adapter:provider 生成、下载/base64 解码、MIME/extension 归一、OSS private upload、HEAD、asset object confirm、entity binding。 - Adapter 输入应显式包含 provider、prompt、reference images、OSS prefix/path/file name、asset kind、entity kind/id、slot、owner/profile/source job、metadata 和可选透明背景后处理。
- Adapter 输出应保留 legacy public path、object key、asset object id、MIME、extension、task id 和实际 prompt。
- Adapter 不负责扣费、退款或钱包读取;计费仍由调用方显式包裹。
- 图片 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 失败审计落库。 - OSS 平台适配日志统一在
server-rs/crates/platform-oss输出,覆盖sign_post_object、sign_get_object_url、head_object和put_object。日志字段固定使用provider、operation、bucket、endpoint、object_key/key_prefix、access、content_type、content_length、status、status_class、error_kind和elapsed_ms,只记录对象定位和排障信息;不得输出 AccessKey、policy、signature、Authorization header 或完整 signed URL。generated 私有对象上传时必须由 OSS 对象头承载浏览器 / CDN 缓存策略,默认写入Cache-Control: public, max-age=31536000, immutable,不得改成 api-server 本地磁盘静态资源兜底。 - Puzzle、Match3D、音频、GLB、视频等复杂媒体可以复用 OSS + asset object + binding 的底层持久化能力,但玩法专属处理规则留在各自编排层,不塞进公共接口。
- 拼图入口页与结果页新增关卡的本地参考图不走浏览器直传 OSS,前端读取为 Data URL 后随创作 action 提交,并在读取前限制 6MB、显示“图片≤6MB”。
api-server必须对 Data URL 实际字节数再次校验;历史图片才提交referenceImageAssetObjectId(s),后端校验asset_object的 bucket、kind、图片 MIME、大小和 owner 后签发只读 URL 给 VectorEngine 读取。 - 系列素材图集实现真相源在
server-rs/crates/platform-image/src/generated_asset_sheets/:调用方必须传入grid_size作为n*n的n,可选传入物品名称 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 变更规则
- 任何 table、view、reducer、procedure、row shape 或 bindings 变化,都必须同步本文件表 / view 目录和生成绑定;真实 table 变化还必须同步
server-rs/crates/spacetime-module/src/migration.rs,view 属于派生投影,不写入迁移导入导出表清单。 - 已有表新增字段必须放在 Rust 表结构体最后,并设置明确
#[default(...)]。 - 已发布并持久化的 enum 新增 variant 只能追加到末尾,不能插入中间、删除、重命名或重排;否则会移动既有 variant 的判别序号,导致旧数据解释错误或生产发布 schema 迁移失败。
- 删除字段、改名、重排字段、改类型或修改字段属性前,必须先询问用户并确认迁移计划。
- Vec 字段不要直接写无法 const 求值的 default;需要默认空集合时优先使用
Option<Vec<T>>加#[default(None::<Vec<T>>)],业务层归一为空数组。 - 运行态读表必须按已声明索引访问。只要 table 上存在覆盖查询前缀的
#[index(...)]或主键 / unique accessor,列表、详情、快照组装和计数都先用对应 accessor.filter(...)/.find(...),再在内存中处理索引无法覆盖的残余条件;不得用.iter().filter(...)扫整表替代现成索引。 - 面向公开列表的只读投影优先做成 public view / public 读模型表,并由
api-server的spacetime-client长期订阅后读本地 cache。跨玩法公开作品统一主读模型是public_work_gallery_entry和public_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_profile、custom_world_profile等领域源表,也不得自己做 join、聚合或权限逻辑。首屏、排序、字段归一、权限降级和 HTTP fallback 由api-serverBFF 维持。 - 多列索引按 SpacetimeDB 绑定生成的元组参数直接传入,例如
.filter((source_type, profile_id, played_day));前缀查询只传前缀元组,例如.filter((scope_kind, scope_id.as_str()))。不要为了绕过类型问题退回整表遍历。 - procedure result 必须返回 typed snapshot / typed value。
spacetime-clientmapper 不得再通过row_json/session_json/work_json/items_json/run_json/event_json/feedback_json: Option<String>做跨层 JSON 字符串传输,也不得在 mapper 里反序列化旧*JsonRecord兼容结构。业务内部持久化字段如profile_payload_json、levels_json等不属于 procedure result 载荷例外,仍按各自表契约处理。 - procedure 需要按调用者 identity 鉴权时,必须先从外层
ProcedureContext::sender()捕获 caller,再把 caller 显式传入try_with_tx闭包内的事务函数。SpacetimeDB 2.6.0 的TxContext使用内部匿名事务身份,tx.sender()固定为Identity::ZERO;2.6.1 已向事务透传 procedure caller,但项目仍保持显式 caller 参数,兼容尚未升级的运行环境并让鉴权边界不依赖 SDK 隐式语义。 - 修改后运行:
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。
账户充值数据契约
profile_recharge_product_config是泥点和会员商品配置真相源,默认商品只在表为空时由 SpacetimeDB 播种。module-runtime中的默认商品 helper 只作为空库种子和兼容入口,不再作为运行期业务真相。- 默认泥点商品固定为四档:
points_60 = 60 泥点 / 600 分、points_180 = 180 + 90 泥点 / 1800 分、points_300 = 300 + 150 泥点 / 3000 分、points_680 = 680 + 340 泥点 / 6800 分。points_60的首充赠送为0;后三档首次购买分别赠送基础泥点的50%。 - 后台通过
/admin/api/profile/recharge-products读写充值商品配置;字段覆盖productId、标题、商品类型、金额分、基础泥点、首充赠送泥点、会员天数、徽标、说明、会员层级、会员每周期限时泥点、周期天数、队列上限、折扣率、启用状态和排序。 - 充值中心、下单校验和支付确认入账都读取
profile_recharge_product_config。充值中心 BFF 还必须在mudPointBalance下发totalPoints、permanentPoints、limitedPoints、limitedExpiresAt、dailyFreePoints、dailyFreeResetPoints和dailyFreeResetsAt;前端以该 read model 为真相源,不得自行用总额相减推算余额桶。当前版本公开 UI 只渲染不限时泥点和每日免费泥点,limitedPoints与limitedExpiresAt仅保留给存量兼容和后端结算。历史订单保留下单时写入的商品标题、金额、渠道、状态和 provider transaction id,不随配置改动回写。 - 泥点首充资格按
user_id + product_id的历史paid订单独立判断。某个档位已支付后,只隐藏该档位的首充赠送;其它未购买档位仍展示和结算首充赠送。 hasPointsRecharged只保留为账号是否发生过任一泥点充值的兼容字段,不得驱动所有商品展示隐藏或结算金额计算。前端只渲染后端返回的商品快照。- 当前版本公开充值 UI 只展示泥点商品,不渲染会员购买页签、会员商品、购买会员或升级会员入口。充值中心响应中的会员商品兼容字段、默认会员商品、
profile_membership和周期刷新逻辑继续保留;存量会员的cycle_remaining_points仍通过充值中心 read model 下发用于兼容和结算,但不作为限时泥点在当前版本前台展示。 - 默认会员商品为空库播种时使用
Starter / Basic / Pro / Ultimate四档,默认有效期均为 30 天,每周期限时泥点分别为200 / 800 / 2500 / 6000,队列上限分别为2 / 2 / 5 / 10。 - 会员有效期和周期重置时间是两条独立时间线。
expires_at只决定会员是否生效;cycle_resets_at只决定当前周期限时泥点何时重置。会员升级只更新档位并补齐当前周期限时泥点差额,不延长expires_at,不移动cycle_resets_at和周期天数。同级会员购买只从当前expires_at延长有效期,不发放额外当前周期泥点,也不移动重置时间。 - 会员周期刷新发生在个人中心、充值中心、任务中心、账单读取和钱包扣费入口:到达
cycle_resets_at时先清除上周期剩余限时泥点,再发放当前会员档位周期额度;会员过期时清除剩余限时泥点并把状态降为普通。周期发放和重置流水分别使用membership_period_grant、membership_period_reset。 paymentChannel缺失、未知或冒用小程序支付设备时必须拒绝;真实微信渠道只允许wechat_mp、wechat_mp_virtual、wechat_jsapi、wechat_h5、wechat_native,生产配置不得把真实支付静默降级为mock。- access JWT 只携带最小设备快照
device.client_type、device.client_runtime、device.client_platform。充值下单按该快照拦截小程序渠道:小程序只允许wechat_mp/wechat_mp_virtual;移动网页和微信内 H5 走wechat_h5;桌面网页和桌面微信走wechat_native;wechat_jsapi仅保留后端能力,未接微信开放平台前不由前端自动选择。历史普通 Web 登录态若缺少设备快照也允许继续进入 JSAPI / H5 / Native 渠道的后续支付配置校验,但不放宽小程序虚拟支付。 - 所有微信真实渠道都以微信支付通知或服务端查单确认
SUCCESS为到账事实;小程序、H5 跳转和 Native 二维码返回都不能直接发放泥点或会员。 - 微信 JSAPI / H5 / 小程序 / Native 下单统一显式传 5 分钟
time_expire,格式为 RFC3339 秒级时间;Native 额外通过wechatNativePayment.expiresAt下发给前端二维码弹窗展示。 - 真实微信渠道的新建 pending 充值订单会写入 SpacetimeDB 原生 scheduled 表
profile_recharge_order_expiration_timer。到期 reducer 只做数据库内状态转换:订单仍为pending时更新为expired并写expired_at,同时删除 timer。HTTPapi-server只订阅这张活跃 timer 表的删除事件,收到order_id后通过 procedure 重新读取订单,只有状态确认为expired才执行微信查单补偿;支付或主动关闭同样会删除 timer,但会被状态判断忽略。监听断线期间遗漏的删除事件由未检查过期订单 catch-up 补齐,不订阅完整profile_recharge_order历史表。普通微信支付查单中SUCCESS可把expired补确认成paid入账;NOTPAY会调用微信关单并把本地订单保持为expired;CLOSED/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_goods从status=2恢复时先幂等入账,再调用/xpay/notify_provide_goods,失败后允许基于本地paid状态只重试发货;short_series_coin不调用现金单发货接口。external-generation-worker/ controller 不处理充值过期。 - 普通微信支付 V3 退款事实由
profile_recharge_refund保存,回调、退款 API 响应、主动查单和交易账单发现统一调用record_profile_recharge_refund_observation_and_return。out_refund_no是商户幂等键,provider_refund_id唯一;退款申请响应和主动查单等生产者生成 observation ID 时必须同时纳入来源和事实指纹,同一来源同一事实稳定重放、不同来源不得复用 ID;重复 observation 必须校验订单、交易、金额、状态、来源和指纹,不允许仅按主键直接吞掉冲突。 profile_recharge_order_refund_settlement按原订单聚合累计成功退款金额和权益回收。部分退款不改充值订单paid;累计金额等于订单金额时才改为refunded。历史支付和首充资格以paid_at是否存在判断,退款不把用户重新变成首充。- 泥点退款按累计成功退款比例计算目标回收量,全额退款强制精确回收原
points_delta。自动回收只扣普通永久泥点,每日免费和会员周期限时泥点保持不变;不足部分持久化为shortfall并冻结正式钱包消费,后续 worker 只重试本地回收。已成功退款的泥点订单若出现微信交易号、订单总额冲突或结算计划无效,必须将 settlement 标记为wallet_frozen并阻断普通消费。管理员只能对交易号或订单总额冲突执行“确认退款归属”:BFF 必须把微信退款事实的交易号、订单总额、当前冲突类型与本地订单值并排下发并展示,提交时携带管理员实际看到的expectedErrorCode,SpacetimeDB procedure 在同一事务内核对当前错误码一致后,才在退款行尾部不可变记录管理员、原因、时间和本次获批的错误码并重新运行标准结算;已处理请求只有管理员、归一化原因和错误码全部一致时才视为幂等重放,状态变化或任一审计内容不一致必须 fail-closed。一次确认只豁免对应的交易号或订单总额冲突,渠道、支付状态及其他未获批冲突继续 fail-closed。能追回的永久泥点照常扣除,余额不足继续形成欠账;不能直接清空冻结或伪造已追回量。非法结算计划仍保持冻结等待数据/代码修复。流水来源为recharge_refund_recovery。会员充值没有可逆 grant 快照,退款统一标记manual_review,不猜测回滚档位、有效期或周期泥点,也不复用泥点退款冻结语义。 - 外部现金退款
SUCCESS必须先持久化并 ACK,即使本地订单缺失、金额冲突、权益不足或会员需要人工处理,也不能回滚已经发生的现金事实。冲突 observation 记录 resolution code 并进入告警;只有验签、解密、契约解析或 SpacetimeDB 持久化失败才让微信重试。 - 主动查询与退款交易账单 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 和十进制定点金额解析;发现手工退款后必须再查单取得当前状态。 - 普通 V3 支付通知同时校验 AppID、商户号、本地订单渠道、金额和微信支付单号;
success_time缺失或非法时拒绝,不能用本机时间补齐。晚到通知遇到refunded订单只做交易号一致性幂等校验,不再次发放权益。 - 后台主动退款只支持
wechat_mp、wechat_jsapi、wechat_h5、wechat_native普通 V3 泥点订单。api-server必须先按正式支付渠道和商品类型拦截不支持的订单,再做微信支付订单查单预检,然后调用 SpacetimeDB procedure 原子创建退款 hold;只有 hold 成功才允许调用微信退款。wechat_mp_virtual、历史非正式渠道值、会员、未支付、对账未完成、退款已满额、人工冻结、退款欠账或永久泥点不足必须在调用普通 V3 provider 前 fail-closed。 profile_recharge_refund_hold以稳定out_refund_no为主键,保存订单、用户、本次退款金额、占用永久泥点、管理员、原因和active / settled / released状态。重试只有在订单、out_refund_no、退款金额、管理员和归一化原因全部与原 hold 一致时才可复用,任一不一致都按幂等内容冲突 fail-closed,不能用新原因调用微信后保留旧审计。部分退款的 hold 在累计应追回增量之外额外保留 1 泥点并发舍入缓冲,全额退款不加缓冲;活动 hold 不改变钱包总额,但普通钱包消费必须预留全部活动 hold;成功退款 observation 扣款并结算匹配 hold,关闭退款释放 hold,外部退款追回不得消耗其他活动 hold。- 退款欠账继续以
profile_recharge_order_refund_settlement.unrecovered_points为唯一真相;不新增平行 debt 累计。profile_wallet_manual_restriction只保存人工冻结,普通消费同时检查人工冻结与退款欠账。后续永久泥点到账后继续偿还欠账,每日免费与会员周期泥点不参与;解除人工冻结不得清除退款欠账限制。 - 管理员充值订单、用户详情、历史花费手动对账、退款预检/执行、应急退款号登记、退款人工复核和钱包冻结接口只留在
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 返回的正式状态。
创作入口泥点扣费契约
creation_entry_type_config.unified_creation_spec_json内的mudPointCost是玩法新建草稿初始生成的泥点成本真相源,同时供入口卡展示和前端余额前置校验使用;旧契约缺失时允许按代码默认成本兜底。api-server执行拼图首图生成、抓大鹅完整草稿生成和汪汪声浪初始三图生成时,必须通过GET /api/creation-entry/config同源配置解析对应玩法成本后再调用钱包扣费 procedure,不得继续使用前端或后端硬编码常量作为实际扣费真相。- 结果页单图重生成、发布、道具使用和其它独立资产操作仍按各自业务操作成本执行;不要把初始草稿成本误套到这些单次操作上。
- 资产操作的预扣费必须 fail-closed:钱包或 SpacetimeDB 预扣费不可达、超时或返回业务错误时,
api-server直接返回错误,不允许继续调用图片、音频、GLB 等外部生成 provider。 - 需要支持 HTTP retry 的计费 ledger id 必须包含当前请求的
request_id;前端fetchWithApiAuth同一次业务请求的静默刷新重试复用同一个x-request-id,后端不得再使用 prompt 指纹或随机 asset id 作为扣费幂等键。 - 外部生成已预扣费但后续失败时必须先同步调用钱包退款;若 SpacetimeDB 暂不可用,退款请求写入
wallet-refund-outbox本地文件并由后台 worker 重放。默认启用,配置项为GENARRATIVE_WALLET_REFUND_OUTBOX_ENABLED、GENARRATIVE_WALLET_REFUND_OUTBOX_DIR、GENARRATIVE_WALLET_REFUND_OUTBOX_BATCH_SIZE、GENARRATIVE_WALLET_REFUND_OUTBOX_FLUSH_INTERVAL_MS和GENARRATIVE_WALLET_REFUND_OUTBOX_MAX_BYTES。outbox 文件按 refund ledger id 幂等落盘;成功重放后删除,坏文件隔离为corrupt-*。外部生成任务触发的扣费和退款必须在profile_wallet_ledger.metadata_json中写入externalGenerationJobId,outbox 重放也必须保留同一任务 ID,便于从退款记录追溯到正式生成任务。 - 拼图首图后台生成的跨实例互斥锁必须落在 SpacetimeDB
puzzle_background_compile_task表,claim id 由task_id + request_id构成,释放时必须校验 claim id,避免旧后台任务释放新请求抢到的租约。
用户钱包与编辑器生成扣费契约
profile_wallet_config是账号初始泥点和每日免费泥点基础发放量的统一真相源;后台通过/admin/api/profile/wallet-config一次读写initialMudPoints和dailyFreePointsPerDay。新用户账号完成注册并成功同步正式认证表后,注册赠送金额读取initial_mud_points;未写入配置时默认为100。每日免费基础发放量未写入时默认为20。注册赠送流水原因仍使用new_user_registration_reward,流水 ID 继续保持幂等,重复发放请求不得叠加余额。- 用户钱包余额对外仍暴露为一个总余额,但后端扣费必须按“每日免费泥点 -> 会员周期限时泥点 -> 普通永久泥点”的顺序消耗,前端不得自行决定扣费桶。扣费流水
metadata_json必须记录dailyFreePointsDelta、dailyFreeDayKey、membershipPeriodPointsDelta、permanentPointsDelta和会员限时泥点所属cycleResetsAtMicros;资产退款中的会员限时泥点只在原周期仍有效时恢复会员额度,其余会员部分进入普通永久泥点。原每日免费消费部分在同一业务日退款时恢复原当日额度;跨北京时间业务日退款时叠加到退款当日每日免费桶,不进入普通永久泥点,当日granted_points与remaining_points均可因此超过当前基础发放量。原永久泥点消费部分无论是否跨业务日,均按退款流水中的permanentPointsDelta退回普通永久泥点。 - 每日免费泥点是独立于每日任务和会员周期的正式余额额度,基础发放量读取
profile_wallet_config.daily_free_points_per_day,不得由前端或后台任务配置改写。profile_daily_free_points保存当前北京时间业务日、当日基础发放及跨日退款叠加后的总额度和剩余额度;北京时间每日00:00作为业务日边界,个人中心、充值中心、账单读取和钱包扣费入口在首次触达新业务日时原子清除昨日剩余及退款叠加量,并按当时最新配置重置今日granted_points、remaining_points。同一业务日已初始化的用户不因后台改配置被即时追补或回收;新配置从尚未初始化当日额度的用户或下一次跨日重置起生效。首次初始化使用daily_free_grant流水,跨日重置使用daily_free_reset流水。充值中心的dailyFreeResetPoints显式来自该配置,不得用可因跨日退款增大的当日granted_points反推。惰性落库不能改变“北京时间 00:00 后读取即为新日额度”的对外语义。 - 每日任务奖励继续使用
daily_task_reward流水并进入普通永久泥点,但主站隐藏每日任务卡片和任务中心入口,不再把每日登录任务描述为“每日免费泥点”。任务配置、进度、领取记录和后台管理能力暂时保留,除非后续需求明确删除。 - 编辑器画板所有会调用外部生成 provider 的入口都不从前端请求接收
priceMudPoints;同步请求以 SpacetimeDBeditor_generation_pricing_config当前全局配置计算,外部生成队列则以external_generation_job.price_mud_points保存的入队价格为准,worker 的扣费、退款、响应和资产成本不得按执行时配置重算。前端按钮泥点只作为展示。 - 编辑器图片生成 / 图片修改 / 图标 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 必须返回同一份 canonicalprojectId + assetFolderId,调用方在入队、worker/provider 执行和原子结果准备中都复用这份值;禁止校验 trim/默认映射后的值却继续序列化或持久化原始请求。worker / inline 执行在首个 provider 或 OSS 写副作用前再次执行同一预检,不能只信任入队时结果;任一读取不可达、超时、项目或目录不匹配都失败关闭。该预检不创建锁或 reservation,最终资源 / 素材 procedure 仍必须重新校验归属,以处理预检后并发删除或转移。 - 队列任务按
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。 - 音频生成的编辑器链路虽然任务提交和结果发布分离,仍必须把提交时后端计算出的模型价格写入
AudioAssetBindingTarget.billing_points_cost,最终发布落资产时按该价格扣费;创作音频目标未提供该字段时才使用旧的创作音频固定成本。 - 编辑器进入外部生成持久队列的图片生成、去背景、图标 spritesheet、UI 设计图提取、角色动作和视频参考图,调用方必须提交
objectKey/resourceId/assetId候选稳定引用;BFF 只做内联媒体与 payload 门禁,登记状态和归属由 worker 统一解析。图片修改主来源是明确例外:站内/api/editor/images/edits与 External v1 API 调用方只提交必填sourceReferenceId,且只接受当前账号已登记的editor_project_resource.resource_id或editor_asset.asset_id;objectKey、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。任务request_payload_json/result_payload_json任意层级都禁止data:/blob:,并受统一字节上限保护。其它候选引用的 objectKey 最终必须归属于当前账号的editor_project_resource、editor_asset或asset_object,由 worker 在解析后、签名读取 OSS 前完成归属校验。本地红框序号标注图必须先上传并确认对象,再作为图片修改的辅助referenceImageSrcs入队,主来源仍使用原图业务 ID;不得把既有 objectKey 下载成 Data URL 后写入任务。图标素材、图片快速编辑和 UI 素材提取的额外参考图必须真正传入 provider,不得只写入generationInputs展示快照。普通图片生成最多 5 张参考图;图片修改、图标素材和 UI 提取的额外参考图上限还必须与所选 provider 的总容量共同取最小值:GPT-image-2 总计 5 张,nanobanana2 总计 14 张。前端添加和提交、api-server 入队 / 扣费前以及platform-imageprovider 边界都必须明确拒绝超限,禁止用.take(...)静默截断。同步且不持久化的历史兼容入口即使仍能解析 Data URL,也不能把该值转存到工程、素材、元数据、审计或任务表。- 图标规范结构化分析里位于
<playSetting>/<artStyle>XML 元素内的数据必须转义& < > " ';玩法润色、美术风格润色、规范图生图和图标 spritesheet 等自然语言 prompt 必须保留已经过边界校验的原文。图标 spritesheet 的iconDescriptions在请求边界执行独立合同:原始数组满足 OpenAPI1..100,去空后至少保留 1 条;单条最多200个 Unicode 字符、拼接后合计最多2000个 Unicode 字符且不超过6144个 UTF-8 字节;只有ValidatedEditorIconSpritesheetPrompt能进入 prompt builder,External v1 超限同步返回400。
- 图标规范结构化分析里位于
- 已有静态图片的
POST /api/editor/images/pixel-art-snaps是免费 inline 派生操作,不调用外部 provider、不创建external_generation_job、不读写泥点 ledger,也不进入任务侧栏。免费不放宽 owner、稳定引用、输入上限、持久化或处理阶段零持久化门禁。 - 主站编辑器生成队列使用同一次前端请求稳定复用的
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 属于音频 / 视频 / 角色动作等未迁移任务时必须完整比较,其余请求字段始终完全一致。 generationInputs.references是最终资产的服务端权威行引用,不接受客户端自报 provenance。图片生成类请求入队、完美像素及直接创建资源 / 素材时删除客户端 references;worker 和 inline 路径按本次真实参考图、当前 owner 的项目资源 / 素材记录重建refType/refId后再持久化。仅能证明 owned objectKey、但找不到对应资源或素材行时可以参与生成,不得制造虚假行引用;title/label只作为展示快照,不提升为资源身份。完美像素为兼容升级前的未知结果重放,可继续用旧版 canonical 客户端输入计算 operation fingerprint;新操作持久化元数据只能使用服务端重建值。owner-scoped 项目快照发现同一 operation 的稳定 resultresourceId时,HTTP 路径必须在来源解析、OSS 下载、规整、preflight 和 PUT 前直接返回409,携带operationResultAlreadyExists=true与resultResourceId,客户端仅以 GET-only 项目对账判定权威结果,不复用既存 metadata 作 exact compare-and-return。
外部服务与资产
- 已有图片完美像素化:登录态
POST /api/editor/images/pixel-art-snaps使用sourceImageSrc承载objectKey / resourceId / assetId候选稳定引用,要求projectId / canvasCompletion且canvasCompletion.dialogId必须非空,并可携带sourceResourceId / assetKind / generationInputs / assetFolderId / assetLabel;BFF 必须在下载前将候选解析为当前 owner 已登记的私有 OSS object key,并校验 project / resource / asset 归属,拒绝data:/blob:、signed URL、普通外链和音频、视频、图片序列等非静态栅格输入。归属校验有两条等价路径:带sourceResourceId且sourceImageSrc能免查确认指向同一张图(本身即该 objectKey 或就是该 resourceId)时,来源资源已随 owner-scoped 项目读取完成鉴权,直接断言resource.ownerUserId与resource.projectId后取用其 objectKey,不再按注册 ID 做全账号项目与素材库扫描;两个字段指向不同图片必须直接拒绝而不是退回扫描。其余情况仍走完整解析。跨记录的 asset_kind 扫描随扫描一并省略,按(bucket, objectKey)的存储类型点查两条路径都保留,动图仍由下载后的静态编码门禁按实际字节拒绝。编码门禁只接受静态 PNG / JPEG / WebP,明确拒绝 GIF、带acTL的 APNG 及带动画标志 /ANIM/ANMFchunk 的 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 tombstone,completion 先提交、删除保存后冲突的极端竞态仍按权威快照收口。客户端不得为该 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 秒绝对 deadline;preflight 失败或超时不得发送 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 的稳定 resultresourceId时,HTTP 路径必须在来源解析、OSS 下载、规整、preflight 和 PUT 前直接返回409,携带operationResultAlreadyExists=true与resultResourceId,并由客户端 GET-only 对账;本次请求不得附加resultPersistenceStarted。AlreadyApplied仅在 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/after;queue guard 的 Drop 归还由独立用例覆盖。不得用相对断言、--test-threads=1或全局串行锁掩盖并行竞态。 - LLM:通用 LLM 门面继续使用
GENARRATIVE_LLM_*;platform-llm文本请求默认走 Responses,旧/api/llm/chat/completions代理和少数旧运行态聊天显式保留 Chat Completions 兼容协议;创意 Agentgpt-5Responses / Chat Completions 文本链路已于 2026-06 从 APIMart 迁移到 VectorEngine,使用VECTOR_ENGINE_BASE_URL/VECTOR_ENGINE_API_KEY构造 OpenAI-compatible client,api-server会把未带/v1的 VectorEngine base URL 规范化到/v1后请求/responses。APIMART_BASE_URL/APIMART_API_KEY只作为历史残留,不再作为创意 Agent gpt-5 客户端来源;后续排障时优先确认 VectorEngine/v1/models、/v1/chat/completions和/v1/responses可用性。 - LLM:通用 LLM 门面继续使用
GENARRATIVE_LLM_*;创意 Agentgpt-5.4-miniChat Completions 文本链路已于 2026-06 从 APIMart 迁移到 VectorEngine,使用VECTOR_ENGINE_BASE_URL/VECTOR_ENGINE_API_KEY构造 OpenAI-compatible client,api-server会把未带/v1的 VectorEngine base URL 规范化到/v1后请求/chat/completions。通用/api/llm/chat/completions代理使用GENARRATIVE_LLM_PROVIDER=openai-compatible、GENARRATIVE_LLM_BASE_URL=https://api.vectorengine.cn/v1、GENARRATIVE_LLM_MODEL=gpt-5.4-mini;未单独配置GENARRATIVE_LLM_API_KEY时可复用VECTOR_ENGINE_API_KEY。APIMART_BASE_URL/APIMART_API_KEY只作为历史残留,不再作为创意 Agent gpt-5.4-mini 客户端来源;后续排障时优先确认 VectorEngine/v1/models、/v1/chat/completions和/v1/responses可用性。
平台适配器:platform-llm 公共能力与三协议工具契约
platform-llm 的统一公共抽象为 LlmRunRequest / LlmRunResponse。OpenAiChat、OpenAiResponses 和 Anthropic 三种 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_index;response.completed / response.incomplete 的 response.output[] 可作为仅有终态事件时的兜底 |
Anthropic |
顶层 tools[] 为 name / description / input_schema,无 function 包装层和 strict |
{ "type": "auto" } / { "type": "any" };Required → any |
content[].type=tool_use,input 序列化为 arguments |
content block index;content_block_start + input_json_delta |
流式 on_delta 只发送文本增量、累计文本和完成原因,工具调用不进入回调。平台层按协议 slot 聚合并行工具片段:Chat 使用 delta.tool_calls[].index,Responses 使用 output_index,Anthropic 使用 content block index。Responses 的 function_call_arguments.done、response.completed 和 response.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_filter,Responses 的 incomplete / failed / cancelled,Anthropic 的 max_tokens / pause_turn / refusal。这里必须用黑名单而非白名单,未知值与缺失一律放行,否则会误杀不发或自定义该字段的兼容网关。该检查只在存在工具调用时生效:正文被 max_tokens 截断仍是可用的降级结果,一并拒绝会打死所有触及输出上限的长文本回答。与非流式畸形参数透传同时成立时,本检查优先。
流式工具调用必须来自已收尾的流:只要聚合出过工具 slot,收尾时就必须已观察到本协议的完成信号,否则按截断返回 Deserialize。完成信号按协议判定——Chat 为非空 choices[].finish_reason 或 data: [DONE],Responses 为 response.completed 或 response.incomplete,Anthropic 为带 stop_reason 的 message_delta 或 message_stop。不能用 data: [DONE] 作为统一判据:MiniMax 兼容层不发该标记,只发 finish_reason。也不能只用 “参数是合法 JSON” 当完成证明——顶层花括号闭合只说明单个参数对象字节完整,说明不了模型是否还要发下一个工具块,更说明不了上游随后会不会报 max_tokens 或 error;代理超时、网关自行掐断和 HTTP/2 提前 END_STREAM 都表现为干净 EOF,与正常收尾在字节层无法区分。该门禁当前只覆盖工具路径;纯文本响应缺完成信号仍按成功返回并打 warn,改动前必须先确认所有在用网关的文本收尾行为。流在任何工具分片到达前就断掉时槽位为空,门禁无从触发,这是已知残留缺口。
各协议的最终事件必须同时终止读取循环,不能只标记完成:Chat 的 data: [DONE]、Responses 的 response.completed 与 response.incomplete、Anthropic 的 message_stop 都置终止位。Responses 的整体收尾信号有两个——撞到 max_output_tokens 时上游只发 response.incomplete、不发 response.completed(真实端点抓包确认),其载荷与 completed 同构,同样带完整 output[],item 上标 status=incomplete,incomplete_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.delta 的 delta、.done 的 arguments、终态载荷 output[].arguments,以及 Anthropic input_json_delta 的 partial_json;Chat 走强类型 DTO,同样输入本就反序列化失败,本规则是把三协议口径拉齐。该约束只覆盖参数字段:id 与函数名即使类型不对也只会退化成缺失,随后被归一层按缺 id / 缺函数名拒绝,本来就是失败关闭。
反过来,已经收尾的流遇到尾部传输 / 解析错误时必须保留结果,不能重跑 Provider。判断“有没有值得保留的东西”要看正文或工具调用任一非空,不能只看正文——纯工具调用响应的正文本来就是空的(MiniMax 的 Anthropic 工具流恒定如此),只看正文会让这类响应每次都被丢弃,白白多跑一轮往返。保留的安全性由“协议完成信号已到 + finish_reason 已到 + 工具参数完整 + 错误属可容忍尾部错误”共同保证,与正常路径判据一致。Chat 的非空 finish_reason 与 Anthropic 带 stop_reason 的 message_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_event,event_key = external_generation_run,metadata 记录开始 / 结束时间、耗时、状态、成功标记、失败原因、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 required。request_send阶段的 curl timeout / connect error 按可重试传输错误处理,最多尝试 5 次,并使用指数退避加短抖动;排障时优先看attempt、max_attempts、retry_delay_ms、fallback_from_model、fallback_to_model、reference_image_bytes_total和request_params,不要把SendRequest当成上游业务错误。 - 抠图输入以私有 OSS 作为内存生命周期边界:生成原图和角色动作抽取帧上传时消费图片字节所有权,上传完成后不保留原图缓冲;手动去背景直接解析并校验已有 OSS object key,不下载原图。BgFilter 必须为 object key 签发 600 秒 GET URL 并通过 multipart
image_url提交,不用file重传;flat 链路进入阿里云 fallback 时由platform-mattingURL 接口单独下载并上传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 设计图素材提取、角色动作抽帧后的透明化统一通过唯一 loopbackbgfilter-worker调用 BgFilter provider。provider 配置继续使用GENARRATIVE_EDITOR_BGFILTER_BASE_URL与GENARRATIVE_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 配置已经删除。父流程先把候选objectKey、resourceId或assetId解析为当前 owner 已登记的私有 OSS object key;BFF 入队前统一拒绝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 从Qadmission 时刻起算,完成 JSON 校验并进入 provider permit 等待队列时再取得队长快照,按min((队长+5)×est×2, maxQueueWaitMs)约束排队等待。子 worker 还负责严格最多两次顺序 attempt、结果校验和按 flat / complex 隔离的进程级熔断;两种模式共享GENARRATIVE_EDITOR_BGFILTER_CIRCUIT_FAILURE_THRESHOLD=3和GENARRATIVE_EDITOR_BGFILTER_CIRCUIT_COOLDOWN_SECONDS=120默认值,但失败和成功只更新当前模式,且只由子 worker 读写。手动去背景固定使用background_mode=complex、seg_model=birefnet、cross_check=off,不传file或screen_color;complex provider 失败累计自身熔断,任意失败或自身熔断都直接返回父流程失败,不接 flat fallback,也不影响 flat 熔断。标准纯色背景四条链路固定使用background_mode=flat、screen_color=<screenColor>、seg_model=<segModel>和cross_check=<on|off>,其中角色形象生成、图标 spritesheet 生成和角色动作逐帧去背传cross_check=on,UI 设计图素材提取传cross_check=off。角色、图标 spritesheet 和 UI 设计图素材提取的同源画布请求不展示抠图模型选择,但自动提交默认segModel=birefnet;api-server 负责 allowlist 校验并在字段缺失时回落默认值,角色动作逐帧去背的seg_model则由后端固定。background_mode与cross_check只存在于 api-server 到 worker 的内部 RPC;请求中的segModel不进入generationInputs、普通用户响应、搜索、详情、导出或错误详情,External OpenAPI 是否接受该字段按独立契约决定。父侧不重试已建立连接的内部 RPC,仅对 TCP 连接从未建立的失败按父预算有界退避重试(跨过 worker 重启与开机排序窗口,收到任何 HTTP 响应即停止);flat 两次 provider attempt 失败、熔断、overload、内部 deadline 或断连后,只要父业务预算仍有效,父流程才继续“阿里云通用抠图 → 本地editor_green_screen键色扣除”,熔断期不得直接退化到本地兜底。角色动作视频生成的背景色已与生图链路统一:screenColor=auto时由视觉 LLM(gpt-5-mini,Responses 协议、low 推理档)读源角色图自动决策,并经硬过滤器剔除与前景 / 皮肤撞色的候选,手动 hex 则尊重用户选择;透明源角色图在提交 Ark 图生视频前先合成到选定背景色实色,使视频背景等于抠图键色;抽帧后每帧先上传私有 OSS 并释放原帧缓冲,再以 object key 固定使用seg_model=birefnet、cross_check=on进入上述三段式链路。阿里云通用抠图配置为GENARRATIVE_ALIYUN_MATTING_ENABLED、GENARRATIVE_ALIYUN_MATTING_ENDPOINT、GENARRATIVE_ALIYUN_MATTING_ACCESS_KEY_ID、GENARRATIVE_ALIYUN_MATTING_ACCESS_KEY_SECRET和GENARRATIVE_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_decode、source_validate等阶段。成功图片字节返回后,最终 Alpha / 尺寸恢复、OSS / asset object、画布写回、计费和父任务终态仍全部由父流程负责。 - 请求与可见性边界:同源画布 BFF 的角色、图标 spritesheet 和 UI 设计图素材提取 request DTO 保留
segModel;前端只自动提交默认值,不提供用户选择控件。api-server 对该字段执行 allowlist 校验并提供缺省回退,再按链路固定background_mode=flat与cross_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 在同一个Npermit 内严格最多执行两次顺序 provider attempt。唯一子 worker 使用GENARRATIVE_BGFILTER_WORKER_CONCURRENCY=N(生产16)限制真实 provider 在途数;GENARRATIVE_BGFILTER_WORKER_MAX_REQUESTS=Q降级为可选 admission 保险丝(默认2048,仅防连接风暴,显式配置时必须>= N)。超时全部由N与est运行时派生:单 attempt 上限N × est × 2、调用预算callBudgetMs = 2 × attempt + 1s(自取得Npermit 起算)、排队等待受min((provider 等待队列队长+5)×est×2, maxQueueWaitMs)双重上界(动态项充当自适应过载探测,超时带bound = estimate | parent标记),排队不侵蚀调用预算;N与est必须同放共享 API 基础环境;请求携带的callBudgetMs只是父侧配置指纹,worker 比对后不一致只告警并计bgfilter_internal_call_budget_drift_total指标、始终以本进程公式值执行——发布调优 N / est 的新旧进程共存窗口不得误伤在途任务,持久漂移由部署脚本共享 env 对齐校验在启动前拦截。角色动作不再增加2000ms × 本次实际帧数,32 / 40 / 48帧使用相同公式。父侧按剩余绝对预算派生maxQueueWaitMs(flat 扣除39s父侧预留(37sfallback +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、PUT400错误体读取失败(未解析出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/editsmultipartimage,模型为gpt-image-2,2K 1:1输出10*10spritesheet;物品 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:1UI spritesheet 与1K 9:16背景图,模型均为gpt-image-2。UI spritesheet prompt 固定要求单一纯绿色#00FF00 / RGB(0,255,0)绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;背景图必须合成为全画幅不透明 PNG。 - Match3D 1:1 容器 UI:VectorEngine
/v1/images/editsmultipart 参考图。该容器参考图是后端生图协议输入,必须通过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-hyper3d,api-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:assetscope,并始终以 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输出,排查资产写入 / 确认失败时优先按operation、object_key/key_prefix、status_class、error_kind和elapsed_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_send、response_body、upstream_status、response_parse、missing_image和image_download阶段;编辑器screenColor=auto的 gpt-5-mini 背景色决策同样必须审计每次已发出的 LLM 调用失败,包括传输 / 超时、上游拒绝、响应体解析、空响应和返回候选外颜色;即使随后降级默认背景色并继续主流程也不得只记 warning。api-server将这些失败映射成external_api_call_failure,scope_kind = module、scope_id = provider、module_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 outbox,outbox 不可写或保护阈值拒绝时回退同步写 SpacetimeDB;不得新增前端兜底或在 SpacetimeDB reducer 内做外部 I/O。bgfilter-worker是受限资源例外:它使用共享 tracking outbox 基础目录下独立的bgfilter-worker/子目录,provider 失败审计在 spawn 前受进程级1024硬上限保护并由 shutdown tracker 跟踪;满载、outbox 缺失、保护阈值拒绝或写盘失败时直接丢弃并观测,不回退同步直写 SpacetimeDB。优雅退出先排空已获准任务的 enqueue,再封存并尽力 flush;进程被强杀时只有已 enqueue 记录可在下次启动重放。 - 外部生成运行记录:所有外部生成编排的完成态统一写入
tracking_event,event_key = external_generation_run,scope_kind = module,scope_id = provider,module_key = external-generation。metadata 固定包含runId、provider、operation、requestLabel、requestPayload、status、success、failureReason、providerRequestId、resultPayload、startedAtMicros、completedAtMicros和durationMs。这类记录只用于运行审计和排障,不再走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
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-serverHTTP 角色只入队,external-generation-worker角色通过 claim lease 领取、续租、执行,并用lease_token栅栏回写阶段、完成 / 失败。队列行继续保存 worker 执行、计费与滚动发布兼容所需字段,末尾可选phase只取generating / processing;claim 写generating,真实进入抠图处理时由受job_id + worker_id + lease_token保护的 procedure 写processing。phase procedure 以结构化结果区分LeaseFencingRejected与OtherRejected;LeaseFencingRejected立即终止,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_draft、generate_puzzle_images与generate_puzzle_ui_background的业务写回也在对应 SpacetimeDB transaction 内校验job_id + worker_id + lease_token、job kind、owner 和 source entity,避免过期 worker 写 session / work profile;图片画布编辑器的editor_image_generation、editor_image_edit、editor_background_removal、editor_icon_spritesheet_generation、editor_ui_design_asset_extraction、editor_character_animation_generation、editor_video_generation、editor_sound_effect_generation和editor_background_music_generation复用同一队列表。结构化 canvas 激活后,当前 worker completion 先以读取时 canvas revision 执行 CAS,并发冲突时拒绝覆盖并保留可诊断失败;目标是进一步收口为受 lease 栅栏保护的单事务幂等写入editor_project_resource、结果editor_canvas_layer、editor_canvas_generation_dialog终态和 canvas revision。未激活 canvas 在 2 MiB 上限内继续走 legacyeditor_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_return;procedure 在同一try_with_tx内写入可选asset_object、全部editor_project_resource、editor_asset、可选asset_entity_binding、可选 canvas V2 CAS、queue job 终态和editor_generation_operationreceipt。结构化 canvas 的 layer / dialog / revision 与未激活 canvas 的 legacylayers_json仍经既有 V2 布局验证分流,前端不直接发明正式完成态。queue 首次提交在同一快照验证 owner、job kind、request fingerprint 和有效job_id + worker_id + lease_token;统一 procedure 已完成 job 后 worker 不得再单独 complete。 -
载荷约束:本次先对
source_module = editor-canvas的request_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 透明图集成功但自动拆分降级时仍保留透明图集;通用
warning与sliceWarning只在「透明背景最终失败」这一条上互斥,风格归一化或像素规整产生的通用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 并发 OSSPUT + HEAD,OSS 连接 / 单请求超时固定为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 procedure;running + 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 字符。phase与warning_message分别表示当前执行阶段和成功降级提示,不得混用;worker / BFF / Web 必须同版本协调发布,不保证滚动混部或旧 Web 缓存下的字符串语义兼容。 - 正式读取 procedure 为
get_external_generation_job_summary_and_return、list_external_generation_job_summaries_and_return和acknowledge_external_generation_job_summaries_and_return。历史维护 procedure 为compact_external_generation_job_payloads_and_return与backfill_external_generation_job_summaries_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 索引,不得静默改写其它玩法历史任务。
external_generation_job_event
- Rust 结构体:
ExternalGenerationJobEvent - 源码:
server-rs/crates/spacetime-module/src/external_generation.rs - 用途:外部生成任务审计事件表,按
job_id和owner_user_id记录enqueued、claimed、lease_renewed、completed、failed、acknowledged等状态转换事实。状态转换只能由 SpacetimeDB procedure 写入,不由前端或 worker 直接改表;该表用于追溯任务生命周期和排障,不替代external_generation_job当前状态。
ai_text_chunk
- Rust 结构体:
AiTextChunk - 源码:
server-rs/crates/spacetime-module/src/ai/stages.rs
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 时失败关闭,不能任选一条继续授权。普通对象只有权威位置查询返回不存在且显式使用 curatedlegacyPublicPath时才能进入白名单兼容;历史活动卡上传缺少 metadata 时,可由“global配置已启用 +image_object_key精确匹配 + key 位于generated-character-drafts/editor/showcase-campaign/”这一窄授权继续换签,禁用或替换活动卡后旧 key 立即失效。新上传活动卡仍必须完成asset_objectconfirm,不能把该历史兼容当作跳过正式登记的常规路径。 - 编辑器生成结果例外不允许先单独 confirm 再分段创建业务记录:OSS
PUT / HEAD仍在事务外,但AssetObjectUpsertInput必须交由统一结果 procedure 在同一事务内与 resource、asset、binding、canvas、job 和 receipt 原子写入。候选省略 object 时,procedure 必须在同一事务快照中验证已登记 object 的 owner、位置和媒体身份;HEAD成功不能代替正式登记。
SpacetimeDB view:public_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 或微信 openid,provider_union_id保存微信 unionid。账户资料以user_account.phone_number_e164、user_account.display_name、user_account.avatar_url为准,auth_identity.phone_e164/display_name/avatar_url仅保留旧行兼容,不再作为写入或恢复真相。
auth_store_projection_meta
- Rust 结构体:
AuthStoreProjectionMeta - 源码:
server-rs/crates/spacetime-module/src/auth/tables.rs
认证恢复策略:api-server 启动时只从 SpacetimeDB 正式认证表(user_account / auth_identity / refresh_session)导出 typed AuthStoreProjectionView,再恢复 module-auth 的进程内认证工作集;运行中 Bearer sid 或 refresh cookie 在本进程工作集内未命中时直接按失效处理,不再从 SpacetimeDB 导出整包认证状态刷新内存,避免旧投影把重复手机号或旧会话重新灌回进程。module-auth 只保留内存工作集和 projection 导入 / 导出能力,不再保留 JSON 快照导入 / 导出能力,也不写本地持久化文件;auth-store.json / GENARRATIVE_AUTH_STORE_PATH 不再是兼容恢复源。认证创建、登录会话、刷新、退出、改密、重置密码、绑定和资料变更等写操作必须在返回客户端前通过 sync_auth_store_projection 成功同步 SpacetimeDB 正式认证表;同步失败时接口返回错误,不允许把只存在于当前进程内存的账号或会话当成成功结果。新用户注册奖励、邀请码绑定和登录埋点必须排在认证同步成功之后,避免认证没落库时先写出钱包或邀请关系。若启动恢复阶段 SpacetimeDB 不可连接或超时,api-server 会按固定间隔持续重试认证工作集恢复,恢复成功后才开始监听 HTTP,避免一次短超时让进程永久停留在依赖不可用状态。
auth_store_snapshot 表和旧 import_auth_store_snapshot_json / export_auth_store_snapshot_from_tables procedure 已删除。认证投影同步只读写 user_account、auth_identity、refresh_session 和 auth_store_projection_meta;auth_identity 不再写 phone_e164、display_name、avatar_url,这些账号资料只以 user_account 为准。
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_resource与editor_asset表尾只追加image_sequence_frames_json: Option<String>和image_sequence_duration_ms: Option<u64>;editor_showcase_asset作为提交时冻结的审核与公开快照,也在表尾追加并从账号素材复制相同两字段。前者保存完整有效帧数组,数组位置是唯一播放顺序,正式帧对象不保存或返回frameIndex;后者只表示该图片序列完整播放一次的毫秒时长。帧数始终取数组长度,FPS 在播放或导出时即时推导,不持久化frame_count或fps。- 图片序列时长不能复用音频 / 视频生成请求的
durationSeconds,资源和素材也不保存通用duration_seconds。角色动作与视频生成响应保留各自既有的请求 / 结果级秒数;普通音频 / 视频的用户可见时长只作为字符串展示项写入generation_inputs_json.fields[],上传媒体使用本次上传探测值,带时长选项的生成任务使用用户提交值,不再复制到EditorAsset、CanvasLayer或画布 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;生成响应可继续返回请求 / 结果层面的frameCount、fps、durationSeconds,但它们不是持久化真相。预览视频仍作为独立asset_kind = video中间素材保留并承担生成成本,最终派生素材成本为 0。 create_editor_project_resource、create_editor_asset和同源资源回填必须在 procedure/storage 边界验证最终状态:asset_kind = character-animation必须同时包含至少两帧的有效数组和大于 0 的image_sequence_duration_ms;其他类别不得携带任一图片序列字段。同源资源只允许从None单调补齐字段,非空冲突失败关闭,补写后的updated_at不得早于既有时间。generation_inputs_json只保存用户生成 / 重放输入,例如fields、references、artSpec;动作输出和内部背景决策色不得再写入该 JSON。背景决策审计继续走独立审计链路。- 存量
generation_inputs_json.characterAnimation、已确认动作行的 helper 顶层frames/previewVideoPath/frameCount/fps/durationSeconds、screenColorHex、正式帧中的frameIndex和误标动作预览 MP4,统一由 migration operator procedurenormalize_editor_character_animation_metadata_and_return分asset → project-resource → showcase → canvas四个 scope 迁移。顶层旧字段只有在asset_kind=character-animation、正式序列字段、嵌套characterAnimation或权威asset_object已证明该行属于角色动作时才解释,普通图片 / 视频任意 JSON 中同名字段保持原样。迁移在计划态先把同 task 的已验证预览 MP4 重分类为video,只有能形成正式图片序列的候选才可用于后续 scope;project-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,整批拒绝 apply;procedure 同时返回不含生成输入正文的 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提交旧运行字段,或正式帧包含frameIndex,api-server 与 SpacetimeDB storage 均失败关闭;其它素材的任意生成输入不受动作专属门禁影响。 - 2026-08-10 前短期错误版本写入的
asset_kind = "image"不走永久兼容,统一由 migration operator procedureclean_editor_image_asset_kind_and_return清理。scope 固定为asset → project-resource → showcase → canvas:前三者只把精确旧值改为None;canvas 同时清理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、CanvasMediaType或asset_object.asset_kind。dialog 权威行必须进入 dry-run 命中计数、批次 SHA-256 和同一事务内的 patch,清理后以重建的完整 structured layout 复核并更新 migration 摘要。纯分类修复保留原updated_at、画布 revision 和迁移状态。双份 layout 清理后仍不一致、缺工程、owner 不一致或命中字段的画布项缺少稳定 layer / resource 身份时形成 blocker,整批拒绝 apply;project-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-03,2026-08-08 增补)
新增前先确定稳定字符串、主媒体含义、专属元数据、来源血缘、下载产物、复用规则和公开边界。只修改新类别实际经过的链路,不机械改动全部结构。
| 数据结构 / 投影 | 位置 | 需要修改的情况与内容 |
|---|---|---|
EditorProjectResource、EditorAsset |
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.rs、api-server/src/admin.rs |
后台素材查询或精选审核需要展示该类别时返回完整正式字段,不能只返回封面或首帧 |
| TypeScript 客户端类型 | src/services/image-editor/editorProjectClient.ts、apps/admin-web/src/api/adminApiTypes.ts |
接收后端 camelCase 字段,不定义第二套业务真相 |
| 画布 layer / layout 映射 | src/components/image-editor/ |
新类别能进入画布时贯通资源加载、素材点击 / 拖放、保存恢复、复制、撤销、删除和导出;layout 只保留恢复副本 |
| 用户标签覆盖白名单 | src/components/image-editor/ImageCanvasWorldView.tsx、server-rs/crates/spacetime-module/src/editor_project_storage.rs |
显式决定用户能否把同媒体族图层覆盖为该类别;允许时同步前端 CANVAS_ASSET_KIND_TAG_OPTIONS 与后端 EDITOR_CANVAS_ASSET_KINDS,并分别覆盖菜单选择和结构化 assetKindOverride 解析测试;不允许时补拒绝测试,不能只改一端 |
| 快速编辑正向白名单 | src/components/image-editor/ImageCanvasGenerationModel.ts、server-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.ts、ImageCanvasGenerationModel.ts、ImageCanvasGenerationDialogModel.ts 及生成提交链 |
新类别接入时必须显式判断是否允许恢复原生成器。可改造的生成类别必须定义稳定 V2 action、fields[].id、references[].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-server、src/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/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_id、by_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
SpacetimeDB view:big_fish_gallery_view
- Rust view:
big_fish_gallery_view - 返回类型:
Vec<BigFishWorkSummarySnapshot> - 源码:
server-rs/crates/spacetime-module/src/big_fish/session.rs - 说明:大鱼吃小鱼公开 source 投影,只从
Publishedcreation 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/gameplay.rs
creation_entry_config
- Rust 结构体:
CreationEntryConfig - 源码:
server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs - 字段:
config_id、start_title、start_description、start_idle_badge、start_busy_badge、modal_title、modal_description、updated_at、event_title、event_description、event_cover_image_src、event_prize_pool_mud_points、event_starts_at_text、event_ends_at_text、event_banners_json、public_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 - 字段:
id、title、subtitle、badge、image_src、visible、open、sort_order、updated_at、category_id、category_label、category_sort_order、unified_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_key、enabled、rollout_percent、allow_user_ids、allow_user_tags、deny_user_ids、description、updated_at。 - 用途:通用功能灰度事实源。当前创作入口使用
creation-entry:<id>约定关联入口 ID;api-server按当前可选登录用户、用户标签和稳定百分比判定后,只把过滤后的入口配置返回普通前端,不下发灰度规则或用户标签。 - 迁移兼容:新增表不改已有入口表字段;未配置 gate 或
enabled=false时不限制功能,黑名单用户 ID 优先于白名单和百分比命中。
custom_world_agent_message
- Rust 结构体:
CustomWorldAgentMessage - 源码:
server-rs/crates/spacetime-module/src/custom_world.rs
custom_world_agent_operation
- Rust 结构体:
CustomWorldAgentOperation - 源码:
server-rs/crates/spacetime-module/src/custom_world.rs
custom_world_agent_session
- Rust 结构体:
CustomWorldAgentSession - 源码:
server-rs/crates/spacetime-module/src/custom_world.rs - 发布约束:
publish_world的 action payload 不要求携带settingText;spacetime-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/custom_world.rs
custom_world_gallery_entry
- Rust 结构体:
CustomWorldGalleryEntry - 源码:
server-rs/crates/spacetime-module/src/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/custom_world.rs - 字段变更:
visible控制是否进入公开列表 / 详情,新作品默认false;旧迁移数据由migration.rs按历史公开默认补true。 - 兼容约束:历史公开 RPG / 自定义世界 profile 可能存在
publication_status=Published但published_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/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;一旦已有 operator,bootstrap secret 不得新增或接管 operator,后续授权只能由既有 operator 完成。
external_api_key
- Rust 结构体:
ExternalApiKey - 源码:
server-rs/crates/spacetime-module/src/external_api_key_storage.rs - 说明:外部 OpenAPI 调用使用的账号级 API Key 凭据表,只保存 key prefix、SHA-256 hash、作用域、撤销状态和使用时间;明文 Key 只在
/api/profile/api-keys创建接口返回一次,不进入 SpacetimeDB,且 API Key 管理接口不写入外部 OpenAPI JSON。v1 默认作用域为editor:project、editor:canvas、editor:image-generate、editor:asset;其中editor:project覆盖项目列表、最近项目、创建、读取、重命名和删除,editor:canvas覆盖默认画布布局保存,editor:image-generate覆盖编辑器现有图片生成、重绘 / 调整、规范图、宣发素材、图标 spritesheet 生成 / 拆分、UI 设计图素材拆分、角色动画、视频、音效和背景音乐生成,editor:asset覆盖素材直传凭证、素材对象确认、签名读取、账号级素材库和项目画布资源记录操作。 - 索引:
by_external_api_key_owner_user_id用于登录态 API Key 列表;key_hash唯一索引用于外部 API 鉴权。
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 identity;HTTP 列表和写响应不返回密码摘要。 - 索引:
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_return、list_editor_agent_conversations_and_return、get_editor_agent_conversation_and_return、touch_editor_agent_conversation_and_return、delete_editor_agent_conversation_and_returnprocedure 操作;api-server不直接绕过spacetime-clientfacade 读写表。消息文档当前由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-clientfacade 读写;项目页列表、重命名和删除也使用该能力,删除工程时级联清理默认画布、结构化画布行、迁移状态和资源元数据。 - 索引:
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_layer、editor_canvas_generation_dialog为权威,layers_json只作为存量迁移输入和受限回滚载体。所有结构化 mutation 必须以expected_revision做 CAS,成功事务只递增一次 revision。 - 索引:
by_editor_canvas_project_id、by_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_override。asset_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 hash;active 后只能续存同一行且扩展不可变,不能新增或篡改。其他缺资源图层继续 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 activate;active 重入和 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_kind、generation_inputs_json、序列媒体结果字段和历史public_showcase_enabled。asset_kind是跨布局共享的资源默认素材类型;单个结构化图层的差异只写editor_canvas_layer.asset_kind_override,有效类型按override ?? resource default计算,不能通过新增资源行模拟标签修改。image_sequence_frames_json与image_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-snapshot、source_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-run,apply 必须绑定 plan SHA-256 并在成功后自动复核 already-repaired,禁止手工 SQL 绕过事务 guard。 - 生成链路的新 resource ID 必须按 owner + operation kind + operation ID + stable slot 派生,只能在统一结果 procedure 中创建或完整比较;普通“同媒体复用”不得把它替换成另一随机 ID。
- 普通静态图片的默认
asset_kind为NULL;登录态 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/integrity,backfilled 还要证明两侧与原凭证一致且语义等价,rolled_back 沿用重复 rollback 的完整复检。只有旧凭证有效且差异仅由本次 project-resource 清零与精确image字段删除构成时才允许写入、复核新状态并受控重算 legacy / structured hash、图层 / 对话框 / 资源引用完整性和 verified revision;migration 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是不可由用户选择的请求控制字段,不进入generationInputs;background_mode和cross_check只属于 api-server 到 worker 的内部 RPC。External OpenAPI 不开放segModel。上述内部结果字段不写入fields,普通用户(包括素材 owner)与匿名公开读取均不得取得;普通用户响应还必须省略素材顶层provider和内部处理model,但保留正常用户可见model与其他合法的顶层功能字段。后台管理和服务端审计可读取原始值。过滤只作用于普通用户 / 公开响应边界,不修改素材或精选快照,因此历史数据无需迁移。generation_inputs_jsonV2 可执行改造契约沿用现有 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_id、by_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_count、asset_kind、generation_inputs_json、可选source_resource_id、generation_cost_mud_points和序列媒体结果字段。asset_kind是唯一权威媒体类别;image_sequence_frames_json与image_sequence_duration_ms保存角色动作正式结果,数组长度派生帧数、数组顺序决定播放顺序、FPS 按需推导,generation_inputs_json只保存用户可见生成 / 重放输入。prompt固定表示规范化后的用户原始意图,供跨资源搜索和用户侧元数据使用;provider 实际返回的改写只写actual_prompt,提交给 provider 的系统 / 工程化 prompt 不得写入prompt。角色透明图、图标透明图和自动切片等派生产物继承源用户 prompt,并用source_resource_id、generation_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,完整帧数组与图片序列毫秒时长写入两个正式媒体结果字段;生成端确认每个最终帧对象后,必须把该帧的objectKey与assetObjectId同时写入正式帧 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、媒体字段和生成输入逐字段一致。
- 正式序列帧写入边界:写入端必须逐帧取得非空
objectKey与assetObjectId,并始终按objectKey重建imageSrc的持久站内路径。只有临时签名 URL 而没有这两项稳定引用的 External v1 请求返回400,不能进入正式素材或项目资源。 - 索引:
by_editor_asset_owner_user_id、by_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 = pending、display_enabled = false且showcase_category = null。公开 read model 只读取正式序列快照并返回既有imageSequenceFrames/imageSequenceDurationMs可选字段;旧快照必须先经 showcase scope 规范化,不能在公开 mapper 回读generation_inputs_json。公开角色动作帧只从当前有效展示快照中的逐帧assetObjectId/objectKey派生 exact read grant,完整数组无效或任一公开条件撤销后不再授权。后台审核通过后写入approved,但仍保持不展示;运营可在后台按前台具体 Tab 手动设置showcase_category(characters、ui、music、marketing)并开启展示。未设置分类的素材不归入具体 Tab,但展示开启后仍进入前台“全部”。审核通过时生成确定性返还流水editor-showcase-refund:{showcase_id},BFF 按 50% 生成成本返还泥点后回写refund_completed_at;拒绝后写入rejected。公开精选GET /api/editor/showcase/resources读取review_status = approved、display_enabled = true且媒体非空的记录,按通过时间 /showcase_id倒序 cursor 分页。素材删除时,待审核记录标记asset_deleted_while_pending,已拒绝记录删除,已通过记录保留快照继续展示。 - 索引:
by_editor_showcase_asset_owner_user_id、by_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_id、by_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、内部图片 OSSimage_object_key、image_width和image_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 = global。models: 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-256,procedure 对入参原文重新计算摘要并做常量时间比较。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 receipt,queue 与受控 inline 路径共用。主键
operation_key由 owner 与 operation ID 稳定派生,同 owner 不得跨 kind 复用 operation ID;operation_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 完成时间与完成事件仍使用 SpacetimeDBctx.timestamp,不信任调用方时钟。 - 索引:主键
operation_key;by_editor_generation_operation_owner(owner_user_id, operation_id)仅用于受控定位和诊断,不允许同 owner 跨 operation kind 复用同一 operation ID。
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.mjs,CLI 会核对当前登录 operator identity 并要求双录新 identity。 - 索引:自增主键
rotation_id。
inventory_slot
- Rust 结构体:
InventorySlot - 源码:
server-rs/crates/spacetime-module/src/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 主题按钮兜底。
SpacetimeDB view:jump_hop_gallery_card_view
- Rust view:
jump_hop_gallery_card_view - 返回类型:
Vec<JumpHopGalleryCardViewRow> - 源码:
server-rs/crates/spacetime-module/src/jump_hop.rs - 说明:跳一跳公开列表 source 投影,只暴露
publication_status = Published的作品卡片字段;统一公开列表主路径通过public_work_gallery_entry消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布和运行态仍按 procedure 路径处理。
SpacetimeDB view:jump_hop_gallery_view
- Rust view:
jump_hop_gallery_view - 返回类型:
Vec<JumpHopGalleryViewRow> - 源码:
server-rs/crates/spacetime-module/src/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兼容。
SpacetimeDB view:wooden_fish_gallery_card_view
- Rust view:
wooden_fish_gallery_card_view - 返回类型:
Vec<WoodenFishGalleryCardViewRow> - 源码:
server-rs/crates/spacetime-module/src/wooden_fish.rs - 说明:敲木鱼公开列表 source 投影,只暴露
publication_status = published的作品卡片字段;统一公开列表主路径通过public_work_gallery_entry消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布和运行态仍按 procedure 路径处理。
SpacetimeDB view:wooden_fish_gallery_view
- Rust view:
wooden_fish_gallery_view - 返回类型:
Vec<WoodenFishGalleryViewRow> - 源码:
server-rs/crates/spacetime-module/src/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 accessor:
match_3_d_work_profile - 源码:
server-rs/crates/spacetime-module/src/match3d/tables.rs - 兼容说明:dev 现有 SpacetimeDB 元数据中的真实表名 / 索引名为
match_3_d_work_profile与match_3_d_work_profile_*_idx_btree。module 内部 accessor 必须与该 canonical name 对齐,避免 Rust SDK 在index_id_from_name初始化二级索引时查找match3d_work_profile_*并触发No such indexpanic。migration.rs仍兼容旧迁移包中的match3d_work_profile表名补默认字段。
SpacetimeDB view:match_3_d_gallery_view
- Rust view:
match3d_gallery_view - 返回类型:
Vec<Match3DGalleryViewRow> - 源码:
server-rs/crates/spacetime-module/src/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/gameplay.rs
player_progression
- Rust 结构体:
PlayerProgression - 源码:
server-rs/crates/spacetime-module/src/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_points和remaining_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_points、membership_period_days、membership_queue_limit、membership_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包含pending、paid、failed、closed、refunded、expired;过期补偿字段expired_at、expiration_checked_at、expiration_provider_state、expiration_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_id、by_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_no、by_profile_recharge_refund_observation_order_id。
profile_recharge_order_refund_settlement
- Rust 结构体:
ProfileRechargeOrderRefundSettlement - 源码:
server-rs/crates/spacetime-module/src/runtime/profile.rs - 作用:原充值订单维度的退款与权益结算摘要,保存累计成功退款金额、目标/已回收/未回收泥点、权益状态和钱包冻结事实。部分退款不改订单终态;累计全额才把订单改为
refunded。 - 索引:主键
order_id,by_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_no,by_profile_recharge_refund_hold_order_id、by_profile_recharge_refund_hold_user_id、by_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_timer;order_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_at。metadata_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_id与last_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_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/puzzle.rs
puzzle_agent_session
- Rust 结构体:
PuzzleAgentSessionRow - 源码:
server-rs/crates/spacetime-module/src/puzzle.rs
puzzle_background_compile_task
- Rust 结构体:
PuzzleBackgroundCompileTaskRow - 源码:
server-rs/crates/spacetime-module/src/puzzle.rs - 说明:拼图首图后台生成的跨 api-server 实例互斥 claim 表,只保存活动任务租约,不表达最终生成结果;
task_id为主键,claim_id用于释放时防止误删新租约,租约超时时间为 30 分钟。
puzzle_event
- Rust 结构体:
PuzzleEvent - 源码:
server-rs/crates/spacetime-module/src/puzzle.rs
puzzle_leaderboard_entry
- Rust 结构体:
PuzzleLeaderboardEntryRow - 源码:
server-rs/crates/spacetime-module/src/puzzle.rs
puzzle_runtime_run
- Rust 结构体:
PuzzleRuntimeRunRow - 源码:
server-rs/crates/spacetime-module/src/puzzle.rs
puzzle_work_profile
- Rust 结构体:
PuzzleWorkProfileRow - 源码:
server-rs/crates/spacetime-module/src/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 的开局、关卡完成、全局完成、失败、超时和消除统计来源;首版不做排行榜。
SpacetimeDB view:puzzle_clear_gallery_view
- Rust view:
puzzle_clear_gallery_view - 返回类型:
Vec<PuzzleClearGalleryViewRow> - 源码:
server-rs/crates/spacetime-module/src/puzzle_clear.rs - 说明:拼消消公开详情 source 投影,只暴露
publication_status = published且visible = true的作品,包含 atlas、底图、图案组和卡牌切片等详情级字段;统一公开详情主路径通过public_work_detail_entry消费该 view,只保留平台详情页展示摘要。
SpacetimeDB view:puzzle_clear_gallery_card_view
- Rust view:
puzzle_clear_gallery_card_view - 返回类型:
Vec<PuzzleClearGalleryCardViewRow> - 源码:
server-rs/crates/spacetime-module/src/puzzle_clear.rs - 说明:拼消消公开列表 source 投影,只暴露平台卡片需要的公开字段;统一公开列表主路径通过
public_work_gallery_entry消费该 view,/api/runtime/puzzle-clear/gallery保留玩法专属 HTTP shape。
SpacetimeDB view:puzzle_gallery_view
- Rust view:
puzzle_gallery_view - 返回类型:
Vec<PuzzleWorkProfile> - 源码:
server-rs/crates/spacetime-module/src/puzzle.rs - 说明:拼图广场公开详情 source / 兼容投影,只暴露
publication_status = Published且visible = true的作品,但返回完整PuzzleWorkProfile,包含 levels / anchor_pack 等详情级字段;统一公开详情主路径通过public_work_detail_entry消费该 view,只保留平台详情页展示摘要。
SpacetimeDB view:puzzle_gallery_card_view
- Rust view:
puzzle_gallery_card_view - 返回类型:
Vec<PuzzleGalleryCardViewRow> - 源码:
server-rs/crates/spacetime-module/src/puzzle.rs - 说明:拼图公开列表 source 投影,只暴露
publication_status = Published且visible = 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引用,并返回hasMore、nextCursor与totalCount。 - 缓存策略:
api-server在PuzzleGalleryCache中缓存最终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_flight和genarrative.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_entrySELECT * FROM public_work_detail_entrySELECT * FROM bark_battle_gallery_viewSELECT * FROM puzzle_gallery_card_viewSELECT * FROM puzzle_clear_gallery_card_viewSELECT * FROM jump_hop_gallery_card_viewSELECT * FROM wooden_fish_gallery_card_viewSELECT * FROM custom_world_gallery_entrySELECT * FROM match_3_d_gallery_viewSELECT * FROM square_hole_gallery_viewSELECT * FROM visual_novel_gallery_viewSELECT * FROM big_fish_gallery_viewSELECT * 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_configSELECT * FROM creation_entry_type_config
private asset_object 不进入长期订阅;安全判断通过受限 procedure 在服务端按主键或 (bucket, object_key) 索引读取事务内真相,避免全表复制、订阅失败和增量同步延迟把已登记私有对象误判为 legacy 未登记对象。
跨玩法公开作品列表 / 详情主读模型是 public_work_gallery_entry 与 public_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_id、category_label、category_sort_order 后,后台 upsert、shared-contracts、module-runtime 和 spacetime-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=true、open=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_ENABLED 或 GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED。
统一公开作品 BFF 路由是 GET /api/public-works 与 GET /api/public-works/{publicWorkCode},响应契约由 shared-contracts::public_work 和 packages/shared/src/contracts/publicWork.ts 共同维护。前端首期仍走 BFF HTTP,不直接订阅 SpacetimeDB;后续若允许浏览器直连订阅,也只能订阅 public_work_gallery_entry / public_work_detail_entry 这类稳定公开 read model,不能订阅 puzzle_work_profile、custom_world_profile 等源表后自行拼装列表。设计细节见 docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md。
- 字段变更:
visible控制是否进入公开列表 / 详情,新作品默认false;旧迁移数据由migration.rs按历史公开默认补true。
quest_log
- Rust 结构体:
QuestLog - 源码:
server-rs/crates/spacetime-module/src/gameplay.rs
quest_record
- Rust 结构体:
QuestRecord - 源码:
server-rs/crates/spacetime-module/src/gameplay.rs
refresh_session
- Rust 结构体:
RefreshSession - 源码:
server-rs/crates/spacetime-module/src/auth/tables.rs
runtime_setting
- Rust 结构体:
RuntimeSetting - 源码:
server-rs/crates/spacetime-module/src/runtime/active/settings.rs - 说明:这是现役账号级公共设置表,保存
music_volume与platform_theme,不是旧玩法运行态数据壳。表结构与历史数据保持不变;鉴权后的GET/PUT /api/runtime/settings只能经spacetime-clientfacade 调用get_runtime_setting_or_default与upsert_runtime_setting_and_returnprocedure 读写,不从连接订阅 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
SpacetimeDB view:square_hole_gallery_view
- Rust view:
square_hole_gallery_view - 返回类型:
Vec<SquareHoleGalleryViewRow> - 源码:
server-rs/crates/spacetime-module/src/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/gameplay.rs
story_session
- Rust 结构体:
StorySession - 源码:
server-rs/crates/spacetime-module/src/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/gameplay.rs
user_account
- Rust 结构体:
UserAccount - 源码:
server-rs/crates/spacetime-module/src/auth/tables.rs - 职责:账号资料真相源,保存
phone_number_e164、display_name、avatar_url、created_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/visual_novel.rs
visual_novel_agent_session
- Rust 结构体:
VisualNovelAgentSessionRow - 源码:
server-rs/crates/spacetime-module/src/visual_novel.rs
visual_novel_runtime_event
- Rust 结构体:
VisualNovelRuntimeEvent - 源码:
server-rs/crates/spacetime-module/src/visual_novel.rs
visual_novel_runtime_history_entry
- Rust 结构体:
VisualNovelRuntimeHistoryEntryRow - 源码:
server-rs/crates/spacetime-module/src/visual_novel.rs
visual_novel_runtime_run
- Rust 结构体:
VisualNovelRuntimeRunRow - 源码:
server-rs/crates/spacetime-module/src/visual_novel.rs
visual_novel_work_profile
- Rust 结构体:
VisualNovelWorkProfileRow - 源码:
server-rs/crates/spacetime-module/src/visual_novel.rs
SpacetimeDB view:visual_novel_gallery_view
- Rust view:
visual_novel_gallery_view - 返回类型:
Vec<VisualNovelGalleryViewRow> - 源码:
server-rs/crates/spacetime-module/src/visual_novel.rs - 说明:视觉小说公开 source 投影,只暴露
publication_status = published的作品卡片字段,不把完整draft暴露给公开列表订阅;统一公开列表 / 详情主路径通过public_work_gallery_entry/public_work_detail_entry消费该 view 并映射成跨玩法契约。个人历史、详情、运行态和发布仍按原有 procedure / reducer 路径处理。 - 字段变更:
visible控制是否进入公开列表 / 详情,新作品默认false;旧迁移数据由migration.rs按历史公开默认补true。