bg-coolify-migrate 2.2.2__tar.gz → 2.2.3__tar.gz

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.
Files changed (123) hide show
  1. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/CHANGELOG.md +13 -0
  2. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/PKG-INFO +3 -3
  3. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/README.MD +2 -2
  4. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/__init__.py +1 -1
  5. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/engine/steps.py +14 -2
  6. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/settings/base.py +9 -0
  7. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_engine_steps.py +15 -1
  8. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/.gitignore +0 -0
  9. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/LICENSE +0 -0
  10. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/README.template.MD +0 -0
  11. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/SECURITY.template.MD +0 -0
  12. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/architecture.md +0 -0
  13. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/cli.md +0 -0
  14. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/configuration.md +0 -0
  15. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/index.md +0 -0
  16. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/installation.md +0 -0
  17. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/quickstart.md +0 -0
  18. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/safety.md +0 -0
  19. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/server-migration.md +0 -0
  20. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/troubleshooting.md +0 -0
  21. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/docs/why.md +0 -0
  22. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/pyproject.toml +0 -0
  23. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/api/__init__.py +0 -0
  24. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/api/client.py +0 -0
  25. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/api/fields.py +0 -0
  26. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/api/resources.py +0 -0
  27. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/cli.py +0 -0
  28. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/discovery/__init__.py +0 -0
  29. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/discovery/docker.py +0 -0
  30. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/discovery/quiesce.py +0 -0
  31. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/dns/__init__.py +0 -0
  32. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/dns/extract.py +0 -0
  33. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/dns/gate.py +0 -0
  34. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/dns/resolve.py +0 -0
  35. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/domain/__init__.py +0 -0
  36. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/domain/compose.py +0 -0
  37. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/domain/drift.py +0 -0
  38. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/domain/images.py +0 -0
  39. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/domain/kinds.py +0 -0
  40. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/domain/manifest.py +0 -0
  41. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/domain/naming.py +0 -0
  42. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/domain/plan.py +0 -0
  43. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/domain/statemachine.py +0 -0
  44. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/engine/__init__.py +0 -0
  45. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/engine/compensations.py +0 -0
  46. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/engine/context.py +0 -0
  47. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/engine/executor.py +0 -0
  48. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/engine/keys.py +0 -0
  49. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/engine/planner.py +0 -0
  50. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/engine/runner.py +0 -0
  51. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/errors.py +0 -0
  52. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/journal/__init__.py +0 -0
  53. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/journal/store.py +0 -0
  54. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/observability/__init__.py +0 -0
  55. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/observability/logging_setup.py +0 -0
  56. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/py.typed +0 -0
  57. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/server/__init__.py +0 -0
  58. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/server/appkey.py +0 -0
  59. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/server/fencing.py +0 -0
  60. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/server/inventory.py +0 -0
  61. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/server/runner.py +0 -0
  62. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/server/statemachine.py +0 -0
  63. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/server/steps.py +0 -0
  64. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/settings/__init__.py +0 -0
  65. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/transfer/__init__.py +0 -0
  66. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/transfer/partition.py +0 -0
  67. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/transfer/rsync.py +0 -0
  68. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/transfer/ssh.py +0 -0
  69. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/transfer/verify.py +0 -0
  70. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/ui/__init__.py +0 -0
  71. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/ui/console.py +0 -0
  72. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/ui/dashboard.py +0 -0
  73. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/ui/report.py +0 -0
  74. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/ui/run_report.py +0 -0
  75. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/ui/server_report.py +0 -0
  76. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/src/bg_coolify_migrate/ui/wizard.py +0 -0
  77. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/__init__.py +0 -0
  78. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/conftest.py +0 -0
  79. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/README.md +0 -0
  80. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/__init__.py +0 -0
  81. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/bootstrap.py +0 -0
  82. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/conftest.py +0 -0
  83. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/docker-compose.yml +0 -0
  84. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/prepare.py +0 -0
  85. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/runner.Dockerfile +0 -0
  86. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/test_all_engines.py +0 -0
  87. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/test_compose_service.py +0 -0
  88. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/test_drift_gate.py +0 -0
  89. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/test_label_contract.py +0 -0
  90. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/test_multi_resource.py +0 -0
  91. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/test_real_migration.py +0 -0
  92. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/e2e/test_rollback.py +0 -0
  93. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/integration/__init__.py +0 -0
  94. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/integration/docker-compose.yml +0 -0
  95. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/integration/prepare.py +0 -0
  96. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/integration/test_real_transfer.py +0 -0
  97. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_api_client.py +0 -0
  98. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_api_fields.py +0 -0
  99. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_api_resources.py +0 -0
  100. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_cli.py +0 -0
  101. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_discovery.py +0 -0
  102. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_dns.py +0 -0
  103. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_domain_compose.py +0 -0
  104. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_domain_drift.py +0 -0
  105. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_domain_images.py +0 -0
  106. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_domain_kinds.py +0 -0
  107. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_domain_manifest.py +0 -0
  108. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_domain_naming.py +0 -0
  109. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_domain_plan.py +0 -0
  110. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_domain_statemachine.py +0 -0
  111. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_engine.py +0 -0
  112. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_engine_context.py +0 -0
  113. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_engine_planner.py +0 -0
  114. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_engine_wiring.py +0 -0
  115. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_journal.py +0 -0
  116. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_observability.py +0 -0
  117. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_packaging.py +0 -0
  118. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_server_migration.py +0 -0
  119. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_transfer.py +0 -0
  120. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_ui.py +0 -0
  121. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_ui_run.py +0 -0
  122. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_ui_wizard.py +0 -0
  123. {bg_coolify_migrate-2.2.2 → bg_coolify_migrate-2.2.3}/tests/test_verify_collect.py +0 -0
@@ -1,6 +1,19 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v2.2.3 (2026-07-16)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **engine**: Finish the no-volume migration path
9
+ ([`0c1a4b8`](https://github.com/bauer-group/IP-CoolifyMigration/commit/0c1a4b8684f6f872d01d72c1d0236e1be04ea63a))
10
+
11
+ ### Documentation
12
+
13
+ - Update README.MD [automated]
14
+ ([`764b31e`](https://github.com/bauer-group/IP-CoolifyMigration/commit/764b31e4b7d1dd73076634154bf97f5f5524131a))
15
+
16
+
4
17
  ## v2.2.2 (2026-07-16)
5
18
 
6
19
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bg-coolify-migrate
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: BAUER GROUP Coolify migration toolkit — failsafe, resumable project and instance migration with byte-exact volume mirroring
5
5
  Project-URL: Homepage, https://github.com/bauer-group/IP-CoolifyMigration
6
6
  Project-URL: Source, https://github.com/bauer-group/IP-CoolifyMigration
@@ -55,7 +55,7 @@ Requires-Dist: mkdocstrings[python]>=0.26.0; extra == 'docs'
55
55
  Description-Content-Type: text/markdown
56
56
 
57
57
  <!-- AUTO-GENERATED FILE. DO NOT EDIT. Edit docs/README.template.MD instead. -->
58
- <!-- Generated on 2026-07-16 17:04:54 UTC -->
58
+ <!-- Generated on 2026-07-16 17:36:02 UTC -->
59
59
 
60
60
  # BAUER GROUP - Coolify Migration Toolkit
61
61
 
@@ -78,7 +78,7 @@ external orchestrator has none of those constraints. That is what this is.
78
78
 
79
79
  ## Repository Information
80
80
 
81
- - **Version:** v2.2.1
81
+ - **Version:** v2.2.2
82
82
  - **Repository:** bauer-group/IP-CoolifyMigration
83
83
  - **Branch:** main
84
84
  - **Architecture:** Pure-logic cores with thin IO shells; saga engine with a
@@ -1,5 +1,5 @@
1
1
  <!-- AUTO-GENERATED FILE. DO NOT EDIT. Edit docs/README.template.MD instead. -->
2
- <!-- Generated on 2026-07-16 17:04:54 UTC -->
2
+ <!-- Generated on 2026-07-16 17:36:02 UTC -->
3
3
 
4
4
  # BAUER GROUP - Coolify Migration Toolkit
5
5
 
@@ -22,7 +22,7 @@ external orchestrator has none of those constraints. That is what this is.
22
22
 
23
23
  ## Repository Information
24
24
 
25
- - **Version:** v2.2.1
25
+ - **Version:** v2.2.2
26
26
  - **Repository:** bauer-group/IP-CoolifyMigration
27
27
  - **Branch:** main
28
28
  - **Architecture:** Pure-logic cores with thin IO shells; saga engine with a
@@ -32,7 +32,7 @@ from __future__ import annotations
32
32
 
33
33
  from typing import TYPE_CHECKING, Any
34
34
 
35
- __version__ = "2.2.2"
35
+ __version__ = "2.2.3"
36
36
 
37
37
  # Name -> submodule, resolved on first attribute access.
38
38
  _LAZY: dict[str, str] = {
@@ -306,6 +306,10 @@ async def _capture_mounts(ctx: MigrationContext) -> None:
306
306
  "not carry the labels we filter on."
307
307
  ),
308
308
  )
309
+ # Record an EXPLICIT empty capture, not nothing: DISCOVER treats a
310
+ # missing key (None) as a lost capture and aborts, but an empty list
311
+ # correctly means "this resource had no mounts".
312
+ ctx.pre_stop_mounts[snapshot.uuid] = []
309
313
  log.info("quiesce.no_containers_no_volumes", resource=snapshot.name)
310
314
  continue
311
315
  mounts = await inspect_all_mounts(ctx.source_host, containers)
@@ -497,6 +501,13 @@ async def _transfer_endpoint(ctx: MigrationContext) -> tuple[str, int, str | Non
497
501
 
498
502
  async def step_copy(ctx: MigrationContext) -> dict[str, Any]:
499
503
  """Mirror every volume, byte for byte, in parallel where it is safe."""
504
+ # A stateless / rebuilt resource has no volume pairs. Skip the ephemeral-key
505
+ # setup and the restart re-check entirely: there is nothing to copy, and doing
506
+ # the work anyway only adds a way for a no-data migration to fail spuriously.
507
+ if not any(ctx.volume_pairs.get(r.snapshot.uuid) for r in ctx.plan.resources):
508
+ log.info("copy.nothing", reason="no volumes to migrate")
509
+ return {"volumes_copied": [], "key_fingerprint": None}
510
+
500
511
  ctx.ephemeral_key = await keys.install(
501
512
  source=ctx.source_host, target=ctx.target_host, migration_id=ctx.migration_id
502
513
  )
@@ -674,9 +685,10 @@ async def step_healthcheck(ctx: MigrationContext) -> dict[str, Any]:
674
685
  """Wait for the target's containers to come up.
675
686
 
676
687
  A deploy is asynchronous, so "start returned" means nothing. We poll the
677
- target's daemon for the same reason we poll the source's.
688
+ target's daemon for the same reason we poll the source's. The window is
689
+ deploy_timeout, not stop_timeout: a git-built app clones and builds here first.
678
690
  """
679
- deadline = ctx.settings.stop_timeout
691
+ deadline = ctx.settings.deploy_timeout
680
692
  labels = observed_labels(ctx.plan)
681
693
  waited = 0.0
682
694
  interval = 3.0
@@ -99,6 +99,15 @@ class SshSettingsMixin(BaseSettings):
99
99
  "an unflushed, torn volume."
100
100
  ),
101
101
  )
102
+ deploy_timeout: float = Field(
103
+ default=900.0,
104
+ ge=10.0,
105
+ description=(
106
+ "How long to wait for the target to come up after start. Much longer than "
107
+ "stop_timeout because a git-built application clones and BUILDS on the "
108
+ "target first, which can take many minutes."
109
+ ),
110
+ )
102
111
 
103
112
 
104
113
  class ObservabilityMixin(BaseSettings):
@@ -156,7 +156,21 @@ class TestCaptureMounts:
156
156
  )
157
157
  )
158
158
  await _capture_mounts(ctx) # no raise
159
- assert ctx.pre_stop_mounts == {} # nothing captured, nothing lost
159
+ # An EXPLICIT empty capture (not a missing key), so DISCOVER does not treat
160
+ # it as a lost capture and abort at the next step.
161
+ assert ctx.pre_stop_mounts == {"db1": []}
162
+
163
+ async def test_copy_is_a_noop_and_installs_no_key_without_volumes(
164
+ self, ctx: MigrationContext
165
+ ) -> None:
166
+ # No volume pairs -> copy must not install an ephemeral key or re-check the
167
+ # source; a no-data migration should not have a way to fail there.
168
+ from bg_coolify_migrate.engine.steps import step_copy
169
+
170
+ ctx.volume_pairs = {}
171
+ result = await step_copy(ctx)
172
+ assert result["volumes_copied"] == []
173
+ assert ctx.ephemeral_key is None
160
174
 
161
175
 
162
176
  class TestPreflight: