@iwonz/ywtc 3.2.1 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/migrator.js CHANGED
@@ -9,7 +9,7 @@ import { assertTransferCapabilities } from './domain.js';
9
9
  import { FilesystemExporter } from './filesystem-export.js';
10
10
  import { discoverPageGraph } from './graph.js';
11
11
  import { profileBaseUrl, sameRemoteInstance } from './locators.js';
12
- import { TransferReport } from './report.js';
12
+ import { TransferReport, } from './report.js';
13
13
  import { DestinationNameAllocator, preferredDestinationName, verifiedCacheFile } from './resource-reconciliation.js';
14
14
  import { resolveStateLocation, StateStore, } from './state.js';
15
15
  function sourceIdentity(locator, root) {
@@ -119,8 +119,24 @@ function destinationFromState(state, adapter) {
119
119
  }
120
120
  function reportPage(input) {
121
121
  const transferable = input.resources.filter((resource) => resource.kind !== 'grid');
122
- const sizes = transferable.map((resource) => input.resourceSizes?.[resource.id]?.size ?? resource.size);
123
- const knownBytes = sizes.reduce((total, size) => total + (size ?? 0), 0);
122
+ const resourceItems = transferable.map((resource) => {
123
+ const state = input.resourceStates?.[resource.id];
124
+ const sourceBytes = state?.size ?? resource.size;
125
+ const status = input.resourceStatusOverride ?? state?.status ?? 'planned';
126
+ return {
127
+ sourceId: resource.id,
128
+ sourceName: resource.name,
129
+ kind: resource.kind,
130
+ status,
131
+ ...(sourceBytes !== undefined ? { sourceBytes } : {}),
132
+ ...(status === 'complete' && sourceBytes !== undefined ? { transferredBytes: sourceBytes } : {}),
133
+ ...(state?.destinationId ? { destinationId: state.destinationId } : {}),
134
+ ...(state?.destinationName ? { destinationName: state.destinationName } : {}),
135
+ ...(state?.lastError ? { error: state.lastError } : {}),
136
+ };
137
+ });
138
+ const knownBytes = resourceItems.reduce((total, resource) => total + (resource.sourceBytes ?? 0), 0);
139
+ const transferredBytes = resourceItems.reduce((total, resource) => total + (resource.transferredBytes ?? 0), 0);
124
140
  return {
125
141
  sourceCanonicalId: input.page.canonicalId,
126
142
  sourceId: input.page.id,
@@ -134,12 +150,16 @@ function reportPage(input) {
134
150
  ...(input.page.projectedParentId ? { parentId: input.page.projectedParentId } : {}),
135
151
  depth: input.page.depth,
136
152
  status: input.status,
153
+ inventoryStatus: input.inventoryStatus ?? 'complete',
137
154
  resources: transferable.length,
138
- ...(transferable.length === 0 || sizes.some((size) => size !== undefined) ? { bytes: knownBytes } : {}),
139
- bytesUnknown: sizes.filter((size) => size === undefined).length,
155
+ resourceItems,
156
+ ...(transferable.length === 0 || resourceItems.some((resource) => resource.sourceBytes !== undefined) ? { sourceBytes: knownBytes } : {}),
157
+ transferredBytes,
158
+ bytesUnknown: resourceItems.filter((resource) => resource.sourceBytes === undefined).length,
140
159
  links: input.document.metrics.links,
141
160
  images: input.document.metrics.images,
142
161
  tables: input.document.metrics.tables,
162
+ grids: input.resources.filter((resource) => resource.kind === 'grid').length,
143
163
  codeBlocks: input.document.metrics.codeBlocks,
144
164
  degraded: input.document.degraded || (input.resourceFallbacks?.length ?? 0) > 0,
145
165
  resourceFallbacks: input.resourceFallbacks ?? [],
@@ -148,10 +168,13 @@ function reportPage(input) {
148
168
  ...(input.error ? { error: input.error } : {}),
149
169
  };
150
170
  }
151
- function runStatus(errors, warnings) {
171
+ function runStatus(errors, warnings, degradations) {
152
172
  if (errors > 0)
153
173
  return 'partial';
154
- return warnings > 0 ? 'completed-with-warnings' : 'completed';
174
+ return warnings + degradations > 0 ? 'completed-with-warnings' : 'completed';
175
+ }
176
+ function attentionCount(summary) {
177
+ return summary.warnings + summary.degradations;
155
178
  }
156
179
  export class MigrationRunError extends Error {
157
180
  reportPaths;
@@ -233,6 +256,7 @@ export class Migrator {
233
256
  ...(store.loadInfo.backupFilename ? { backupPath: store.loadInfo.backupFilename } : {}),
234
257
  },
235
258
  });
259
+ report.registerPages([{ ...root, depth: 0 }]);
236
260
  let graph;
237
261
  let sourceFingerprints;
238
262
  try {
@@ -244,6 +268,10 @@ export class Migrator {
244
268
  ...(options.signal ? { signal: options.signal } : {}),
245
269
  onProgress: (progress) => status(`Discovery: ${progress.pages} pages, ${progress.edges} links${progress.active ? ` · ${progress.active}` : ''}`),
246
270
  });
271
+ report.registerPages(graph.pages);
272
+ report.setEdges(graph.edges);
273
+ for (const warning of graph.warnings)
274
+ report.warning(warning);
247
275
  if (container
248
276
  && options.destination.type === 'remote'
249
277
  && sameRemoteInstance(options.source.profile, options.destination.profile)
@@ -253,20 +281,25 @@ export class Migrator {
253
281
  status(`Caching source content for ${graph.pages.length} pages`);
254
282
  sourceFingerprints = await cacheDiscoveredPages(options.cacheDirectory, graph);
255
283
  await store.setGraph(graph.rootId, graph.edges);
256
- report.setEdges(graph.edges);
257
- for (const warning of graph.warnings)
258
- report.warning(warning);
259
284
  }
260
285
  catch (error) {
261
286
  const finalStatus = options.signal?.aborted ? 'cancelled' : 'failed';
262
287
  const message = error instanceof Error ? error.message : String(error);
263
- report.error(message);
288
+ report.updatePage(root.canonicalId, { status: 'failed', inventoryStatus: 'failed', error: message });
289
+ report.issue({
290
+ code: 'source-discovery-failed',
291
+ severity: 'error',
292
+ phase: 'discovery',
293
+ pageId: root.canonicalId,
294
+ retryable: true,
295
+ message,
296
+ });
264
297
  report.finish(finalStatus);
265
298
  const reportPaths = await report.write(options.reportsDirectory);
266
299
  if (!options.dryRun)
267
300
  await store.finishRun(run.id, finalStatus, {
268
301
  pages: 0,
269
- warnings: report.data.summary.warnings,
302
+ warnings: attentionCount(report.data.summary),
270
303
  errors: report.data.summary.errors,
271
304
  });
272
305
  throw new MigrationRunError(message, reportPaths, stateLocation.filename, { cause: error });
@@ -293,12 +326,26 @@ export class Migrator {
293
326
  inventory.images += document.metrics.images;
294
327
  inventory.tables += document.metrics.tables;
295
328
  inventory.codeBlocks += document.metrics.codeBlocks;
329
+ report.addPage(reportPage({
330
+ page,
331
+ status: 'planned',
332
+ resources,
333
+ document,
334
+ }), undefined, false);
296
335
  status(`Inspected ${page.title} · ${resources.length} resources`);
297
336
  }
298
337
  catch (error) {
299
338
  const message = `${page.title}: ${error instanceof Error ? error.message : String(error)}`;
300
339
  inventoryErrors.push(message);
301
- report.error(message);
340
+ report.updatePage(page.canonicalId, { status: 'failed', inventoryStatus: 'failed', error: message });
341
+ report.issue({
342
+ code: 'source-inventory-failed',
343
+ severity: 'error',
344
+ phase: 'inventory',
345
+ pageId: page.canonicalId,
346
+ retryable: true,
347
+ message,
348
+ });
302
349
  }
303
350
  })));
304
351
  if (inventoryErrors.length > 0) {
@@ -308,7 +355,7 @@ export class Migrator {
308
355
  if (!options.dryRun)
309
356
  await store.finishRun(run.id, finalStatus, {
310
357
  pages: graph.pages.length,
311
- warnings: report.data.summary.warnings,
358
+ warnings: attentionCount(report.data.summary),
312
359
  errors: report.data.summary.errors,
313
360
  });
314
361
  throw new MigrationRunError('Resource preflight failed before destination mutation', reportPaths, stateLocation.filename);
@@ -345,16 +392,16 @@ export class Migrator {
345
392
  report.output('destination', container.url);
346
393
  }
347
394
  report.finish('completed');
348
- const finalStatus = runStatus(report.data.summary.errors, report.data.summary.warnings);
395
+ const finalStatus = runStatus(report.data.summary.errors, report.data.summary.warnings, report.data.summary.degradations);
349
396
  report.finish(finalStatus);
350
397
  const reportPaths = await report.write(options.reportsDirectory);
351
398
  if (!options.dryRun)
352
399
  await store.finishRun(run.id, finalStatus, {
353
400
  pages: graph.pages.length,
354
- warnings: report.data.summary.warnings,
401
+ warnings: attentionCount(report.data.summary),
355
402
  errors: report.data.summary.errors,
356
403
  });
357
- status(`${finalStatus}: ${graph.pages.length} pages · ${report.data.summary.errors} errors · ${report.data.summary.warnings} warnings`);
404
+ status(`${finalStatus}: source ${report.data.summary.pages} pages · ${report.data.summary.complete} complete · ${report.data.summary.partial} partial · ${report.data.summary.failed + report.data.summary.blocked} failed/blocked · files ${report.data.summary.resourcesComplete}/${report.data.summary.resources} complete · ${report.data.summary.errors} errors · ${report.data.summary.warnings} warnings · ${report.data.summary.degradations} degradations`);
358
405
  return {
359
406
  status: finalStatus,
360
407
  hasErrors: report.data.summary.errors > 0,
@@ -377,7 +424,7 @@ export class Migrator {
377
424
  if (!options.dryRun)
378
425
  await store.finishRun(run.id, finalStatus, {
379
426
  pages: graph.pages.length,
380
- warnings: report.data.summary.warnings,
427
+ warnings: attentionCount(report.data.summary),
381
428
  errors: report.data.summary.errors,
382
429
  });
383
430
  this.logger.error(`${message}. Report: ${reportPaths.html}`);
@@ -422,10 +469,11 @@ export class Migrator {
422
469
  destinationName: resource.name,
423
470
  ...(resource.sha256 ? { sha256: resource.sha256 } : {}),
424
471
  ...(resource.size !== undefined ? { size: resource.size } : {}),
425
- status: 'complete',
472
+ status: options.dryRun ? 'planned' : 'complete',
426
473
  updatedAt: new Date().toISOString(),
427
474
  }];
428
475
  }));
476
+ const pageStatus = options.dryRun ? 'planned' : manifestPage?.degraded ? 'partial' : 'complete';
429
477
  await store.setPage({
430
478
  sourceId: page.id,
431
479
  sourceCanonicalId: page.canonicalId,
@@ -438,17 +486,18 @@ export class Migrator {
438
486
  destinationTitle: page.title,
439
487
  sourceFingerprint: sourceFingerprints.get(page.canonicalId),
440
488
  ...(manifestPage || previous?.destinationFingerprint ? { destinationFingerprint: manifestPage ? sha256(JSON.stringify(manifestPage)) : previous.destinationFingerprint } : {}),
441
- status: options.dryRun ? 'planned' : manifestPage?.degraded ? 'partial' : 'complete',
489
+ status: pageStatus,
442
490
  resources,
443
491
  degradations: manifestPage?.warnings ?? [],
444
492
  updatedAt: new Date().toISOString(),
445
493
  });
446
494
  report.addPage(reportPage({
447
495
  page,
448
- status: options.dryRun ? 'planned' : result.errors.length ? 'partial' : 'complete',
496
+ status: pageStatus,
449
497
  resources: sourceResources,
450
498
  document,
451
- resourceSizes: resources,
499
+ resourceStates: resources,
500
+ ...(options.dryRun ? { resourceStatusOverride: 'planned' } : {}),
452
501
  ...(manifestPage?.warnings ? { warnings: manifestPage.warnings } : {}),
453
502
  }), { source: normalizedPreviewHtml(document), destination: normalizedPreviewHtml(document) });
454
503
  }
@@ -461,6 +510,7 @@ export class Migrator {
461
510
  async transferRemote(input) {
462
511
  const { source, destination, container, graph, options, store, report, sourceFingerprints, resourcesByPage } = input;
463
512
  const destinationPages = new Map();
513
+ const stagingFailures = new Map();
464
514
  const sourcePages = new Map(graph.pages.map((page) => [page.canonicalId, page]));
465
515
  const bootstrapBodies = new Map();
466
516
  const totalResources = [...resourcesByPage.values()].reduce((total, resources) => total + resources.filter((resource) => resource.kind !== 'grid').length, 0);
@@ -496,7 +546,16 @@ export class Migrator {
496
546
  const parent = page.projectedParentId ? destinationPages.get(page.projectedParentId) : container;
497
547
  if (!parent) {
498
548
  const message = `Destination parent is unavailable for ${page.title}`;
499
- report.error(message);
549
+ stagingFailures.set(page.canonicalId, { status: 'blocked', message });
550
+ report.updatePage(page.canonicalId, { status: 'blocked', error: message });
551
+ report.issue({
552
+ code: 'destination-parent-unavailable',
553
+ severity: 'error',
554
+ phase: 'page',
555
+ pageId: page.canonicalId,
556
+ retryable: true,
557
+ message,
558
+ });
500
559
  progress.staged += 1;
501
560
  updateProgress(message);
502
561
  continue;
@@ -530,21 +589,54 @@ export class Migrator {
530
589
  if (page.projectedParentId) {
531
590
  await verifyConfirmedDestinationPage(destination, parent, this.consistencyOptions);
532
591
  }
533
- const candidate = await (destination.findRecoverablePage?.(parent, title) ?? destination.findChild(parent, title));
592
+ const findRecoveryChild = (candidateTitle) => destination.findRecoverablePage
593
+ ? destination.findRecoverablePage(parent, candidateTitle)
594
+ : destination.findChild(parent, candidateTitle);
595
+ let candidate;
596
+ let recoveryLookupFailed = false;
597
+ try {
598
+ candidate = await findRecoveryChild(title);
599
+ }
600
+ catch (lookupError) {
601
+ recoveryLookupFailed = true;
602
+ report.issue({
603
+ code: 'destination-recovery-lookup-failed',
604
+ severity: 'warning',
605
+ phase: 'page',
606
+ pageId: page.canonicalId,
607
+ retryable: true,
608
+ message: `${page.title}: recovery lookup failed; bootstrap recreation will be attempted: ${lookupError instanceof Error ? lookupError.message : String(lookupError)}`,
609
+ });
610
+ }
534
611
  if (candidate && sourceDerivedTextMatches(bootstrap.body, candidate.content, destination.kind, cachedPage)) {
535
612
  created = candidate;
536
613
  }
537
614
  else {
538
615
  let recoveryTitle = title;
539
- const occupied = candidate ?? await destination.findChild(parent, recoveryTitle);
540
- if (occupied) {
616
+ if (candidate) {
541
617
  const base = collisionTitle(page);
542
618
  const hash = sha256(page.canonicalId).slice(0, 8);
543
619
  recoveryTitle = base;
544
620
  let sequence = 0;
545
- while (await destination.findChild(parent, recoveryTitle)) {
546
- sequence += 1;
547
- recoveryTitle = `${base} [${hash}${sequence > 1 ? `-${sequence}` : ''}]`;
621
+ while (!recoveryLookupFailed) {
622
+ try {
623
+ if (!await findRecoveryChild(recoveryTitle))
624
+ break;
625
+ sequence += 1;
626
+ recoveryTitle = `${base} [${hash}${sequence > 1 ? `-${sequence}` : ''}]`;
627
+ }
628
+ catch (lookupError) {
629
+ recoveryLookupFailed = true;
630
+ recoveryTitle = `${base} [${hash}]`;
631
+ report.issue({
632
+ code: 'destination-recovery-lookup-failed',
633
+ severity: 'warning',
634
+ phase: 'page',
635
+ pageId: page.canonicalId,
636
+ retryable: true,
637
+ message: `${page.title}: suffixed-title lookup failed; bootstrap recreation will be attempted: ${lookupError instanceof Error ? lookupError.message : String(lookupError)}`,
638
+ });
639
+ }
548
640
  }
549
641
  }
550
642
  created = await destination.createPage({ container: parent, title: recoveryTitle, body: bootstrap.body });
@@ -632,10 +724,20 @@ export class Migrator {
632
724
  }
633
725
  catch (error) {
634
726
  const message = `${page.title}: ${error instanceof Error ? error.message : String(error)}`;
635
- report.error(message);
727
+ const failureStatus = /conflict/i.test(message) ? 'conflict' : 'failed';
728
+ stagingFailures.set(page.canonicalId, { status: failureStatus, message });
729
+ report.updatePage(page.canonicalId, { status: failureStatus, error: message });
730
+ report.issue({
731
+ code: failureStatus === 'conflict' ? 'destination-conflict' : 'destination-page-staging-failed',
732
+ severity: 'error',
733
+ phase: 'page',
734
+ pageId: page.canonicalId,
735
+ retryable: failureStatus !== 'conflict',
736
+ message,
737
+ });
636
738
  const savedState = store.value.pages[page.canonicalId];
637
739
  if (savedState)
638
- await store.setPage({ ...savedState, status: /conflict/i.test(message) ? 'conflict' : 'failed', lastError: message, updatedAt: new Date().toISOString() });
740
+ await store.setPage({ ...savedState, status: failureStatus, lastError: message, updatedAt: new Date().toISOString() });
639
741
  }
640
742
  finally {
641
743
  progress.staged += 1;
@@ -651,13 +753,16 @@ export class Migrator {
651
753
  const cachedPage = await loadCachedPage(options.cacheDirectory, page.canonicalId);
652
754
  const resources = resourcesByPage.get(page.canonicalId) ?? [];
653
755
  if (!destinationPage) {
756
+ const currentFailure = stagingFailures.get(page.canonicalId);
654
757
  const failedState = store.value.pages[page.canonicalId];
655
758
  report.addPage(reportPage({
656
759
  page,
657
- status: failedState?.status ?? 'blocked',
760
+ status: currentFailure?.status ?? 'blocked',
658
761
  resources,
659
762
  document,
660
- error: failedState?.lastError ?? 'Destination page with source-derived content was not created',
763
+ ...(failedState?.resources ? { resourceStates: failedState.resources } : {}),
764
+ ...(options.dryRun ? { resourceStatusOverride: 'planned' } : {}),
765
+ error: currentFailure?.message ?? failedState?.lastError ?? 'Destination page with source-derived content was not created',
661
766
  }));
662
767
  progress.pages += 1;
663
768
  updateProgress(`blocked ${page.title}`);
@@ -906,28 +1011,48 @@ export class Migrator {
906
1011
  await writeFile(snapshotPath, cachedPage.rawContent, { mode: 0o600 });
907
1012
  const snapshotKey = `snapshot:${page.canonicalId}`;
908
1013
  const existingSnapshot = state.resources[snapshotKey];
1014
+ const snapshotFile = {
1015
+ path: snapshotPath,
1016
+ size: Buffer.byteLength(cachedPage.rawContent),
1017
+ sha256: sha256(cachedPage.rawContent),
1018
+ };
909
1019
  try {
910
- const snapshot = existingSnapshot?.status === 'complete' && existingSnapshot.destinationId
911
- ? {
912
- id: existingSnapshot.destinationId,
913
- name: existingSnapshot.destinationName,
914
- ...(existingSnapshot.size !== undefined ? { size: existingSnapshot.size } : {}),
1020
+ const knownSnapshot = remoteResources.find((item) => item.id === existingSnapshot?.destinationId)
1021
+ ?? remoteResources.find((item) => item.name.toLocaleLowerCase() === snapshotName.toLocaleLowerCase());
1022
+ let snapshot = knownSnapshot && await remoteMatches(knownSnapshot, snapshotFile, true)
1023
+ ? knownSnapshot
1024
+ : undefined;
1025
+ if (!snapshot) {
1026
+ try {
1027
+ snapshot = await destination.uploadResource({
1028
+ page: destinationPage,
1029
+ name: snapshotName,
1030
+ filePath: snapshotPath,
1031
+ size: snapshotFile.size,
1032
+ mimeType: page.rawFormat === 'yfm' ? 'text/markdown' : 'application/xml',
1033
+ ...(knownSnapshot ? { existingId: knownSnapshot.id } : {}),
1034
+ });
915
1035
  }
916
- : await destination.uploadResource({
917
- page: destinationPage,
918
- name: snapshotName,
919
- filePath: snapshotPath,
920
- size: Buffer.byteLength(cachedPage.rawContent),
921
- mimeType: page.rawFormat === 'yfm' ? 'text/markdown' : 'application/xml',
922
- ...(existingSnapshot?.destinationId ? { existingId: existingSnapshot.destinationId } : {}),
923
- });
1036
+ catch (uploadError) {
1037
+ remoteResources = await destination.listUploadedResources(destinationPage);
1038
+ const uncertain = remoteResources.find((item) => item.name.toLocaleLowerCase() === snapshotName.toLocaleLowerCase());
1039
+ if (uncertain && await remoteMatches(uncertain, snapshotFile, true))
1040
+ snapshot = uncertain;
1041
+ else
1042
+ throw uploadError;
1043
+ }
1044
+ }
1045
+ if (!remoteResources.some((item) => item.id === snapshot.id))
1046
+ remoteResources.push(snapshot);
924
1047
  state.resources[snapshotKey] = {
925
1048
  sourceId: snapshotKey,
926
1049
  sourceName: snapshotName,
927
1050
  destinationId: snapshot.id,
928
1051
  destinationName: snapshot.name,
929
1052
  ...(snapshot.size !== undefined ? { size: snapshot.size } : {}),
930
- sha256: sha256(cachedPage.rawContent),
1053
+ sha256: snapshotFile.sha256,
1054
+ cachePath: snapshotPath,
1055
+ mimeType: page.rawFormat === 'yfm' ? 'text/markdown' : 'application/xml',
931
1056
  status: 'complete',
932
1057
  updatedAt: new Date().toISOString(),
933
1058
  };
@@ -935,7 +1060,15 @@ export class Migrator {
935
1060
  catch (error) {
936
1061
  const detail = `Raw source snapshot was not transferred: ${error instanceof Error ? error.message : String(error)}`;
937
1062
  resourceProblems.push(detail);
938
- report.error(`${page.title}: ${detail}`);
1063
+ report.issue({
1064
+ code: 'source-snapshot-transfer-failed',
1065
+ severity: 'error',
1066
+ phase: 'resource',
1067
+ pageId: page.canonicalId,
1068
+ resourceId: snapshotKey,
1069
+ retryable: true,
1070
+ message: `${page.title}: ${detail}`,
1071
+ });
939
1072
  state.resources[snapshotKey] = {
940
1073
  sourceId: snapshotKey,
941
1074
  sourceName: snapshotName,
@@ -954,7 +1087,11 @@ export class Migrator {
954
1087
  if (destinationPage.contentFingerprint
955
1088
  && current.contentFingerprint
956
1089
  && current.contentFingerprint !== destinationPage.contentFingerprint) {
957
- throw new Error(`Destination conflict for ${page.title}: content changed while resources were transferred`);
1090
+ const normalization = verifyDestinationContent(destinationPage.content ?? bootstrap.body, current.content, destination.kind, cachedPage);
1091
+ if (normalization.status !== 'complete') {
1092
+ throw new Error(`Destination conflict for ${page.title}: content changed while resources were transferred`);
1093
+ }
1094
+ rendered.normalizations.push('Destination normalized page markup without semantic content changes');
958
1095
  }
959
1096
  verified = await destination.updatePage({
960
1097
  page: current,
@@ -999,7 +1136,8 @@ export class Migrator {
999
1136
  status: finalState.status,
1000
1137
  resources,
1001
1138
  document,
1002
- resourceSizes: finalState.resources,
1139
+ resourceStates: finalState.resources,
1140
+ ...(options.dryRun ? { resourceStatusOverride: 'planned' } : {}),
1003
1141
  resourceFallbacks: fallbackDetails,
1004
1142
  warnings: [...rendered.warnings, ...verificationDegradations],
1005
1143
  normalizations: rendered.normalizations,
@@ -1019,7 +1157,14 @@ export class Migrator {
1019
1157
  failureStatus = 'failed';
1020
1158
  }
1021
1159
  }
1022
- report.error(message);
1160
+ report.issue({
1161
+ code: failureStatus === 'conflict' ? 'destination-conflict' : 'page-transfer-failed',
1162
+ severity: 'error',
1163
+ phase: 'page',
1164
+ pageId: page.canonicalId,
1165
+ retryable: failureStatus !== 'conflict',
1166
+ message,
1167
+ });
1023
1168
  await store.setPage({
1024
1169
  ...state,
1025
1170
  ...(actual?.fingerprint ? { destinationFingerprint: actual.fingerprint } : {}),
@@ -1039,9 +1184,9 @@ export class Migrator {
1039
1184
  status: failureStatus,
1040
1185
  resources,
1041
1186
  document,
1042
- resourceSizes: state.resources,
1187
+ resourceStates: state.resources,
1188
+ ...(options.dryRun ? { resourceStatusOverride: 'planned' } : {}),
1043
1189
  resourceFallbacks: fallbackDetails,
1044
- warnings: resourceProblems,
1045
1190
  error: message,
1046
1191
  }), actual?.content ? { source: normalizedPreviewHtml(document), destination: renderedPreviewHtml(actual.content, destination.kind) } : undefined);
1047
1192
  }