Skip to content

Commit ed2ecc3

Browse files
committed
fix(migration): Clarify reports and dev startup
Successful migrations showed warnings for expected exclusions, while View Report only refreshed an already displayed summary. Development startup also required a manually built Migrator and an environment flag. - Open report details with per-domain results and grouped explanations. - Treat excluded session paths and orphaned parent relationships as informational, and count warnings by their actual severity. - Exclude normal built-in MiniApp storage imports from conflict counts while preserving existing target-conflict handling. - Simplify migration instructions in English and both Chinese locales. - Automatically retry denied Windows Job breakaway in Debug builds without an environment flag; retain Release launch behavior. - Incrementally build Data Migrator before Desktop dev and preview startup, and stop with an error if preparation fails. Validation: focused migration tests, Web and locale checks, Migrator script checks, and dev script syntax checks passed. Application startup and UI interaction were not exercised.
1 parent a22e57b commit ed2ecc3

10 files changed

Lines changed: 106 additions & 50 deletions

File tree

‎scripts/dev.cjs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ function runCommandPrefixed(prefix, cmd, args, cwd = ROOT_DIR, envOverrides = {}
153153
const child = spawn(cmd, args, {
154154
cwd,
155155
shell: process.platform === 'win32',
156+
windowsHide: true,
156157
stdio: ['ignore', 'pipe', 'pipe'],
157158
env: {
158159
...process.env,
@@ -672,6 +673,22 @@ async function main() {
672673
process.exit(1);
673674
}
674675

676+
// Build after version generation and before Desktop starts. Cargo checks
677+
// freshness so an existing but stale Migrator is rebuilt as well.
678+
if (desktopMode) {
679+
printInfo('Preparing Data Migrator (incremental Debug build)');
680+
const migratorBuild = await runCommandPrefixed(
681+
'data-migrator',
682+
'cargo',
683+
['build', '-p', 'openbitfun-data-migrator', '--bin', 'openbitfun-data-migrator'],
684+
);
685+
if (!migratorBuild.ok) {
686+
printError('Data Migrator build failed; Desktop was not started');
687+
if (migratorBuild.error?.message) printError(migratorBuild.error.message);
688+
process.exit(1);
689+
}
690+
}
691+
675692
if (desktopMode) {
676693
const baselineHelperUrl = pathToFileURL(
677694
path.join(__dirname, 'frontend-workbench-dev-baseline.mjs')

‎src/apps/data-migrator/ui/app.js‎

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const translations = {
1313
stepConfirm: 'Step 4', planTitle: 'Confirm migration', retryWriters: 'Check processes again',
1414
start: 'Start migration', stepProgress: 'Step 5', progressTitle: 'Migration progress',
1515
phase: 'Phase', domain: 'Domain', count: 'Completed steps',
16-
cancel: 'Cancel at a safe boundary', stepDone: 'Result', reportTitle: 'Migration report',
16+
cancel: 'Cancel', stepDone: 'Result', reportTitle: 'Migration report',
1717
reportPrivacy: 'This summary contains counts and result codes, not credentials or user content.',
1818
exportDiagnostics: 'Export failure diagnostics',
1919
diagnosticsExported: 'Sanitized diagnostics saved to {path}',
@@ -26,7 +26,7 @@ const translations = {
2626
recovery: 'A previous migration journal was found and can be resumed.',
2727
blockers: '{count} data-writing process(es) must stop before migration can continue.',
2828
noBlockers: 'No data-writing processes are blocking migration.',
29-
steps: '{count} ordered domain step(s)', conflicts: '{count} visible conflict(s)',
29+
steps: '{count} migration step(s)', conflicts: '{count} conflict(s)',
3030
imported: 'imported', staged: 'staged', skipped: 'skipped', warnings: 'warnings',
3131
},
3232
'zh-CN': {
@@ -39,7 +39,7 @@ const translations = {
3939
reviewTitle: '检查扫描结果', prepare: '运行迁移预检', stepConfirm: '第 4 步',
4040
planTitle: '确认迁移', retryWriters: '重新检查进程', start: '开始迁移',
4141
stepProgress: '第 5 步', progressTitle: '迁移进度', phase: '阶段', domain: '领域',
42-
count: '已完成步骤', cancel: '在安全边界取消', stepDone: '结果', reportTitle: '迁移报告',
42+
count: '已完成步骤', cancel: '取消', stepDone: '结果', reportTitle: '迁移报告',
4343
reportPrivacy: '此摘要仅包含计数和结果码,不包含凭据或用户正文。',
4444
exportDiagnostics: '导出失败诊断', diagnosticsExported: '去敏诊断已保存到 {path}',
4545
openDesktop: '打开 OpenBitFun',
@@ -51,7 +51,7 @@ const translations = {
5151
sourceFound: '已发现 BitFun {version}。迁移期间来源保持只读。',
5252
recovery: '发现上次迁移日志,可以从安全状态继续。',
5353
blockers: '迁移前还需停止 {count} 个数据写入进程。', noBlockers: '没有进程阻止迁移。',
54-
steps: '{count} 个有序领域步骤', conflicts: '{count} 个可见冲突',
54+
steps: '{count} 个迁移步骤', conflicts: '{count} 个冲突',
5555
imported: '已导入', staged: '已暂存', skipped: '已跳过', warnings: '警告',
5656
},
5757
'zh-TW': {
@@ -64,7 +64,7 @@ const translations = {
6464
reviewTitle: '檢查掃描結果', prepare: '執行遷移預檢', stepConfirm: '第 4 步',
6565
planTitle: '確認遷移', retryWriters: '重新檢查程序', start: '開始遷移',
6666
stepProgress: '第 5 步', progressTitle: '遷移進度', phase: '階段', domain: '領域',
67-
count: '已完成步驟', cancel: '在安全邊界取消', stepDone: '結果', reportTitle: '遷移報告',
67+
count: '已完成步驟', cancel: '取消', stepDone: '結果', reportTitle: '遷移報告',
6868
reportPrivacy: '此摘要僅包含計數和結果碼,不包含憑據或使用者正文。',
6969
exportDiagnostics: '匯出失敗診斷', diagnosticsExported: '去敏診斷已儲存至 {path}',
7070
openDesktop: '開啟 OpenBitFun',
@@ -76,7 +76,7 @@ const translations = {
7676
sourceFound: '已發現 BitFun {version}。遷移期間來源保持唯讀。',
7777
recovery: '發現上次遷移日誌,可以從安全狀態繼續。',
7878
blockers: '遷移前還需停止 {count} 個資料寫入程序。', noBlockers: '沒有程序阻止遷移。',
79-
steps: '{count} 個有序領域步驟', conflicts: '{count} 個可見衝突',
79+
steps: '{count} 個遷移步驟', conflicts: '{count} 個衝突',
8080
imported: '已匯入', staged: '已暫存', skipped: '已略過', warnings: '警告',
8181
},
8282
};
@@ -97,24 +97,24 @@ const groups = [
9797
'zh-TW': ['設定與服務憑據', '匯入設定;無法解密的憑據會標記為需要重新登入。'],
9898
}],
9999
['agents_skills_and_miniapps', {
100-
en: ['Agents, Skills, and MiniApps', 'User content is imported; system Skills and built-in MiniApps are excluded.'],
101-
'zh-CN': ['Agents、Skills 与 MiniApps', '导入用户内容;排除系统 Skills 和内置 MiniApps。'],
102-
'zh-TW': ['Agents、Skills 與 MiniApps', '匯入使用者內容;排除系統 Skills 和內建 MiniApps。'],
100+
en: ['Agents, Skills, and MiniApps', 'Imports user extensions and saved data from built-in MiniApps. Built-in code is provided by OpenBitFun.'],
101+
'zh-CN': ['Agents、Skills 与 MiniApps', '导入用户扩展和内置 MiniApps 的使用数据;内置代码由新版提供。'],
102+
'zh-TW': ['Agents、Skills 與 MiniApps', '匯入使用者擴充與內建 MiniApps 的使用資料;內建程式碼由新版提供。'],
103103
}],
104104
['workspaces_sessions_and_tasks', {
105-
en: ['Workspaces, sessions, and tasks', 'Includes coordination.sqlite as a required atomic dependency.'],
106-
'zh-CN': ['工作区、会话与 Agent 任务状态', '强制包含 coordination.sqlite 作为原子依赖。'],
107-
'zh-TW': ['工作區、工作階段與 Agent 任務狀態', '強制包含 coordination.sqlite 作為原子相依項。'],
105+
en: ['Workspaces, sessions, and tasks', 'Imports workspaces, conversation history, and Agent task status.'],
106+
'zh-CN': ['工作区、会话与 Agent 任务状态', '导入工作区、会话历史与 Agent 任务状态。'],
107+
'zh-TW': ['工作區、工作階段與 Agent 任務狀態', '匯入工作區、會話歷史與 Agent 任務狀態。'],
108108
}],
109109
['memory', {
110-
en: ['Memory', 'Imports structured and file-backed memory without user-content telemetry.'],
111-
'zh-CN': ['记忆', '导入结构化与文件记忆,不发送用户正文遥测。'],
112-
'zh-TW': ['記憶', '匯入結構化與檔案記憶,不傳送使用者正文遙測。'],
110+
en: ['Memory', 'Imports memory databases and memory files.'],
111+
'zh-CN': ['记忆', '导入记忆数据库与记忆文件。'],
112+
'zh-TW': ['記憶', '匯入記憶資料庫與記憶檔案。'],
113113
}],
114114
['remote_connections_and_devices', {
115-
en: ['Remote connections and devices', 'Non-portable identities and secrets are marked for repair or sign-in.'],
116-
'zh-CN': ['远程连接与设备', '不可移植的身份和秘密会标记为需要修复或重新登录。'],
117-
'zh-TW': ['遠端連線與裝置', '不可移植的身分和秘密會標記為需要修復或重新登入。'],
115+
en: ['Remote connections and devices', 'Imports remote connections and device settings. Some connections may require signing in again.'],
116+
'zh-CN': ['远程连接与设备', '导入远程连接与设备设置;部分连接可能需要重新登录。'],
117+
'zh-TW': ['遠端連線與裝置', '匯入遠端連線與裝置設定;部分連線可能需要重新登入。'],
118118
}],
119119
];
120120

@@ -237,7 +237,7 @@ function render(view) {
237237
const reportSummary = document.getElementById('report-summary');
238238
if (view.report) {
239239
reportSummary.replaceChildren(...view.report.domainResults.map((result) =>
240-
row(result.domain, `${result.imported} ${transferLabel(result)}, ${result.skipped} ${text.skipped}, ${result.warnings.length} ${text.warnings}`)));
240+
row(result.domain, `${result.imported} ${transferLabel(result)}, ${result.skipped} ${text.skipped}, ${result.warnings.filter((item) => item.severity !== 'info').length} ${text.warnings}`)));
241241
}
242242
show('dev-restart-help', !view.restartDesktopOnFinish);
243243
document.getElementById('open-desktop').textContent = view.restartDesktopOnFinish

‎src/crates/assembly/core/src/legacy_migration/extensions.rs‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -916,9 +916,7 @@ fn conflict_for_action(
916916
"extension_duplicate_skipped",
917917
ConflictResolution::DuplicateSkipped,
918918
),
919-
ImportAction::BuiltinStorage => {
920-
("builtin_code_excluded", ConflictResolution::SourceImported)
921-
}
919+
ImportAction::BuiltinStorage => return None,
922920
ImportAction::TargetWins => (
923921
"builtin_storage_target_preserved",
924922
ConflictResolution::TargetWins,
@@ -963,7 +961,12 @@ fn result_from_manifest(
963961
let conflicts = manifest
964962
.entries
965963
.iter()
966-
.filter(|entry| entry.action != ImportAction::Import)
964+
.filter(|entry| {
965+
!matches!(
966+
entry.action,
967+
ImportAction::Import | ImportAction::BuiltinStorage
968+
)
969+
})
967970
.count() as u64;
968971
let warnings = manifest
969972
.skipped_paths

‎src/crates/assembly/core/src/legacy_migration/workspace_sessions.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,19 +308,19 @@ impl LegacyDomainAdapter for WorkspaceSessionsAdapter {
308308
.iter()
309309
.map(|path| MigrationDiagnostic {
310310
code: "session_path_not_migrated".to_string(),
311-
severity: FindingSeverity::Warning,
311+
severity: FindingSeverity::Info,
312312
domain: Some(self.domain()),
313313
relative_path: Some(path.clone()),
314314
message: "A non-owned or rebuildable Session path was left in the legacy source"
315315
.to_string(),
316-
action: Some("Review the migration report before removing legacy data".to_string()),
316+
action: None,
317317
})
318318
.collect::<Vec<_>>();
319319
let orphaned_relationships = orphaned_session_relationship_count(&manifest);
320320
if orphaned_relationships > 0 {
321321
warnings.push(MigrationDiagnostic {
322322
code: "session_parent_not_present".to_string(),
323-
severity: FindingSeverity::Warning,
323+
severity: FindingSeverity::Info,
324324
domain: Some(self.domain()),
325325
relative_path: None,
326326
message: format!(

‎src/crates/services/legacy-migration/src/engine.rs‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -621,10 +621,14 @@ impl MigrationEngine {
621621
diagnostic.severity,
622622
FindingSeverity::Warning | FindingSeverity::Blocking
623623
)
624-
}) || report
625-
.domain_results
626-
.iter()
627-
.any(|result| !result.warnings.is_empty());
624+
}) || report.domain_results.iter().any(|result| {
625+
result.warnings.iter().any(|diagnostic| {
626+
matches!(
627+
diagnostic.severity,
628+
FindingSeverity::Warning | FindingSeverity::Blocking
629+
)
630+
})
631+
});
628632
let final_status = if has_warnings {
629633
MigrationRunStatus::CompletedWithWarnings
630634
} else {

‎src/crates/services/legacy-migration/src/handoff.rs‎

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -817,23 +817,15 @@ pub fn launch_trusted_executable(
817817

818818
#[cfg(windows)]
819819
fn allow_inherited_job_dev_retry(error: &std::io::Error) -> bool {
820-
should_retry_without_job_breakaway(
821-
cfg!(debug_assertions),
822-
std::env::var_os("OPENBITFUN_ALLOW_UNSIGNED_MIGRATOR_DEV").as_deref()
823-
== Some(OsStr::new("1")),
824-
error.kind(),
825-
)
820+
should_retry_without_job_breakaway(cfg!(debug_assertions), error.kind())
826821
}
827822

828823
#[cfg(any(windows, test))]
829824
fn should_retry_without_job_breakaway(
830825
debug_assertions: bool,
831-
unsigned_migrator_dev_enabled: bool,
832826
error_kind: std::io::ErrorKind,
833827
) -> bool {
834-
debug_assertions
835-
&& unsigned_migrator_dev_enabled
836-
&& error_kind == std::io::ErrorKind::PermissionDenied
828+
debug_assertions && error_kind == std::io::ErrorKind::PermissionDenied
837829
}
838830

839831
fn validate_binary_name(name: &str) -> LegacyMigrationResult<()> {
@@ -990,26 +982,18 @@ mod tests {
990982
}
991983

992984
#[test]
993-
fn inherited_job_retry_requires_debug_opt_in_and_permission_denied() {
985+
fn inherited_job_retry_requires_debug_and_permission_denied() {
994986
use std::io::ErrorKind;
995987

996988
assert!(should_retry_without_job_breakaway(
997-
true,
998989
true,
999990
ErrorKind::PermissionDenied
1000991
));
1001992
assert!(!should_retry_without_job_breakaway(
1002993
false,
1003-
true,
1004994
ErrorKind::PermissionDenied
1005995
));
1006996
assert!(!should_retry_without_job_breakaway(
1007-
true,
1008-
false,
1009-
ErrorKind::PermissionDenied
1010-
));
1011-
assert!(!should_retry_without_job_breakaway(
1012-
true,
1013997
true,
1014998
ErrorKind::NotFound
1015999
));

‎src/web-ui/src/infrastructure/config/components/LegacyMigrationSettingsPage.tsx‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button, Checkbox, StatusPill, type StatusPillTone } from '@openbitfun/ui';
1+
import { Button, Checkbox, Dialog, DialogBody, DialogClose, DialogHeader, DialogHeading, DialogTitle, StatusPill, type StatusPillTone } from '@openbitfun/ui';
22
import { useCallback, useEffect, useMemo, useState } from 'react';
33
import { confirmDialog } from '@/infrastructure/confirm-dialog';
44
import { useI18n } from '@/infrastructure/i18n';
@@ -75,6 +75,7 @@ export default function LegacyMigrationSettingsPage() {
7575
const [status, setStatus] = useState<LegacyMigrationStatusView | null>(null);
7676
const [scan, setScan] = useState<LegacyMigrationScanView | null>(null);
7777
const [report, setReport] = useState<MigrationRunReport | null>(null);
78+
const [reportOpen, setReportOpen] = useState(false);
7879
const [selectedGroups, setSelectedGroups] = useState<Set<MigrationGroupId>>(
7980
() => new Set(MIGRATION_GROUPS),
8081
);
@@ -176,6 +177,7 @@ export default function LegacyMigrationSettingsPage() {
176177
try {
177178
const next = await legacyMigrationAPI.getReport();
178179
setReport(next);
180+
setReportOpen(next !== null);
179181
if (!next) notification.info(t('messages.noReport'));
180182
} catch (error) {
181183
log.error('Failed to load legacy migration report', error);
@@ -405,6 +407,37 @@ export default function LegacyMigrationSettingsPage() {
405407
)}
406408
</ConfigPageSection>
407409
</ConfigPageContent>
410+
<Dialog open={reportOpen} onOpenChange={setReportOpen} size="lg">
411+
<DialogHeader>
412+
<DialogHeading><DialogTitle>{t('actions.viewReport')}</DialogTitle></DialogHeading>
413+
<DialogClose />
414+
</DialogHeader>
415+
<DialogBody>
416+
{report ? <>
417+
<p>{t(`statuses.${report.status}`)}</p>
418+
{report.domainResults.map((result) => (
419+
<section key={result.domain}>
420+
<h3>{t(`domains.${result.domain}`)} — {t(`domainStates.${result.state}`)}</h3>
421+
<p>{t('report.domainCounts', {
422+
imported: formatNumber(result.imported), skipped: formatNumber(result.skipped),
423+
conflicts: formatNumber(result.conflicts),
424+
})}</p>
425+
<ul>{Array.from(new Map(result.warnings.map((item) => [item.code, item])).values()).map((item) => (
426+
<li key={item.code}>
427+
{item.code === 'session_path_not_migrated' ? t('report.excludedPaths')
428+
: item.code === 'session_parent_not_present' ? t('report.orphanedSessions') : item.message}
429+
{' '}{t('report.occurrences', { count: result.warnings.filter((entry) => entry.code === item.code).length })}
430+
{item.action ? <p>{item.action}</p> : null}
431+
</li>
432+
))}</ul>
433+
</section>
434+
))}
435+
{report.diagnostics.map((item, index) => <p key={`${item.code}:${index}`}>{item.message}</p>)}
436+
{(report.requiresReauthentication ?? []).length > 0 ? <p>{t('report.reauthentication')}</p> : null}
437+
{(report.requiresRelocation ?? []).length > 0 ? <p>{t('report.relocation')}</p> : null}
438+
</> : null}
439+
</DialogBody>
440+
</Dialog>
408441
</ConfigPageLayout>
409442
);
410443
}

‎src/web-ui/src/locales/en-US/settings/legacy-migration.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
"blocked": "Needs attention"
6161
},
6262
"report": {
63+
"excludedPaths": "Non-core session files were left in the legacy source as intended. No action is required.",
64+
"orphanedSessions": "Some parent sessions are absent from the legacy source. Relationships and session history were preserved; no action is required.",
65+
"occurrences": "{{count}} records",
66+
"reauthentication": "Some connections require authentication again. Review connection settings.",
67+
"relocation": "Some paths require relocation. Review workspace settings.",
6368
"result": "Run result",
6469
"none": "No migration report is available yet.",
6570
"timeUnavailable": "Completion time unavailable",

‎src/web-ui/src/locales/zh-CN/settings/legacy-migration.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
"blocked": "需要处理"
4949
},
5050
"report": {
51+
"excludedPaths": "按迁移范围保留在旧数据中的非核心会话文件,无需处理。",
52+
"orphanedSessions": "部分会话的父会话在旧数据中不存在;已保留关系及会话历史,无需处理。",
53+
"occurrences": "共 {{count}} 条记录",
54+
"reauthentication": "部分连接需要重新认证,请到连接设置中处理。",
55+
"relocation": "部分路径需要重新定位,请检查工作区设置。",
5156
"result": "运行结果",
5257
"none": "尚无迁移报告。",
5358
"timeUnavailable": "完成时间不可用",

‎src/web-ui/src/locales/zh-TW/settings/legacy-migration.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
"blocked": "需要處理"
4949
},
5050
"report": {
51+
"excludedPaths": "依遷移範圍保留在舊資料中的非核心會話檔案,無須處理。",
52+
"orphanedSessions": "部分會話的父會話在舊資料中不存在;已保留關係及會話歷史,無須處理。",
53+
"occurrences": "共 {{count}} 筆記錄",
54+
"reauthentication": "部分連線需要重新認證,請至連線設定處理。",
55+
"relocation": "部分路徑需要重新定位,請檢查工作區設定。",
5156
"result": "執行結果",
5257
"none": "尚無遷移報告。",
5358
"timeUnavailable": "完成時間不可用",

0 commit comments

Comments
 (0)