67 """Attach the M11.2 migration subcommands."""
69 migrate_parser.set_defaults(handler=_cmd_migrate_group, migrate_parser=migrate_parser)
70 subcommands = migrate_parser.add_subparsers(dest=
"migrate_command")
72 import_parser = subcommands.add_parser(
74 help=
"Import legacy queue/status files into the registry.",
76 import_parser.add_argument(
79 help=
"Registry SQLite path. Defaults to the discovery algorithm.",
81 import_parser.add_argument(
84 help=
"Build and report the projection without writing.",
86 import_parser.add_argument(
89 help=
"Emit the structured import report as JSON.",
91 import_parser.set_defaults(handler=_cmd_import)
93 verify_parser = subcommands.add_parser(
95 help=
"Compare the registry with the current legacy projection.",
97 verify_parser.add_argument(
100 help=
"Registry SQLite path. Defaults to the discovery algorithm.",
102 verify_parser.add_argument(
105 help=
"Emit the structured verify report as JSON.",
107 verify_parser.set_defaults(handler=_cmd_verify)
109 rollback_parser = subcommands.add_parser(
111 help=
"Restore the most recent pre-migration registry backup.",
113 rollback_parser.add_argument(
116 help=
"Registry SQLite path. Defaults to the discovery algorithm.",
118 rollback_parser.add_argument(
121 help=
"Emit the structured rollback report as JSON.",
123 rollback_parser.set_defaults(handler=_cmd_rollback)
125 cutover_parser = subcommands.add_parser(
127 help=
"Reserved for the M11.8 cutover phase.",
129 cutover_parser.add_argument(
132 help=
"Emit the structured cutover report as JSON.",
134 cutover_parser.set_defaults(handler=_cmd_cutover)
165 result = run_cutover_stub()
167 print(json.dumps(result.to_dict(), indent=2, sort_keys=
True))
169 print(result.message, file=sys.stderr)
170 return result.exit_code
196 """Return a compact human-readable migration report."""
198 ok = result.get(
"ok")
is True
199 summary = result.get(
"summary", {})
200 lines = [f
"{label} {'ok' if ok else 'failed'}"]
201 if isinstance(summary, Mapping):
204 for key, value
in summary.items()
205 if key !=
"status_counts"
208 lines.append(f
" {counts}")
209 status_counts = summary.get(
"status_counts")
210 if isinstance(status_counts, Mapping):
211 rendered =
", ".join(f
"{key}={value}" for key, value
in status_counts.items())
212 lines.append(f
" statuses: {rendered}")
213 backup_path = result.get(
"backup_path")
or result.get(
"restored_from")
215 lines.append(f
" backup: {backup_path}")
216 for field
in (
"warnings",
"errors"):
217 values = result.get(field, [])
218 if isinstance(values, list)
and values:
219 lines.append(f
" {field}:")
220 lines.extend(f
" - {value}" for value
in values)
221 if result.get(
"diff"):
222 lines.append(
" diff: present")
223 return "\n".join(lines)