Skip to content

[Fix] Recover conversation migration state safely#817

Merged
dingyi222666 merged 2 commits intov1-devfrom
fix/conversation-system-1
Apr 6, 2026
Merged

[Fix] Recover conversation migration state safely#817
dingyi222666 merged 2 commits intov1-devfrom
fix/conversation-system-1

Conversation

@dingyi222666
Copy link
Copy Markdown
Member

This pr hardens the built-in conversation migration flow so startup recovery can safely handle an existing legacy sentinel, avoid re-reading already-purged legacy tables, and preserve current ChatLuna data.

New Features

  • None.

Bug fixes

  • Skip admin-only conversation management checks for personal routes.
  • Persist migration completion metadata before purging legacy tables so recovery can resume from a finished state.
  • Treat an existing legacy sentinel as startup recovery, including fresh-install and already-migrated data cases.
  • Continue legacy migration when a stale sentinel exists but legacy ChatHub rows are still present.
  • Recognize cannot resolve table as a missing-table error during legacy cleanup.
  • Add migration coverage for sentinel recovery and missing-table cleanup behavior.

Other Changes

  • Simplify conversation creation by removing the redundant manage-permission assertion from the service path.

Skip admin-only conversation checks for personal routes and persist migration completion metadata before legacy tables are purged. This lets startup recovery handle an existing legacy sentinel without re-reading missing tables or discarding existing ChatLuna data.

Tests: not run (not requested)
Probe legacy ChatHub tables before treating the sentinel as authoritative so restored databases still run the built-in migration instead of being marked complete. Keep sentinel-based recovery for genuinely empty legacy state while preserving the existing self-heal path.

Tests: npx cross-env TS_NODE_PROJECT=packages/core/tests/tsconfig.json mocha -r tsconfig-paths/register -r esbuild-register -r yml-register --exit 'packages/core/tests/conversation-migration.spec.ts'
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

该PR修改了会话管理、数据迁移验证和权限检查逻辑。增强了conversation管理中间件的条件判断,改进了wipe操作中的元数据写入,优化了room-to-conversation迁移的流程控制,并添加了相应的测试覆盖。

Changes

Cohort / File(s) Summary
会话管理中间件
packages/core/src/middlewares/system/conversation_manage.ts
conversation_new中间件中添加了额外的条件检查:现在要求routeModepersonal时才能绕过admin权限校验。
数据清理操作
packages/core/src/middlewares/system/wipe.ts
扩展了wipe操作以删除chatluna_meta表中的所有行,并在清理后写入多个元数据值(schema_version、validation_result、migration_done标志和migration_finished_at时间戳)。
迁移表定义与验证
packages/core/src/migration/legacy_tables.ts
更新defineLegacyMigrationTables添加可选的force参数以控制sentinel行为;扩展isMissingTableError的错误模式识别;将isMissingTableError从内部函数导出为公开函数。
Room到Conversation迁移
packages/core/src/migration/room_to_conversation.ts
添加文件系统sentinel检测以提前确定迁移模式;扩展ensureMigrationValidated以支持sentinel驱动的分支;分离writeMigrationDonewriteMigrationFinished的职责;新增hasLegacyMigrationData函数检测遗留数据。
迁移验证工具
packages/core/src/migration/validators.ts
新增导出函数createPassedValidationResult(),返回验证通过的MigrationValidationResult对象,各项检查结果均为"matched"状态。
会话服务
packages/core/src/services/conversation.ts
从新会话创建路径中移除assertManageAllowed权限检查调用。
迁移测试
packages/core/tests/conversation-migration.spec.ts
添加四个新测试用例:三个针对ensureMigrationValidated的sentinel场景(不完整元数据、现有数据、过期sentinel),一个针对runRoomToConversationMigration的启动恢复行为,一个针对dropTableIfExists的缺失表错误处理。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 一只长耳的小兔子轻声呢喃

迁移之路已铺平,sentinel把守门,
元数据整整齐齐,验证结果皆相逢,
wipe后新生重启始,权限守卫更精准,
旧房换新居,一切井然有序☃️

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰准确地总结了主要变更:修复对话迁移状态恢复。
Description check ✅ Passed 描述与变更集相关联,详细说明了错误修复、新增功能和其他变更。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/conversation-system-1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dingyi222666 dingyi222666 merged commit 3e86731 into v1-dev Apr 6, 2026
3 of 5 checks passed
@dingyi222666 dingyi222666 deleted the fix/conversation-system-1 branch April 6, 2026 13:47
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 594cf247e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 339 to 341
if (!resolved.constraint.allowNew) {
throw new Error('Conversation creation is disabled by constraint.')
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reinstate admin check before implicit conversation creation

Removing the assertManageAllowed call in ensureActiveConversation() opens an authorization bypass in the normal chat path: request_conversation always calls this method, so when no active conversation exists a non-admin user can now create one even if manageMode is admin. This contradicts the admin-only management behavior still enforced in conversation_new for non-personal routes and lets regular users bootstrap shared/custom managed conversations just by sending a message.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refines the migration logic from legacy tables to the new conversation system, introducing a sentinel-based recovery mechanism to handle interrupted migrations or fresh installs. It also updates the wipe process to re-initialize metadata and adjusts permission checks in the conversation management middleware. The review feedback highlights a security concern regarding the removal of permission assertions in the conversation service which could lead to unauthorized conversation creation. Additionally, there are suggestions to remove a redundant function call in the migration logic and to optimize sequential database queries using Promise.all for better performance.

}
}

await assertManageAllowed(session, resolved.constraint)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Removing the assertManageAllowed check here introduces a permission bypass for automatic conversation creation. While the conversation_new middleware now handles this for explicit commands (with a personal route exception), the service layer's ensureActiveConversation is also called during the normal chat flow. If manageMode is set to admin for a non-personal route, regular users will now be able to trigger conversation creation simply by sending a message, which violates the intended administrative restriction.

        if (resolved.constraint.routeMode !== 'personal') {
            await assertManageAllowed(session, resolved.constraint)
        }

ctx.logger.warn(
'Legacy sentinel exists but legacy ChatHub data is still present; continuing migration from legacy tables.'
)
defineLegacyMigrationTables(ctx, true)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This call to defineLegacyMigrationTables is redundant. The hasLegacyMigrationData function, which is called just a few lines above at line 122, already ensures that the legacy tables are defined by calling defineLegacyMigrationTables(ctx, true) internally.

Comment on lines +151 to +178
const conversations = (await ctx.database.get(
'chatluna_conversation',
{},
{
limit: 1
}
)) as ConversationRecord[]
const messages = (await ctx.database.get(
'chatluna_message',
{},
{
limit: 1
}
)) as MessageRecord[]
const bindings = (await ctx.database.get(
'chatluna_binding',
{},
{
limit: 1
}
)) as BindingRecord[]
const acl = (await ctx.database.get(
'chatluna_acl',
{},
{
limit: 1
}
)) as ACLRecord[]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These four database queries are executed sequentially, which can be optimized by using Promise.all to fetch the existence of data in parallel. This improves startup performance during the recovery flow.

Suggested change
const conversations = (await ctx.database.get(
'chatluna_conversation',
{},
{
limit: 1
}
)) as ConversationRecord[]
const messages = (await ctx.database.get(
'chatluna_message',
{},
{
limit: 1
}
)) as MessageRecord[]
const bindings = (await ctx.database.get(
'chatluna_binding',
{},
{
limit: 1
}
)) as BindingRecord[]
const acl = (await ctx.database.get(
'chatluna_acl',
{},
{
limit: 1
}
)) as ACLRecord[]
const [conversations, messages, bindings, acl] = (await Promise.all([
ctx.database.get('chatluna_conversation', {}, { limit: 1 }),
ctx.database.get('chatluna_message', {}, { limit: 1 }),
ctx.database.get('chatluna_binding', {}, { limit: 1 }),
ctx.database.get('chatluna_acl', {}, { limit: 1 })
])) as [ConversationRecord[], MessageRecord[], BindingRecord[], ACLRecord[]]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant