修复账号画布绑定与切号恢复边界

按账号主体持久化项目与资源绑定并迁移旧版绑定
在线性化会话租约内冻结生成与编辑账本身份
通过 Runner owner claim 隔离跨 GUI 登录态
为编辑器幂等创建补充持久回执与公开契约
保留不确定本地登记现场并阻止重复生成
补充切号、恢复、下载、幂等和会话竞态回归
增加真实 SpacetimeDB 幂等事务门禁
This commit is contained in:
2026-08-24 01:32:30 +08:00
parent 29894de43d
commit ad93d4534d
45 changed files with 12113 additions and 1163 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,83 @@
import { spawn } from 'node:child_process';
import { describe, expect, it } from 'vitest';
import {
parseProcedureOutput,
parseSqlRows,
sanitizeDiagnostic,
stopStandalone,
} from './spacetime-editor-idempotency-smoke.mjs';
describe('SpacetimeDB editor idempotency smoke parsers', () => {
it('parses successful and failed procedure SATS results', () => {
expect(
parseProcedureOutput(
'[true,[0,["project-a","owner-a"]],[1,[]]]',
'create_editor_project_and_return',
),
).toEqual({
ok: true,
record: ['project-a', 'owner-a'],
errorMessage: null,
});
expect(
parseProcedureOutput(
'[false,[1,[]],[0,"幂等创建冲突"]]',
'create_editor_project_and_return',
),
).toEqual({
ok: false,
record: null,
errorMessage: '幂等创建冲突',
});
expect(
parseProcedureOutput(
'[true,[1,[]],[0,["receipt-owner",[],[]]],[1,[]]]',
'delete_editor_asset_folder_and_return',
),
).toEqual({
ok: true,
record: ['receipt-owner', [], []],
errorMessage: null,
});
});
it('parses only the rows from one JSON SQL statement', () => {
const stdout = JSON.stringify([
{
schema: { elements: [{ name: 'project_id' }] },
rows: [['project-a'], ['project-b']],
total_duration_micros: 12,
},
]);
expect(parseSqlRows(stdout, 'projects')).toEqual([
['project-a'],
['project-b'],
]);
expect(() => parseSqlRows('[]', 'projects')).toThrow(
'invalid result shape',
);
});
it('redacts an isolated CLI token from diagnostics', () => {
expect(sanitizeDiagnostic('failed token-private', ['token-private'])).toBe(
'failed [redacted]',
);
});
});
describe('SpacetimeDB editor idempotency smoke cleanup', () => {
it('terminates a live standalone-shaped child process', async () => {
const child = spawn(
process.execPath,
['-e', 'setInterval(() => {}, 1000)'],
{ stdio: 'ignore' },
);
await stopStandalone(child);
expect(child.exitCode !== null || child.signalCode !== null).toBe(true);
});
});