fix:mcp-sibling-create-draft-confirmation
This commit is contained in:
1 parent
b2c22ca050
commit
ce8609116b
5 files changed
+115
-1
No files matched your search
@@ -131,3 +131,5 @@ git config remote.pushDefault github
|
||||
| [Gold API](https://gold-api.com/docs) | [黄金 XAU](https://api.gold-api.com/price/XAU)、[白银 XAG](https://api.gold-api.com/price/XAG) | 美元/金衡盎司参考价;以 31.1034768 克/金衡盎司换算为每克价格。已配置贵金属每日尝试更新,可在网站手动刷新。 | 无需密钥;请求固定 XAU/XAG 品种,不发送个人数据和持仓。 | 12 秒超时;格式、时间或汇率异常时保留之前的报价与估值;已有历史导入报价保留。 |
|
||||
|
||||
上述接口由后端访问,浏览器业务请求使用本站 `/api`,第三方不会收到本应用登录 Cookie。Gold API 返回的参考价只用于持仓估值,不包含饰品工费、买卖价差或回收折价。外币价格使用最新可用公共日汇率,休市时两类报价时间可能不同;报价日期会显示在界面并随备份保存。自动估价为可选项,开启后新增估值历史,不改写历史记录。
|
||||
|
||||
MCP 同一连接、同一账目快照下的多个新增账号草稿可依次审阅确认;其他修改仍保留状态冲突检查。修复前已失效的草稿需只为尚未新增的账号重新生成,见 [多账号草稿确认修复](docs/fix-mcp-create-drafts-2026-10-04.md)。
|
||||
@@ -337,6 +337,22 @@ export class AgentOperations {
|
||||
if ((await this.state(r.userId)) !== row.snapshot)
|
||||
throw new ConflictException('账目已变化,请取消并重新创建操作');
|
||||
const result = await this.execute(t, grant, p);
|
||||
if (row.tool === 'position_create') {
|
||||
// Advance only sibling additions reviewed against the same snapshot.
|
||||
// The user lock and transaction keep edits and other grants stale.
|
||||
await this.db.agentOperation.updateMany({
|
||||
where: {
|
||||
userId: r.userId,
|
||||
grantId: row.grantId,
|
||||
tool: 'position_create',
|
||||
status: 'pending',
|
||||
snapshot: row.snapshot,
|
||||
id: { not: row.id },
|
||||
expiresAt: { gt: new Date() },
|
||||
},
|
||||
data: { snapshot: await this.state(r.userId) },
|
||||
});
|
||||
}
|
||||
return this.view(
|
||||
await this.db.agentOperation.update({
|
||||
where: { id },
|
||||
|
||||
@@ -198,6 +198,79 @@ test('official SDK Streamable HTTP: real MySQL full business paths, drafts, isol
|
||||
const applied = await confirm(d, draft);
|
||||
assert.ok(applied.result.id);
|
||||
assert.equal((await call(d, 'positions_list')).total, 1);
|
||||
// Only independent creates from the same connection and snapshot may advance.
|
||||
const batchState = (await call(d, 'state_get')).state;
|
||||
const batchArgs = [0, 1, 2].map((i) => ({
|
||||
...position,
|
||||
name: 'batch account ' + i,
|
||||
amount: '8.86420975',
|
||||
expectedState: batchState,
|
||||
idempotencyKey: randomUUID(),
|
||||
}));
|
||||
const batch = await Promise.all(batchArgs.map((args) => call(d, 'position_create', args)));
|
||||
const edit = await call(d, 'balance_record', {
|
||||
id: applied.result.id,
|
||||
data: { amount: '2', date: day },
|
||||
expectedState: batchState,
|
||||
idempotencyKey: randomUUID(),
|
||||
});
|
||||
const otherToken = await web(d.cookie, '/agent/tokens', 'POST', {
|
||||
name: 'separate draft connection',
|
||||
days: 1,
|
||||
scopes: ['read', 'draft'],
|
||||
password: d.password,
|
||||
});
|
||||
assert.equal(otherToken.status, 201);
|
||||
const otherClient = new Client({ name: 'other draft connection', version: '1.31.0' });
|
||||
clients.push(otherClient);
|
||||
await otherClient.connect(
|
||||
new StreamableHTTPClientTransport(new URL(resource), {
|
||||
requestInit: { headers: { Authorization: 'Bearer ' + otherToken.data.token } },
|
||||
}),
|
||||
);
|
||||
const otherDraft = await call({ client: otherClient }, 'position_create', {
|
||||
...position,
|
||||
name: 'other grant create',
|
||||
expectedState: batchState,
|
||||
idempotencyKey: randomUUID(),
|
||||
});
|
||||
const firstBatch = await confirm(d, batch[0]);
|
||||
assert.equal(
|
||||
(await call(d, 'position_create', batchArgs[0])).operationId,
|
||||
firstBatch.operationId,
|
||||
);
|
||||
await Promise.all(batch.slice(1).map((operation) => confirm(d, operation)));
|
||||
assert.equal((await call(d, 'positions_list', { q: 'batch account' })).total, 3);
|
||||
assert.equal(
|
||||
(await call(d, 'position_get', { id: firstBatch.result.id })).amount,
|
||||
'8.86420975',
|
||||
);
|
||||
for (const operation of [edit, otherDraft]) {
|
||||
assert.equal(
|
||||
(
|
||||
await web(d.cookie, '/agent/operations/' + operation.operationId, 'POST', {
|
||||
approve: true,
|
||||
})
|
||||
).status,
|
||||
409,
|
||||
);
|
||||
}
|
||||
const webStale = await write(d, 'position_create', { ...position, name: 'web stale' });
|
||||
assert.equal(
|
||||
(
|
||||
await web(d.cookie, '/positions', 'POST', {
|
||||
...position,
|
||||
name: 'manual web account',
|
||||
})
|
||||
).status,
|
||||
201,
|
||||
);
|
||||
assert.equal(
|
||||
(await web(d.cookie, '/agent/operations/' + webStale.operationId, 'POST', { approve: true }))
|
||||
.status,
|
||||
409,
|
||||
);
|
||||
|
||||
const expired = await write(d, 'position_create', { ...position, name: 'expired' });
|
||||
await db.agentOperation.update({
|
||||
where: { id: expired.operationId },
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# MCP 多账号草稿确认修复(2026-10-04)
|
||||
|
||||
## 问题与原因
|
||||
|
||||
通过 MCP 在同一个 state_get 状态下创建多个 position_create 草稿时,草稿都保存同一账目快照。确认第一个新增后账目变化,剩余新增草稿会报“账目已变化,请取消并重新创建操作”。
|
||||
|
||||
## 修复范围
|
||||
|
||||
确认 position_create 成功后,在用户锁和同一数据库事务内,将同一用户、同一连接、相同旧快照下尚未过期的待确认 position_create 草稿快照推进到最新状态。每份草稿仍需用户逐项审阅确认,不会自动执行。
|
||||
|
||||
仅独立新增草稿适用此规则。余额记录、修改、转账等其他草稿,以及不同连接的新增草稿,不会随之推进。网页新增或修改、设置变化仍触发原有冲突检查。原始幂等键和请求摘要不变,重试已完成操作不会重复创建账户。不需要数据库迁移。
|
||||
|
||||
修复前已因部分确认而落后于当前账目的草稿不自动恢复。应重新查询当前账号,只重新生成尚未新增的账号草稿,避免重复创建已经成功的账号。
|
||||
|
||||
## 验证
|
||||
|
||||
- 修复前通过官方 SDK 和真实 MySQL 复现后续草稿确认返回 409。
|
||||
- 修复后三份同快照新增草稿全部确认成功,后两份并发确认也通过。
|
||||
- 幂等重试返回原操作;金额十进制字符串保持精确。
|
||||
- 不同连接的新增草稿、同快照余额草稿仍返回 409;网页新增也使旧草稿失效。
|
||||
- MCP/OAuth 集成测试 6 项通过,单元测试 52 项通过,后端编译通过。
|
||||
- 测试仅使用隔离用户,结束后清理。未确认或修改实际用户的草稿、账号或余额。
|
||||
@@ -92,4 +92,5 @@
|
||||
- ~~快速记账的转账按钮 和快速记账显示在附近,不在显示在右边~~ — 已完成并验证:2026-10-04 16:00(UTC+8),见 docs/update-quick-entry-2026-10-04.md
|
||||
- ~~快速记账时,可以出现点击当天已完成记账的按钮,点击可以快速设置某个账号当天已完成记账,同时在快速记账中可以显示或隐藏当天已完成记账~~ — 已完成并验证:2026-10-04 16:00(UTC+8),见 docs/update-quick-entry-2026-10-04.md
|
||||
- ~~还款可以优惠功能(负数表示手续费)~~ — 已完成并验证:2026-10-04 22:18(UTC+8),见 docs/update-repayment-trend-2026-10-04.md
|
||||
- ~~净资产轨迹,可以设置隐藏某条折线,鼠标放线线可以查看当天数据~~ — 已完成并验证:2026-10-04 22:18(UTC+8),见 docs/update-repayment-trend-2026-10-04.md
|
||||
- ~~净资产轨迹,可以设置隐藏某条折线,鼠标放线线可以查看当天数据~~ — 已完成并验证:2026-10-04 22:18(UTC+8),见 docs/update-repayment-trend-2026-10-04.md
|
||||
- ~~修复 MCP 同批新增多个账号时后续草稿提示账目已变化~~ — 已完成并验证:2026-10-04 22:53(UTC+8),见 docs/fix-mcp-create-drafts-2026-10-04.md
|
||||
Reference in new issue
Block a user