Six stages, one of which needs you
Crawl, approve, compile, run, verify, ship. Only the second is interactive — and it is the one where a model touches your content at all.
The model is used for judgment — what is this DOM region, which block does it map to — and never for mechanics: string transformation, file writes, path handling. That split is why a defect found on page 4,312 is fixed in the compiler and therefore fixed for all ten thousand, as a failing unit test rather than a re-roll of a stochastic process.
- Hero0.96heroExact match
- Feature grid0.94cardsExact match
- Logo strip0.81logo-cloudVariant
- Comparison table0.42—Needs a new block
- Footer CTA0.98ctaExact match
One model call per cluster — never per region.
Connect a project
Point UEMA at the site you are migrating and the git repository it is migrating into.
- 1
Name the source and the target
A project is one migration: a source URL, a git remote and branch, a target (Edge Delivery or AEM Cloud Service) and a budget. Three fields are mandatory and marked as such — the tenant, the source URL and the git remote.
- 2
Pick a mode
PARITY reproduces the source as closely as possible. DESIGN_SYSTEM maps everything into a library you already have and refuses to invent a block that is not in it. HYBRID lets the two coexist per region, which is what most real migrations need.
- 3
Connect GitHub
A GitHub App installation gives UEMA short-lived, per-push tokens. The API never holds a private key, and a push opens a pull request rather than committing to your default branch.
Cockpit
How far the migration has got, and what it has cost.
10,412
Discovered
9,884
Verified
9,102
Published
528
Excluded
$18.40 / $2,500.00
Spend comes from the exemplar passes you asked for, not from page count — the bulk run makes no model call at all.
- Content parity Pass
- Visual diff Pass
- Links Pass
- Accessibility Pass
- Performance Pass
- SEO preservation Pass
- Structural sanity 12 to review
Crawl the source site
Capture the pages as a browser sees them — no export, no plugin, no vendor tool.
- 1
Queue an ingest job
Start it from the workspace and the job is queued in the database with a scope and a page cap. Nothing claims it automatically — the runner is a local process, because a few hundred megabytes of headless Chromium doing a batch job is the wrong thing to put on a web service.
- 2
Run the ingest runner
One command, pointed at your deployment. It claims the job, drives real Chromium over the site, respects robots and the scope you set, and completes the job with the captured documents.
- 3
Watch it in the Cockpit
Discovered, verified, published and excluded counts, live. Excluded is not a failure — it is deliberately out of scope, and collapsing the two would misreport how complete the migration is in the direction that flatters it.
- Hero0.96heroExact match
- Feature grid0.94cardsExact match
- Logo strip0.81logo-cloudVariant
- Comparison table0.42—Needs a new block
- Footer CTA0.98ctaExact match
One model call per cluster — never per region.
Approve one to three exemplars
The only step where a model touches your content, and the only one that needs your judgment.
- 1
Open a page in the Workspace
The canvas shows the captured page, the regions detected in it, and the block each region was mapped to. Regions are found deterministically and then clustered; a model is asked what a cluster is — one call per cluster, never per region.
- 2
Correct what is wrong
Each mapping arrives as an exact match, a match with a variant, a request for a new block, or unsupported — each with a reason. Fixing one here is fixing it for every page in that cluster.
- 3
Approve the template
Approving is the human gate. It is what tells the agent this pattern is understood well enough to write a compiler for.
Cockpit
How far the migration has got, and what it has cost.
10,412
Discovered
9,884
Verified
9,102
Published
528
Excluded
$18.40 / $2,500.00
Spend comes from the exemplar passes you asked for, not from page count — the bulk run makes no model call at all.
- Content parity Pass
- Visual diff Pass
- Links Pass
- Accessibility Pass
- Performance Pass
- SEO preservation Pass
- Structural sanity 12 to review
Let the compiler run the rest
Deterministic, parallel, resumable — and it makes no model call at all.
- 1
The agent writes an importer
From the approved exemplars, it produces a deterministic TypeScript module with fixture tests. You approve that too — the template and the importer are approved separately, because the pattern being right and the code being right are different claims.
- 2
The module runs over every remaining page
Not the model. That is the whole economic argument of the product, and it is enforced by the dependency graph: the packages that do this work cannot reach a model call.
- 3
Fix defects in the compiler, not the output
A problem on page 4,312 becomes a failing unit test in the importer. Fix it once and it is fixed for all ten thousand — reproducibly, and reviewably.
Review queue
What the gates could not clear on their own.
- /pricing/enterpriseStructural sanity
Comparison table matched no block above the confidence floor
Approve Waive with reason - /about/leadershipVisual diff
Visual diff 4.1% — headshot grid reflowed at 768px
Approve Waive with reason - /resources/2019-reportLinks
3 outbound links resolve to 404 on the source site
Approve Waive with reason
Every decision records who made it and why, in an append-only table.
Pass the gates, clear the queue
Eight blocking checks, and a human on whatever they could not settle.
- 1
Seven gates run
Content parity, visual diff, links, accessibility, performance, SEO preservation and structural sanity. Each is a gate rather than a report: a page that fails one does not proceed with a warning attached to it.
- 2
Work the review queue
What the gates could not clear alone arrives ranked by what is uncertain about it — a region with no confident block match, a broken link, a layout that shifted.
- 3
Your decision is recorded
Approving or waiving writes an append-only resolution carrying who you are and why. The decision is the artifact; removing the row is just what happens next.
- blocks/hero/hero.js
- blocks/hero/hero.css
- blocks/cards/cards.js
- content/pricing.md
- content/legacy-promo.md
A deleted path keeps its prior content until the next emission, so you can stage the deletion.
export default function decorate(block) {- block.classList.add('hero-legacy');+ const [picture, text] = block.children;+ picture?.classList.add('hero-media');+ text?.classList.add('hero-body'); }Review the diff and open the PR
What you review is the commit itself, not a rendering of an intention.
- 1
Emit
Assembly runs inline — it is pure CPU over rows already in the database and makes no model call by construction, so there is no queue to wait on. Emitting zero documents is refused rather than stored, because an empty set would read as 'the migration produced nothing'.
- 2
Read the diff
Every emitted file against what is on your branch. Deletions stay visible with their prior content until the next emission, so you can see and stage them rather than discovering them later.
- 3
Push
Stage what you want, and UEMA commits, pushes and opens a pull request — or updates the existing one on that branch, which is what a re-emission should do.
The crawl is one command
The ingest runner is deliberately not deployed. It drives headless Chromium, and a few hundred megabytes of browser doing a batch job is the wrong thing to put on a web service — so it runs on your machine, against your deployment.
A job sits queued until something claims it. Nothing claims it automatically — which is a design decision, not an omission.
cd packages/ingest-runner
UEMA_API_URL=https://your-api.example \
npx tsx src/main.ts --ingest <siteId> https://acme.comcurl -s https://your-api.example/projects/<id>/stats \
-H "authorization: Bearer $TOKEN"
# { "discovered": 10412, "verified": 9884,
# "published": 9102, "excluded": 528 }See it run against a real site
The quickest way to judge this is a working session on one of your own templates, not a page of prose describing one.