@@ -18,6 +18,7 @@ import { pushTrack } from '../../../../common/middle/tracks/utils';
1818import { getS3SandboxSource } from '../../../../common/s3/sources/sandbox' ;
1919import {
2020 MongoLegacySandboxInstance ,
21+ LegacySandboxInstanceZodSchema ,
2122 type LegacySandboxInstanceSchemaType
2223} from '../infrastructure/instance/legacySchema' ;
2324import {
@@ -100,11 +101,40 @@ type ResolvedLegacyApp = {
100101 chatId : string ;
101102} ;
102103
103- const resolveLegacySourceId = ( doc : LegacySandboxInstanceSchemaType ) =>
104- doc . sourceId || doc . appId || doc . metadata ?. skillId || undefined ;
104+ /**
105+ * 在产生任何迁移副作用前校验整张 Legacy 表。
106+ */
107+ const parseLegacySandboxInstances = ( rawDocs : unknown [ ] ) : LegacySandboxInstanceSchemaType [ ] => {
108+ const parsedDocs : LegacySandboxInstanceSchemaType [ ] = [ ] ;
109+ const failures : string [ ] = [ ] ;
110+
111+ for ( const rawDoc of rawDocs ) {
112+ const parsed = LegacySandboxInstanceZodSchema . safeParse ( rawDoc ) ;
113+ if ( parsed . success ) {
114+ parsedDocs . push ( parsed . data ) ;
115+ continue ;
116+ }
105117
106- const isLegacySkillInstance = ( doc : LegacySandboxInstanceSchemaType ) =>
107- doc . sourceType === ChatSourceTypeEnum . skillEdit || doc . type === SandboxTypeEnum . editDebug ;
118+ const doc = rawDoc && typeof rawDoc === 'object' ? ( rawDoc as Record < string , unknown > ) : { } ;
119+ const issues = parsed . error . issues
120+ . map ( ( issue ) => `${ issue . path . join ( '.' ) || '<root>' } : ${ issue . message } ` )
121+ . join ( '; ' ) ;
122+ failures . push (
123+ `_id=${ String ( doc . _id ?? 'unknown' ) } , sandboxId=${ String ( doc . sandboxId ?? 'unknown' ) } [${ issues } ]`
124+ ) ;
125+ }
126+
127+ if ( failures . length > 0 ) {
128+ throw new Error (
129+ [
130+ `Legacy Sandbox preflight validation failed for ${ failures . length } record(s)` ,
131+ ...failures
132+ ] . join ( '\n' )
133+ ) ;
134+ }
135+
136+ return parsedDocs ;
137+ } ;
108138
109139/**
110140 * 检查同一 App + User 是否仍有未迁移的 Legacy 记录。
@@ -299,35 +329,25 @@ const runLegacySandboxMigration = async (
299329) : Promise < UserSandboxMigrationResult > => {
300330 const dryRun = params . dryRun ?? true ;
301331 const migrationStartedAt = Date . now ( ) ;
302- const legacyDocs = await MongoLegacySandboxInstance . find ( { } )
332+ const rawLegacyDocs = await MongoLegacySandboxInstance . collection
333+ . find ( { } )
303334 . sort ( { lastActiveAt : 1 , _id : 1 } )
304- . lean < LegacySandboxInstanceSchemaType [ ] > ( ) ;
335+ . toArray ( ) ;
336+ const legacyDocs = parseLegacySandboxInstances ( rawLegacyDocs ) ;
305337 const skillItems : ResolvedLegacySkill [ ] = [ ] ;
306338 const appItems : ResolvedLegacyApp [ ] = [ ] ;
307- const failures : UserSandboxMigrationFailure [ ] = [ ] ;
308339
309340 for ( const doc of legacyDocs ) {
310- const sourceId = resolveLegacySourceId ( doc ) ;
311- if ( isLegacySkillInstance ( doc ) ) {
312- if ( sourceId ) skillItems . push ( { doc, sourceId } ) ;
313- else failures . push ( { sandboxId : doc . sandboxId , error : 'Legacy Skill sourceId is missing' } ) ;
341+ if ( doc . sourceType === ChatSourceTypeEnum . skillEdit ) {
342+ skillItems . push ( { doc, sourceId : doc . sourceId } ) ;
314343 continue ;
315344 }
316- if ( doc . sourceType && doc . sourceType !== ChatSourceTypeEnum . app ) {
317- failures . push ( {
318- sandboxId : doc . sandboxId ,
319- error : `Unsupported Legacy Sandbox sourceType: ${ doc . sourceType } `
320- } ) ;
321- continue ;
322- }
323- if ( sourceId && doc . userId && doc . chatId ) {
324- appItems . push ( { doc, sourceId, userId : doc . userId , chatId : doc . chatId } ) ;
325- } else {
326- failures . push ( {
327- sandboxId : doc . sandboxId ,
328- error : 'Legacy App sourceId, userId or chatId is missing'
329- } ) ;
330- }
345+ appItems . push ( {
346+ doc,
347+ sourceId : doc . sourceId ,
348+ userId : doc . userId ,
349+ chatId : doc . chatId
350+ } ) ;
331351 }
332352
333353 const appGroups = new Map < string , ResolvedLegacyApp [ ] > ( ) ;
@@ -344,8 +364,8 @@ const runLegacySandboxMigration = async (
344364 migratedAppCount : 0 ,
345365 appGroupCount : appGroups . size ,
346366 completedAppGroupCount : 0 ,
347- failedCount : failures . length ,
348- failures
367+ failedCount : 0 ,
368+ failures : [ ]
349369 } ;
350370 const trackCompleted = ( ) =>
351371 recordMigrationTrack ( {
@@ -358,18 +378,6 @@ const runLegacySandboxMigration = async (
358378 } ) ;
359379
360380 await recordMigrationTrack ( { runId, phase : 'started' , dryRun } ) ;
361- await Promise . all (
362- failures . map ( ( failure ) =>
363- recordMigrationTrack ( {
364- runId,
365- phase : 'failure' ,
366- dryRun,
367- sandboxId : failure . sandboxId ,
368- step : 'validate_legacy_record' ,
369- error : failure . error
370- } )
371- )
372- ) ;
373381 if ( dryRun ) {
374382 await trackCompleted ( ) ;
375383 return result ;
0 commit comments