orbital migrate verify
orbital migrate verify is the third step in a migration. It samples source-side artifacts via the same per-source adapter the planner uses and compares each (path, sha256, size) tuple against the matching OrbitalReg-side artifact row. The output is a pass/fail verdict plus a per-decision divergence list the operator can act on.
The check is read-only — verify never mutates artifacts, repositories, or migration_runs. It also never persists the source-side credential server-side: re-supply the token on each verify run so a credential rotation takes effect immediately.
The CLI is a thin layer over POST /api/admin/migrate/verify.
When to run verify
| Strategy | When to run |
|---|---|
big-bang | After every apply pass, to catch drift before consumers notice. |
blue-green | Before flipping consumers, to confirm the green side is byte-equivalent to the source. |
lazy-proxy | Periodically — verify reports missing_on_orbital for not-yet-cached artifacts, but sha256_mismatch flags real corruption regardless of warm-up state. |
Verify is also the integration test you run after a --resume'd apply, or after re-running orbital migrate apply against a partially-corrupt target.
Decisions
Each comparison resolves to one of:
| Decision | Meaning |
|---|---|
match | Both sides have the artifact and sha256 + size agree. The happy path. |
no_source_hash | The source listing didn't carry an sha256. Informational; doesn't fail the verdict. |
missing_on_orbital | Source has the artifact; OrbitalReg doesn't. Expected during a lazy-proxy warm-up; failure for big-bang / blue-green. |
sha256_mismatch | Both sides have the artifact but content hashes differ. The most actionable failure — re-import the affected artifact. |
size_mismatch | Neither side reported sha256 but sizes diverge. Treated as failure. |
orbital_repo_missing | Source has artifacts but the OrbitalReg-side repository row doesn't exist. Apply was never run, or the repo was skipped (unsupported format / slug collision). |
Verify passes when every comparison resolves to match or no_source_hash. Anything else makes the CLI exit with the server-error exit code so a CI gate can fail the pipeline.
Quick start
# 1. Generate a plan (Phase A)
orbital migrate plan \
--source jfrog \
--endpoint https://jfrog.example.com \
--token "$JFROG_TOKEN" \
--output-file migration-plan.json
# 2. Apply it (Phase B)
orbital migrate apply --plan migration-plan.json
# 3. Verify the result (Phase C)
orbital migrate verify \
--plan migration-plan.json \
--token "$JFROG_TOKEN"Sample output (pretty-printed, all-pass):
Migration verify — PASS — source=jfrog project=jfrog-import
Repos: 12 examined, 0 skipped
Sample: 300 examined (per-repo target: 25)
Strategy: big-bang
Decisions:
match 300
Next steps:
- Migration looks healthy. Re-run periodically to catch drift.Sample output (one repo with byte-level corruption):
Migration verify — FAIL — source=jfrog project=jfrog-import
Repos: 12 examined, 0 skipped
Sample: 300 examined (per-repo target: 25)
Strategy: big-bang
Decisions:
match 297
sha256_mismatch 3
Failures:
[sha256_mismatch] maven-prod/com/acme/foo/1.0.0/foo-1.0.0.jar — source=ab12cd34ef56… orbital=ff00aa11bb22…
[sha256_mismatch] maven-prod/com/acme/foo/1.0.0/foo-1.0.0.pom — source=… orbital=…
[sha256_mismatch] maven-prod/com/acme/foo/1.0.0/foo-1.0.0-sources.jar — source=… orbital=…
Next steps:
- sha256_mismatch: re-import the affected artifacts.
- missing_on_orbital: re-run `orbital migrate apply` (or wait — lazy-proxy fills on demand).
- orbital_repo_missing: the apply step did not create this repo (unsupported format / slug collision).Flags
| Flag | Purpose |
|---|---|
--plan <path> | The plan envelope produced by orbital migrate plan --output-file. Required. |
--token <bearer> | Source-registry credential. Required. Same shape as plan (bearer / keys:… / sa:…). |
--endpoint <url> | Override the source endpoint captured in the plan (fresh DNS, mid-migration cutover). |
--project-slug <slug> | OrbitalReg project to verify against (default <source>-import). |
--sample <n> | Per-run total sample, split evenly across migrated repos. Default per-repo: 25. |
--format <fmt> | Limit verify to one format (maven, npm, docker, …). |
--json | Emit the JSON verify-result instead of the pretty-printed view. |
Plus the same per-source extras plan accepts (--github-org, --gitlab-project-id, --azure-organization, --aws-region, --gcp-project-id, etc.) — supply them when the source needs them.
Sample sizing
Verify caps each repo at 250 artifacts on the server side, regardless of --sample, so a deliberately oversized request can't pin a production source for minutes. The default per-repo (when --sample is zero) is 25 — enough to surface a systemic divergence on a well-populated repo while keeping the total runtime sub-30-seconds against a representative source.
If you set --sample N, the CLI splits N evenly across the migrated repos that pass --format filtering. So --sample 250 --format maven on a plan with 5 maven repos visits 50 artifacts per repo; --sample 600 on 12 mixed-format repos visits 50 per repo across the board.
What verify does not check
- Synthetic format-native pulls (
npm pack,mvn dependency:get,docker pull) — out of scope for this Phase. Format-native pulls test the full publish-side stack (manifest indexing, virtual repo routing, RBAC), which is a meaningfully different surface from artifact byte equivalence; they ship in a follow-up. - Permission spot-checks — verify never simulates synthetic-user pulls. RBAC differences between the source and OrbitalReg are intentional in most migrations (the whole point is often consolidating ten ad-hoc Nexus realms into one OrbitalReg project), so an automated diff would mostly flag deliberate changes.
- Async metadata like build-info envelopes or per-artifact signatures — those land via their own ingestion paths and have their own verification surfaces (
/admin/trust#sigstore,/api/v1/builds).
The byte-equivalence check verify ships answers the procurement question ("did the migration land what it claimed?") in one command; the gaps above are tracked separately.
Exit codes
| Code | Meaning |
|---|---|
0 | Verify passed (every comparison resolved to match or no_source_hash). |
1 | User error — bad flags, missing --plan, missing --token. |
2 | Auth error — OrbitalReg session rejected (orbital auth login). |
3 | Server error — including pass=false. The divergence list is on stdout above this exit code; a CI gate that fails-on-non-zero will catch a corrupt migration without parsing JSON. |
For CI scripting, prefer --json plus a JSON parser over screen-scraping the pretty output:
result=$(orbital migrate verify --plan plan.json --token "$T" --json)
pass=$(echo "$result" | jq -r .pass)
if [ "$pass" != "true" ]; then
echo "$result" | jq '.rows[] | select(.decision != "match" and .decision != "no_source_hash")'
exit 1
fiRoadmap context
orbital migrate verify is Phase C of item 83 in the product roadmap. Phase A (plan) and Phase B (apply) shipped earlier; Phase D (finalize) and Phase E (importer-plugin glue) hang off the same /api/admin/migrate/* mount and orbital migrate subcommand subtree.