收敛 SpacetimeDB 项目指导 skill

新增 Genarrative 单一 SpacetimeDB 项目适配 skill

删除重复的 CLI、Rust、Concepts 项目 skill

更新 AGENTS 与协作工作流入口

同步项目决策记录和官方插件路由
This commit is contained in:
2026-08-27 11:48:35 +08:00
parent f1605de4d4
commit b07fccb9ae
8 changed files with 146 additions and 587 deletions
@@ -0,0 +1,126 @@
---
name: genarrative-spacetimedb
description: Genarrative 的 SpacetimeDB 项目适配规范。用于涉及 SpacetimeDB 架构、Rust module、schema、migration、reducer、procedure、view、绑定生成、CLI、MCP、发布、调试或运行时核验的任务。
---
# Genarrative SpacetimeDB 项目指导
本 skill 只保存 Genarrative 的项目约束和操作边界;SpacetimeDB 的通用 API、语言 SDK 和 CLI 手册由已安装的官方插件提供。项目规则覆盖插件示例中的默认值或与本仓库冲突的建议。
## 官方插件依赖
开始 SpacetimeDB 任务时,按任务范围读取官方插件 skill:
- `spacetimedb:concepts`:核心语义、表、reducer、procedure、view、订阅和身份。
- `spacetimedb:rust-server`Rust module、表属性、访问器、迁移兼容性和 SDK API。
- `spacetimedb:cli`:初始化、构建、发布、生成绑定、SQL、调用、日志和 server 管理。
- `spacetimedb:typescript-client`:前端生成绑定、订阅和 TypeScript 客户端 SDK;其它语言客户端按需读取插件对应 skill。
- `spacetimedb:mcp`:通过已连接的 MCP 操作运行中的数据库;没有 MCP 工具时使用 CLI 等价命令。
如果当前环境尚未安装插件,使用:
```bash
codex plugin marketplace add clockworklabs/SpacetimeDB --sparse .agents --sparse codex-plugin
codex plugin add spacetimedb\@spacetimedb-plugins
```
插件不可用时,以当前源码、`docs/`、生成绑定和仓库脚本为准,不凭记忆发明 SpacetimeDB API。
## 架构边界
Genarrative 的唯一有效后端路线是:
```text
server-rs + Axum + SpacetimeDB
```
- `module-*`:领域模型、命令、应用规则、领域事件和领域错误;不得直接依赖 Axum、SpacetimeDB table/reducer/procedure、`spacetime-client`、外部平台或文件系统。
- `spacetime-module`SpacetimeDB 表、reducer、procedure、view、migration、事务 adapter 和 row mapper。
- `spacetime-client`:后端访问 SpacetimeDB 的 typed facade;其它后端 crate 不直接创建第二套访问路径。
- `api-server`HTTP、SSE、BFF 和外部副作用编排。
- `platform-*`:OSS、LLM、认证、语音等外部平台能力。
- `shared-contracts` / `packages/shared`:前后端 DTO、公开契约和无业务真相的共享 TypeScript 代码。
- 前端只负责表现、交互、临时 UI 状态和后端结果渲染,不绕过 BFF/投影直接读取私有表或推导正式业务状态。
SpacetimeDB 是数据和事务层,不替代 `api-server` BFF、`spacetime-client` facade 或公开 read model。插件提供的“SpacetimeDB 可替代传统服务端”通用描述不能改变本项目边界。
## 语义与安全不变量
- Reducer 是原子事务写路径,不向调用者返回业务数据;读取通过订阅、read model、view 或 BFF。
- Reducer 必须确定性执行:不得访问文件系统、网络、系统时钟或外部随机源;使用 `ctx.timestamp``ctx.rng()` / `ctx.random()` 等 SpacetimeDB 能力。
- 授权使用上下文中的 `ctx.sender()`(或当前语言对应 API),不信任调用参数传入的身份。
- Auto-increment ID 不是排序依据;需要顺序时使用时间戳或显式序列字段。
- Private table 是后端事实;用户可见状态通过 BFF、投影或明确的 public table/view 暴露。公共表仍只能由 reducer/procedure 写入。
- Procedure 在 2.7 已稳定,可使用显式事务和 `ctx.http`Genarrative 默认仍把外部 provider 协议放在 `platform-*`,把编排放在 `api-server`,除非当前架构明确要求 module procedure。
- Event table 必须显式订阅,按插入事件消费;不要依赖其持久化行或 `OnUpdate`。需要更新回调时使用持久表或带主键的 procedural view。
- Standalone MCP 是 operator/developer 集成面,不是 BFF、facade 或公开 read model 的替代品。MCP/SQL/CLI 的写入都必须有明确授权;日常 smoke 优先只读。
## Schema 与迁移
修改现有 SpacetimeDB persistent table 时:
1. 新字段只能追加到 Rust 表结构体末尾,并设置明确的 `#[default(...)]`
2. 删除、改名、重排、改类型或破坏性约束变更前,必须先询问用户并确认迁移计划。
3. 同步更新 `server-rs/crates/spacetime-module/src/migration.rs`、后端架构文档中的表目录、生成绑定和相关契约/测试。
4. 运行:
```bash
npm run spacetime:generate
npm run check:spacetime-schema
```
Event table 的较宽松自动迁移规则不适用于 persistent table,不能借此绕过上述门禁。以当前源码和 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 为 schema 真相。
## CLI、目标 server 与本地开发
- 优先使用仓库 wrapper`npm run dev:spacetime`、`npm run dev:api-server`、`npm run spacetime:generate`。
- 直接使用 CLI 时始终显式传 `--server` 或 `--server-url`;不要依赖默认云端目标或个人 CLI 默认 server。
- 不新增 `maincloud` / `MAINCLOUD` 命令、环境变量、脚本或文档;历史残留只按历史处理。
- 人工命令、本地联调、排障步骤和文档示例禁止使用 `spacetime --root-dir`;本地数据隔离使用项目脚本或 `--data-dir`。
- `spacetime publish` 的 `--delete-data=always` 只在明确授权的破坏性操作中使用;schema 冲突优先按项目脚本和受控迁移流程处理。
- 项目 SpacetimeDB crate、SDK、CLI/standalone 和生成 bindings 按 `2.7.0` 对齐;官方 `v2.7.0-hotfix3` 是发行资产标签,运行时二进制仍应报告 `2.7.0`,当前 hotfix3 CLI commit 为 `d220349adb7af7eefa810eb08a185609356b83f6`,裸 tag commit `a08663c7b94688a2542577532d472f751e641f5b` 即使版本号相同也必须拒绝。升级时核对 Cargo 精确 pin、实际 CLI 和运行中服务二进制,不把本地 CLI 重装当作仓库升级。
本地开发默认由项目启动器管理端口;实际监听地址以 `.app/dev-stack.json` 和启动日志为准,不能从文档默认端口推断当前目标。发布后确认 api-server 使用的是同一 database、server 和 token。
## MCP 与运行时核验
如果当前会话暴露 SpacetimeDB MCP 工具,读取运行中的数据库优先使用 typed MCP:先 `list_databases` / `get_schema`,再做只读 SQL 或 `ping`;调用 reducer 或 SQL 写入前确认目标、身份和授权。没有 MCP 工具时使用显式目标的 CLI。2.7 standalone 的 MCP HTTP endpoint 是 `POST /v1/database/{name_or_identity}/mcp`,提供 `ping`、`get_schema`、`sql`、`call`;升级 smoke 在隔离数据库中只做 `initialize`、`tools/list`、`ping`、`get_schema`,除非写入明确属于任务范围。
排查“服务健康但业务不可用”时按顺序核对:
1. SpacetimeDB standalone 是否运行(本地优先 `npm run dev:spacetime`,主机侧核对 systemd)。
2. module 是否发布到 api-server 实际使用的同一个 server/database。
3. 生成绑定是否来自当前 module。
4. api-server 的 database、server URL 和 token 是否一致。
5. reducer/procedure 是否真正被调用;区分超时、权限、schema 不存在和业务错误。
6. `/healthz` / `/readyz` 通过但业务仍失败时,继续检查 API 日志和公开路由,不把健康检查当作业务成功证明。
主机升级需核对运行中进程而非只看 PATH:
```bash
type -a spacetime
spacetime --version
pid="$(systemctl show spacetimedb.service -p MainPID --value)"
readlink -f "/proc/${pid}/exe"
"/proc/${pid}/exe" --version
curl -fsS http://127.0.0.1:3101/v1/ping
```
## 修改后的最小验证
按范围执行定向测试/类型检查,并至少运行:
```bash
npm run check:encoding
git diff --check
```
涉及 schema 时追加 `npm run spacetime:generate` 和 `npm run check:spacetime-schema`;涉及 API 时按当前后端文档启动 `npm run dev:api-server` 并检查 `/healthz`。无法运行的验证要在交付说明中标记为未验证并说明原因。
## 参考入口
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- `server-rs/README.md`
- `scripts/check-spacetime-schema-guard.mjs`
- `scripts/check-server-rs-ddd-boundaries.mjs`
-178
View File
@@ -1,178 +0,0 @@
---
name: spacetimedb-cli
description: SpacetimeDB 2.7 CLI reference for Genarrative. Use for spacetime build, publish, generate, call, sql, logs, server management, local dev, explicit server targeting, version checks, and remote runtime verification.
---
# SpacetimeDB CLI
Use this skill when working with the `spacetime` CLI in Genarrative. Prefer repository scripts when they exist, and keep every operation pinned to an explicit target server or local process.
## Genarrative Rules
- Do not rely on the default SpacetimeDB cloud target. Pass `--server` or `--server-url` explicitly in scripts, docs, smoke tests, and manual troubleshooting.
- Do not introduce `maincloud` / `MAINCLOUD` commands, env vars, or docs. Treat old references as historical residue.
- Do not use `spacetime --root-dir` in manual commands or docs. Use project scripts, `--data-dir`, explicit `--server`, or the configured running service.
- For repository version upgrades, update `server-rs/Cargo.toml` exact pins, regenerate bindings, and verify the actual CLI/runtime version. Do not treat a local CLI reinstall as a repo upgrade.
- For host upgrades, verify the running service binary, not just shell PATH: `systemctl show ... MainPID` -> `/proc/$pid/exe --version` -> `/v1/ping`.
## Core Commands
```bash
# Build module
spacetime build
spacetime build --debug
# Publish to an explicit server
spacetime publish my-database --server http://127.0.0.1:3101 --yes=migrate,break-clients
# Destructive publish only when explicitly intended
spacetime publish my-database --server http://127.0.0.1:3101 --delete-data=always --yes=delete-data,migrate
# Delete data only for breaking schema conflicts
spacetime publish my-database --server http://127.0.0.1:3101 --delete-data=on-conflict --yes=migrate
# Generate bindings
spacetime generate --lang typescript|csharp|rust|unrealcpp --out-dir ./bindings --module-path ./server
```
## Genarrative Local Workflow
```bash
# Prefer project wrappers
npm run dev:spacetime
npm run dev:api-server
npm run spacetime:generate
# Query local database
spacetime sql my-db --server http://127.0.0.1:3101 "SELECT * FROM players"
# Logs
spacetime logs my-db --server http://127.0.0.1:3101 -f
```
## Database Interaction
```bash
# SQL / describe
spacetime sql my-db --server http://127.0.0.1:3101 "SELECT * FROM users"
spacetime describe my-db --server http://127.0.0.1:3101 --json
spacetime describe my-db table users --server http://127.0.0.1:3101 --json
# Reducer/procedure calls. Arguments are positional JSON values.
spacetime call --server http://127.0.0.1:3101 my-db my_reducer '"value"' '123'
# 2.5+ accepts hex strings for Identity arguments without full JSON tuple syntax.
spacetime call --server http://127.0.0.1:3101 my-db reducer_needing_identity 0xabc123...
# Subscribe from CLI
spacetime subscribe my-db "SELECT * FROM users" --num-updates 10 --server http://127.0.0.1:3101
```
## Standalone MCP Endpoint (2.7)
SpacetimeDB 2.7 standalone exposes an authenticated JSON-RPC MCP endpoint at
`POST /v1/database/{name_or_identity}/mcp`. It advertises `ping`, `get_schema`,
`sql`, and `call`. The SQL and reducer tools execute with the bearer token's
identity, so keep routine smoke checks read-only.
```bash
curl -fsS \
-H "Authorization: Bearer ${SPACETIME_TOKEN}" \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"genarrative-smoke","version":"1.0.0"}}}' \
http://127.0.0.1:3101/v1/database/my-db/mcp
curl -fsS \
-H "Authorization: Bearer ${SPACETIME_TOKEN}" \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"ping","arguments":{"message":"genarrative"}}}' \
http://127.0.0.1:3101/v1/database/my-db/mcp
```
For repository upgrade validation, also call `tools/list` and the read-only
`get_schema` tool against an isolated local database. Do not use `sql` or `call`
for writes unless that mutation is explicitly in scope.
## Server & Auth
```bash
spacetime server list
spacetime server add local --url http://localhost:3000 --default
spacetime server add genarrative-dev --url http://127.0.0.1:3101
spacetime server ping genarrative-dev
spacetime login
spacetime login --token <token>
spacetime login show
spacetime logout
```
## Version & Runtime Verification
```bash
# CLI resolution can be misleading; compare all candidates when diagnosing.
type -a spacetime
spacetime --version
spacetime version list
# Verify a systemd service binary actually changed.
pid="$(systemctl show spacetimedb.service -p MainPID --value)"
readlink -f "/proc/${pid}/exe"
"/proc/${pid}/exe" --version
curl -fsS http://127.0.0.1:3101/v1/ping
```
## Flags
| Flag | Description |
|------|-------------|
| `--server`, `-s` | Target server nickname, host, or URL |
| `--yes`, `-y` | Non-interactive prompt skipping; in 2.6+ use scoped values |
| `--delete-data`, `-c` | Publish data policy: `always`, `on-conflict`, or `never` |
| `--module-path`, `-p` | Module project path |
| `--bin-path`, `-b` | Publish/generate from compiled wasm |
| `--no-config` | Ignore `spacetime.json` |
| `--env` | Select config file layering environment |
## Troubleshooting
### Not Logged In
```bash
spacetime login
```
### Server Not Responding
```bash
spacetime server ping <server>
curl -fsS http://127.0.0.1:3101/v1/ping
```
For local Genarrative work, start SpacetimeDB first with `npm run dev:spacetime`, then start `npm run dev:api-server`.
### Schema Conflict
```bash
spacetime publish my-db --server http://127.0.0.1:3101 --delete-data=on-conflict --yes=migrate
```
Use `--delete-data=always` only with explicit approval.
### Version Mismatch
```bash
rg -n 'spacetimedb' server-rs/Cargo.toml
spacetime --version
spacetime version list
pid="$(systemctl show spacetimedb.service -p MainPID --value)"
"/proc/${pid}/exe" --version
```
## Notes
- Procedure calls remain stable in 2.7; module HTTP handlers/webhooks and RLS capabilities still require their documented gates.
- 2.5 fixed `publish --delete-data` config fallback; 2.6 kept that behavior and improved CLI binary distribution; 2.7 adds `spacetime sql --format json` and database `lock` / `unlock`.
- The official 2.7.0 Linux release archives and container image currently use the `v2.7.0-hotfix3` asset tag while binaries report `2.7.0`; keep the asset tag distinct from the runtime version check.
- Do not assume `spacetime version install 2.7.0` selected hotfix3: stale updater metadata can install bare-tag commit `a08663c7...`. For the current release, verify CLI commit `d220349a...` and use the official hotfix3 archive or repository provision flow when it differs.
- Genarrative scripts should pass `--server` or `--server-url` explicitly instead of relying on CLI defaults.
-119
View File
@@ -1,119 +0,0 @@
---
name: spacetimedb-concepts
description: Understand SpacetimeDB 2.7 architecture, reducer/procedure/table/view semantics, schema evolution, subscriptions, identity, and Genarrative-specific backend boundaries. Use when designing or reviewing SpacetimeDB-backed features.
---
# SpacetimeDB Core Concepts
SpacetimeDB is a relational database that also executes application logic in uploaded modules. In Genarrative, it is the data and transaction layer behind `server-rs + Axum + SpacetimeDB`, not a replacement for the `api-server` BFF or external platform adapters.
## Genarrative Boundaries
- Domain rules live in `module-*`.
- SpacetimeDB tables, reducers, procedures, migrations, row mappers, and read models live in `spacetime-module`.
- Backend access goes through `spacetime-client` facades.
- HTTP/SSE/BFF and external orchestration stay in `api-server`.
- External side effects stay in `platform-*`.
- Frontend renders backend truth and must not bypass BFF/projections to invent formal business state.
## Critical Rules
1. **Reducers are transactional**: they do not return data to callers. Read through subscriptions, read models, views, or BFF endpoints.
2. **Reducers are deterministic**: no filesystem, network, wall-clock, or external RNG. Use `ctx.timestamp`, `ctx.rng()` / `ctx.random()`, and tables.
3. **Procedures are stable in 2.7**: they can use explicit transactions and outgoing HTTP via `ctx.http`.
4. **Identity comes from context**: use `ctx.sender()` or language equivalent for authorization. Never trust identity passed as an argument.
5. **Auto-increment IDs are not ordering guarantees**: gaps are normal. Use timestamps or explicit sequence columns for ordering.
6. **Schema changes need migration discipline**: existing Genarrative table fields must be appended with defaults; update migration code, table catalog, generated bindings, and run `npm run check:spacetime-schema`.
## Tables
- Private tables are the default; only reducers/procedures and database owners can access them.
- Public tables are exposed to clients through subscriptions. Writes still go through reducers/procedures.
- Organize data by access pattern when bandwidth or update frequency differs.
- Existing persistent tables in Genarrative are conservative: no rename, delete, reorder, or type changes without a user-approved migration plan.
## Reducers
Reducers are deterministic transactional functions. They are the primary client-invoked mutation path.
- No global mutable state.
- No filesystem, network, timers, or non-deterministic RNG.
- Return `Result<(), String>` for expected sender-visible errors.
- Use `ctx.sender()` for authorization.
- Store persistent state in tables.
## Procedures
Procedures are stable in 2.7. They can be scheduled, can open explicit transactions with `with_tx` / `try_with_tx`, and can use outgoing HTTP (`ctx.http`).
Genarrative default: keep external provider protocols in `platform-*` and orchestration in `api-server` unless a task explicitly moves a workflow into a module procedure.
Module HTTP handlers/webhooks and RLS `client_visibility_filter` remain subject to their documented gates in 2.7.
## Views
Views expose computed read-only data. SpacetimeDB 2.7 supports primary keys on procedural views in Rust, TypeScript, C#, and C++. Clients can receive update events when subscribed to such views with primary keys. Ensure the view never returns duplicate primary keys, because that can fail view refresh and roll back the triggering transaction.
## Event Tables
Event tables broadcast reducer/procedure-specific facts to subscribers and must be subscribed explicitly. They are excluded from `subscribe_to_all_tables()`.
Since 2.6, event tables support broader layout-altering automigrations, including column removal, reordering, and type changes that regular tables reject. This relaxed migration behavior is for event-only tables, not persistent tables.
Event-table primary keys and constraints are transaction-scoped. They can reject duplicate event rows within one transaction, but event rows are not retained in client cache, so clients observe event tables through insert callbacks only. Do not design Genarrative event tables around `OnUpdate` / `on_update` / `onUpdate`; use a persistent table or a primary-keyed procedural view when update callbacks are required.
Official 2.4.1 through 2.7 release notes document primary-key-backed update callbacks for procedural views, not event tables.
## Subscriptions
1. Subscribe to SQL queries or generated table/query builders.
2. Receive initial matching rows.
3. Receive updates when subscribed rows change.
4. Render from subscribed data, not reducer return values.
Best practices:
- Group subscriptions by lifetime.
- Subscribe to new data before unsubscribing old data during transitions.
- Avoid overlapping queries that duplicate row delivery.
- Use indexes for subscribed filters.
## Standalone MCP
SpacetimeDB 2.7 standalone exposes `POST /v1/database/{name_or_identity}/mcp`
using MCP JSON-RPC protocol `2025-06-18`. Its tools are `ping`, `get_schema`,
`sql`, and `call`; SQL and reducer calls run with the authenticated caller's
identity. In Genarrative this is an operator/developer integration surface, not
a replacement for `api-server` BFF routes, `spacetime-client` facades, or public
read models. Upgrade smoke should use an isolated local database and restrict
itself to `initialize`, `tools/list`, `ping`, and `get_schema` unless writes are
explicitly intended.
## 2.2.0 to 2.7.0 Delta
Genarrative introduced SpacetimeDB around 2.2.0. Important changes since then:
- **2.2.0**: v3 WebSocket transport and TS SDK default, safer production operations (`lock`/`unlock`, safer `delete`, better `publish --yes`), TS React `useProcedure`, table clearing APIs, empty-table drop automigration, primary-key migration fixes, bytes-key B-tree support, durability hardening.
- **2.3.0**: first-party Godot SDK, more WebSocket pipelining/batching, HTTP/2 backend support, Vue `useProcedure`, Unity 6 WebGL support, commitlog compression/throughput improvements, Rust `DbContext` generics, `ReducerContext::identity` deprecated in favor of `database_identity`, connection lifecycle and unsubscribe fixes.
- **2.4.0**: unstable module HTTP handlers/webhooks, faster synchronous WASM reducer runtime, commitlog resume truncation fix for silent data loss risk, better commitlog decode context, V8 heap metrics for procedure workers, JS execution-time billing regression reverted.
- **2.4.1**: Rust and TypeScript procedural views can declare primary keys, enabling `OnUpdate` events for subscribed views; fixed index schema from ST tables.
- **2.5.0**: procedures are stable, C# procedural views gain primary keys, event tables allow broader layout-altering automigrations, BTreeSet storage makes row insertion deterministic and avoids accidentally quadratic bulk insert behavior, `wasm_memory_bytes` billing metric semantics changed, template version constraints unified, `publish --delete-data` config fallback fixed, CLI `call` accepts hex Identity arguments.
- **2.6.0**: procedural-view primary keys are available across Rust, TypeScript, and C#, commitlog gains `max_segment_size` / `write_buffer_size` / `preallocate_segments`, the default write buffer increases for throughput, event-table automigrations improve, and CLI binary distribution expands.
- **2.6.1**: procedure contexts again receive the caller `Identity` and `ConnectionId`; generated TypeScript `Option<T>` fields use optional keys; `spacetime init --template` lists available templates when no template argument is supplied.
- **2.7.0**: existing tables can add unique or primary-key constraints when current data satisfies them; standalone exposes an authenticated database MCP endpoint; Rust adds context-capability and table-accessor traits; `spacetime sql --format json` and database locking are available; view cleanup, backing-table migration, connection metrics, and memory metrics improve. Official current release assets use the `v2.7.0-hotfix3` tag while binaries report `2.7.0`.
## Debugging Checklist
1. Is the Genarrative SpacetimeDB server running? Use `npm run dev:spacetime` locally or host-local `systemctl`.
2. Is the module published to the same server the API uses?
3. Are generated bindings current? Use `npm run spacetime:generate`.
4. Is `api-server` using the same database and token?
5. Is the reducer/procedure actually called?
6. Did `/healthz` / `/readyz` pass while business SpacetimeDB calls still timeout? Inspect API logs and public route behavior.
## Editing Behavior
- Make the smallest change necessary.
- Do not invent SpacetimeDB APIs; verify against current docs, generated bindings, or source.
- For Genarrative schema edits, update migration code, table catalog/docs, generated bindings, and relevant tests.
- After schema edits, run `npm run spacetime:generate` and `npm run check:spacetime-schema`.
-280
View File
@@ -1,280 +0,0 @@
---
name: spacetimedb-rust
description: Develop SpacetimeDB 2.7 server modules in Rust for Genarrative. Use when writing or reviewing tables, reducers, procedures, views, migrations, row mappers, schema changes, and module logic.
---
# SpacetimeDB Rust Module Development
Use this skill for Rust code in `server-rs/crates/spacetime-module` and related Genarrative schema/migration work.
## Genarrative Rules
- Keep domain rules in `module-*`; keep SpacetimeDB tables, reducers, procedures, views, mappers, and transaction adapters in `spacetime-module`.
- Existing table fields must be appended at the end with explicit defaults. Do not rename, remove, reorder, or change field types without a user-confirmed migration plan.
- After schema changes, update `migration.rs`, table catalog/docs, generated bindings, and run `npm run spacetime:generate` plus `npm run check:spacetime-schema`.
- Private tables are backend facts. Expose user-visible state through BFF endpoints/read models rather than direct client SQL.
## Hallucinated APIs: Do Not Use
```rust
#[derive(Table)] // Tables use #[table], not derive
#[derive(Reducer)] // Reducers use #[reducer], not derive
#[derive(SpacetimeType)] // Do not derive this on #[table] structs
pub fn reducer(ctx: &mut ReducerContext) {} // Use &ReducerContext
ctx.db.player // Use ctx.db.player()
ctx.db.player.find(id) // Use ctx.db.player().id().find(&id)
ctx.sender // Use ctx.sender()
ctx.db.user().name().update(..) // Update by primary key only
spacetimedb = { version = "...", features = ["unstable"] } // Not needed for procedures since 2.5
```
## Required Patterns
```rust
use spacetimedb::{reducer, table, Identity, ReducerContext, Table, Timestamp};
use spacetimedb::SpacetimeType; // Custom types only, not tables
#[table(accessor = player, public)]
pub struct Player {
#[primary_key]
#[auto_inc]
pub id: u64,
pub owner: Identity,
pub name: String,
pub created_at: Timestamp,
}
#[reducer]
pub fn create_player(ctx: &ReducerContext, name: String) -> Result<(), String> {
if name.trim().is_empty() {
return Err("name required".to_string());
}
ctx.db.player().try_insert(Player {
id: 0,
owner: ctx.sender(),
name,
created_at: ctx.timestamp,
})?;
Ok(())
}
```
Hard requirements:
- Import `Table` for table operations.
- Use `accessor = identifier`, not string literals.
- Use `ctx.sender()` for authorization.
- Use `ctx.rng()` / `ctx.random()` / `ctx.new_uuid_*()` for deterministic randomness and UUIDs.
- Use `Result<(), String>` for expected sender errors; avoid panics except impossible states.
- Use `try_insert()` in `Result` reducers when constraint violations should be reported cleanly.
## Tables
```rust
#[spacetimedb::table(accessor = game_tick_schedule, scheduled(game_tick))]
pub struct GameTickSchedule {
#[primary_key]
#[auto_inc]
pub scheduled_id: u64,
pub scheduled_at: ScheduleAt,
}
```
Table attributes:
| Attribute | Description |
|-----------|-------------|
| `accessor = identifier` | API name used in `ctx.db.{accessor}()` |
| `public` | Visible to clients via subscriptions |
| `event` | Transient event table |
| `scheduled(function_name)` | Schedule table that triggers a reducer/procedure |
| `index(accessor = idx, btree(columns = [a, b]))` | Multi-column index |
Column attributes:
| Attribute | Description |
|-----------|-------------|
| `#[primary_key]` | One primary key per table |
| `#[auto_inc]` | Auto-generates integer values when inserting `0` |
| `#[unique]` | Unique constraint and `find()` accessor |
| `#[index(btree)]` | B-tree index and `filter()` accessor |
| `#[default(...)]` | Required for new fields on existing Genarrative tables |
## Genarrative Schema Change Pattern
```rust
#[spacetimedb::table(accessor = creation_entry_config, public)]
pub struct CreationEntryConfig {
#[primary_key]
pub id: u64,
pub existing_field: String,
// Append new fields at the end and provide a default.
#[default(false)]
pub new_flag: bool,
}
```
Then update `migration.rs`, table catalog/docs, generated bindings, and run:
```bash
npm run spacetime:generate
npm run check:spacetime-schema
```
## Table Operations
```rust
let row = ctx.db.player().insert(Player { id: 0, owner, name, created_at });
ctx.db.player().try_insert(row)?;
let by_id = ctx.db.player().id().find(&123u64);
for player in ctx.db.player().owner().filter(&ctx.sender()) {}
for player in ctx.db.player().level().filter(&(18u32..=65u32)) {}
for player in ctx.db.player().iter() {}
let count = ctx.db.player().count();
if let Some(player) = ctx.db.player().id().find(&id) {
ctx.db.player().id().update(Player { name: new_name, ..player });
}
ctx.db.player().id().delete(&id);
```
For delete/update based on non-PK filters, collect keys first to avoid iterator invalidation.
## Indexes
```rust
#[spacetimedb::table(
accessor = score,
public,
index(accessor = by_player_level, btree(columns = [player_id, level]))
)]
pub struct Score {
pub player_id: u32,
pub level: u32,
pub points: i64,
}
for row in ctx.db.score().by_player_level().filter(&(42,)) {}
for row in ctx.db.score().by_player_level().filter(&(42, 5)) {}
```
## Event Tables
```rust
#[table(accessor = damage_event, public, event)]
pub struct DamageEvent {
pub target: Identity,
pub amount: u32,
}
#[reducer]
fn deal_damage(ctx: &ReducerContext, target: Identity, amount: u32) {
ctx.db.damage_event().insert(DamageEvent { target, amount });
}
```
Event tables must be subscribed explicitly and are excluded from `subscribe_to_all_tables()`.
Since 2.6, event tables support broader layout-altering automigrations than regular tables, including column removal, reordering, and type changes. This relaxed migration policy does not apply to persistent tables.
Event-table primary keys and constraints are enforced only within the current transaction. They do not make event rows persistent, and client SDKs expose event tables as insert-only event streams. Do not rely on `OnUpdate` / `on_update` / `onUpdate` for event tables; use a persistent table or a primary-keyed procedural view when update callbacks are required.
Official 2.4.1 through 2.7 release notes tie primary-key-backed update callbacks to procedural views, not event tables.
## Views
```rust
#[spacetimedb::view(accessor = my_players, public, primary_key = id)]
pub fn my_players(ctx: &spacetimedb::ViewContext) -> Vec<Player> {
ctx.db.player().owner().filter(&ctx.sender()).collect()
}
```
Rust and TypeScript gained primary key support for procedural views in 2.4.1. With primary keys, clients can receive update events when subscribed to such views. Avoid duplicate primary keys in view results.
## Lifecycle & Scheduled Reducers
```rust
#[spacetimedb::reducer(init)]
pub fn init(ctx: &ReducerContext) -> Result<(), String> { Ok(()) }
#[spacetimedb::reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) -> Result<(), String> { Ok(()) }
#[spacetimedb::reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) -> Result<(), String> { Ok(()) }
use spacetimedb::{ScheduleAt, TimeDuration};
ctx.db.game_tick_schedule().insert(GameTickSchedule {
scheduled_id: 0,
scheduled_at: ScheduleAt::Interval(std::time::Duration::from_millis(100).into()),
});
let run_at = ctx.timestamp + std::time::Duration::from_secs(60);
ctx.db.game_tick_schedule().insert(GameTickSchedule {
scheduled_id: 0,
scheduled_at: ScheduleAt::Time(run_at),
});
```
For scheduled reducers, check `ctx.sender_auth().is_internal()` when the reducer should only be system-triggered.
## Procedures
Procedures remain stable in 2.7 and no longer require the `unstable` feature.
```rust
use spacetimedb::{procedure, ProcedureContext};
#[procedure]
fn save_external_data(ctx: &mut ProcedureContext, url: String) -> Result<(), String> {
let body = ctx.http.get(url).send()?.text()?;
ctx.try_with_tx(|tx| {
tx.db.external_data().insert(ExternalData { id: 0, content: body });
Ok(())
})?;
Ok(())
}
```
| Reducers | Procedures |
|----------|------------|
| `&ReducerContext` | `&mut ProcedureContext` |
| Direct `ctx.db` access | Use `with_tx()` / `try_with_tx()` |
| No HTTP/network | Outgoing HTTP via `ctx.http` |
| Deterministic transaction path | Side-effect-capable workflow path |
In Genarrative, keep external provider protocols in `platform-*` by default unless the architecture explicitly moves that workflow into the module.
## Identity & Auth
```rust
fn require_owner(ctx: &ReducerContext, owner: &Identity) -> Result<(), String> {
if ctx.sender() != *owner {
return Err("Not authorized".to_string());
}
Ok(())
}
```
`ReducerContext::identity` is deprecated since 2.3; use the current database/module identity API when needed, and use `ctx.sender()` for caller identity.
## Commands
```bash
spacetime build
spacetime publish my_database --server http://127.0.0.1:3101 --module-path . --yes=migrate
spacetime publish my_database --server http://127.0.0.1:3101 --delete-data=on-conflict --module-path . --yes=migrate
spacetime logs my_database --server http://127.0.0.1:3101
spacetime call --server http://127.0.0.1:3101 my_database create_player '"Alice"'
spacetime sql my_database --server http://127.0.0.1:3101 "SELECT * FROM player"
npm run spacetime:generate
npm run check:spacetime-schema
```
+1 -1
View File
@@ -42,7 +42,7 @@
- 涉及 AI 游戏创作独立 App、多智能体 Runtime、本地项目产物或本地 HTTP 预览时,先读取 [`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`](docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md)。
- 新增、补齐、迁移或重构玩法入口、玩法类型、创作工作台、生成页、结果页、发布、运行态、作品架、广场或公开 read model 前,必须读取并按 [`genarrative-play-type-integration`](.codex/skills/genarrative-play-type-integration/SKILL.md) 执行。
- 涉及 `npm run dev` / `npm run dev:spacetime` / `npm run dev:api-server` / `npm run dev:web` / `npm run dev:admin-web` 的端口探测、端口漂移、SpacetimeDB publish server、api-server 环境变量、Vite 代理目标或后台 dev 端口时,按 [`.hermes/skills/genarrative-dev-stack-port-routing/SKILL.md`](.hermes/skills/genarrative-dev-stack-port-routing/SKILL.md) 执行。
- 涉及 SpacetimeDB 的设计、实现、脚本、调试、发布、绑定生成、schema、reducer、procedure、view 或 Rust API 时,必须读取并按 [`spacetimedb-cli`](.codex/skills/spacetimedb-cli/SKILL.md)、[`spacetimedb-rust`](.codex/skills/spacetimedb-rust/SKILL.md)、[`spacetimedb-concepts`](.codex/skills/spacetimedb-concepts/SKILL.md) 中相关 skill 执行。
- 涉及 SpacetimeDB 的设计、实现、脚本、调试、发布、绑定生成、schema、reducer、procedure、view 或 API 时,必须读取并按 [`genarrative-spacetimedb`](.codex/skills/genarrative-spacetimedb/SKILL.md) 执行。
## 后端红线
@@ -2177,13 +2177,15 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 验证方式:微信小程序首点登录仍打开原生登录页;小程序支付仍跳转 `/pages/wechat-pay/index` 并保留 hash 回灌确认;订阅授权仍跳转 `/pages/subscribe-message/index` 且返回不阻断生成;普通浏览器分享、H5 支付和 Native 二维码支付不受影响。前端验证运行 HostBridge、auth、payment、分享、订阅和个人中心充值相关定向测试,并执行 `npm run typecheck``npm run check:encoding`
- 关联文档:`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`
## 2026-06-15 SpacetimeDB 本地 skills 只保留 CLI / Concepts / Rust
## 2026-06-15 SpacetimeDB 本地 skills 范围(已由 2026-08-27 决策覆盖)
> 2026-08-27 覆盖说明:本节记录的“三个本地 skill”方案已收敛为单一项目适配层;当前口径见下方“SpacetimeDB 项目 skill 与官方插件职责收敛”。
- 背景:本仓库的 SpacetimeDB 接入已固定为 `server-rs + Axum + SpacetimeDB`,本地 skill 需要从上游 SpacetimeDB `skills/` 更新到 2.5 口径,同时避免继续维护当前项目不使用的 TypeScript server/client、C# 和 Unity 专用 skill。
- 决策:`.codex/skills/` 下只保留 `spacetimedb-cli``spacetimedb-concepts``spacetimedb-rust` 三个本地 SpacetimeDB skill;删除 `spacetimedb-typescript``spacetimedb-csharp``spacetimedb-unity`。前端 / Node 侧如需处理 SpacetimeDB 订阅或绑定,按当前生成绑定、项目代码和官方文档核对,不再依赖仓库内单独 TypeScript skill
- 影响范围:`AGENTS.md` SpacetimeDB skill 清单`.codex/skills/` 本地 skill 维护范围、后续 SpacetimeDB 设计 / CLI / Rust module 开发协作口径
- 验证方式:用上游 `clockworklabs/SpacetimeDB@master``skills/` 目录对照,运行本地 skill 校验、删除引用扫描、`git diff --check -- .codex/skills AGENTS.md .hermes/shared-memory/decision-log.md``npm run check:encoding`
- 关联文档:`AGENTS.md``.codex/skills/spacetimedb-cli/SKILL.md``.codex/skills/spacetimedb-concepts/SKILL.md``.codex/skills/spacetimedb-rust/SKILL.md`
- 决策:当时仅在仓库内维护与当前后端路线相关的 SpacetimeDB skill,通用 SDK/CLI 内容按上游资料核对;该历史范围已由 2026-08-27 的项目适配层方案替代
- 影响范围:当时的 `AGENTS.md` SpacetimeDB skill 清单和本地 skill 维护范围;当前范围以新的项目适配层及官方插件路由为准
- 验证方式:保留当时的上游 skill 对照、本地 skill 校验、删除引用扫描、diff 和编码检查记录
- 关联文档:`AGENTS.md``.codex/skills/genarrative-spacetimedb/SKILL.md``docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`
## 2026-06-13 图片大图预览统一为黑底全屏查看器
@@ -7747,3 +7749,9 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- DirectProject 的 Codex cwd 固定为真实 `game/` 目录时,原生文件工具和 patch 必须使用 cwd 相对路径(`index.html``style.css``game.js`);`game/...` 仅用于 AGC manifest、回执和客户端投影,不能作为 cwd 内原生 patch 路径,以避免 `writing outside of the project`
- 直连 Runtime 已取得 Developer Key 时,资源编辑的 `remote_credentials` 是该操作的完整身份边界;其中冻结平台快照为空表示 Developer 模式,禁止再从进程全局 GUI 登录态补回账号快照。平台账号模式仍只使用同一组凭据捕获的快照。
- 回归覆盖 Direct 系统提示路径合同和 Developer Key / GUI 快照隔离;未触碰用户项目 `.agent` 锁、账本或凭据。
## 2026-08-27 SpacetimeDB 项目 skill 与官方插件职责收敛
- 决策:`.codex/skills/` 下的 SpacetimeDB 指导收敛为单一 `.codex/skills/genarrative-spacetimedb/SKILL.md`。官方 `spacetimedb` 插件负责通用 concepts、Rust server、CLI、TypeScript client 和 MCP 知识;项目 skill 只保留 Genarrative 的架构边界、schema/migration 门禁、目标 server 安全规则、运行时排障和验证路径。
- 路由:涉及 SpacetimeDB 的任务统一先读取项目适配 skill,再按需读取 `spacetimedb:concepts``spacetimedb:rust-server``spacetimedb:cli``spacetimedb:typescript-client``spacetimedb:mcp`。插件通用示例不得覆盖项目禁止 `maincloud`、禁止人工 `spacetime --root-dir`、显式 server 和后端分层等约束。
- 安装:团队环境缺少插件时使用 `codex plugin marketplace add clockworklabs/SpacetimeDB --sparse .agents --sparse codex-plugin``codex plugin add spacetimedb\@spacetimedb-plugins`;个人配置、缓存和凭据不进入仓库。
@@ -35,6 +35,8 @@
## 验证路由
SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.md`;该项目适配层按需调用已安装的官方 `spacetimedb` 插件 skill,插件提供通用 SDK/CLI/MCP 知识,项目 skill 负责 Genarrative 架构边界和验证门禁。
按改动范围选择定向门禁,不以无关全量扫描代替契约验证:
| 范围 | 至少运行 |
@@ -73,11 +73,11 @@ RAG 默认不安装运行时依赖,也不把 LanceDB、Transformers.js 或本
## SpacetimeDB 规则
涉及 SpacetimeDB 设计、实现、脚本、调试、发布、绑定生成、schema、reducer、procedure、view 或 Rust API 时,先读取对应 skill
涉及 SpacetimeDB 设计、实现、脚本、调试、发布、绑定生成、schema、reducer、procedure、view 或 API 时,先读取项目适配 skill
- `.codex/skills/spacetimedb-cli/SKILL.md`
- `.codex/skills/spacetimedb-rust/SKILL.md`
- `.codex/skills/spacetimedb-concepts/SKILL.md`
- `.codex/skills/genarrative-spacetimedb/SKILL.md`
该 skill 按任务范围路由到官方 SpacetimeDB 插件的 `spacetimedb:concepts``spacetimedb:rust-server``spacetimedb:cli``spacetimedb:typescript-client``spacetimedb:mcp` skill;项目边界覆盖插件通用示例。插件缺失时按项目 skill 中的安装命令补齐,个人插件配置、缓存和凭据不得进入仓库。
已有表新增字段时,字段必须放在 Rust 表结构体最后,并设置明确默认值。删除、改名、重排或改类型前必须先询问用户并确认迁移计划。