feat(migrate): add vercel-to-aws migration assessment skill - #141
feat(migrate): add vercel-to-aws migration assessment skill#141herosjourney wants to merge 12 commits into
Conversation
…w migration-to-aws skill: an honest Vercel-to-AWS assessment for Next.js apps (not a full migration plan). Built on the phase DSL (same architecture as heroku-to-aws), 5 backbone phases + 1 optional scaffold checkpoint: - PreScan -> Discover -> Clarify -> Recommend -> Report, with an optional Scaffold checkpoint - Discover computes a Coupling Score and 10 named Pre-Flight Checks unconditionally, before Recommend has run - Recommend is a fixed-precedence, three-outcome engine (OpenNext/SST, ECS Fargate, or a Vercel+AWS Hybrid) - every recommendation traces to exactly one rule, never a judgment call - Report renders a validated HTML assessment (new scripts/validate-assessment-report.py + pytest suite + reference/stub fixtures), with outcome-filtered findings, a reader-vocabulary rule, and a cost-labeling rule enforced by the validator - Scaffold (optional) emits a thin IaC skeleton matching the recommended outcome - SST/Terraform for OpenNext, Terraform-only for Fargate or the Hybrid path - and defaults compute to Graviton (ARM64) where SST/Terraform expose the option - assessment-state.json: a skill-owned resumability ledger (independent of the vendored .phase-status.json) supporting incremental, effort-for-confidence input collection across multiple sessions Registers the skill in the plugin's README, the top-level migrate README, and the Claude/Codex/Cursor plugin manifests. Verified: frontmatter validator (0 problems), pytest (39/39), dprint check and markdownlint-cli2 clean, reference fixture passes the validator (REPORT_OK), stub fixture fails with actionable errors (REPORT_FAIL).
…hipped) Per the precedent set in 033669c (removed heroku-eks-support, heroku-to-aws-migration, and migration-telemetry-sharing spec dirs from a prior PR), .kiro/specs/ is local Kiro working material, not project documentation this repo ships - no .kiro/specs/ directory exists anywhere on main today. The requirements/design/tasks docs used to drive this skill's build don't belong in the PR.
…gration-skill # Conflicts: # .claude-plugin/marketplace.json # migrate/README.md # migrate/plugins/migration-to-aws/.claude-plugin/plugin.json # migrate/plugins/migration-to-aws/.codex-plugin/plugin.json # migrate/plugins/migration-to-aws/.cursor-plugin/plugin.json # migrate/plugins/migration-to-aws/README.md
… the worker contract Three architecture contradictions made the skill unexecutable as written: prescan declared _interactive:false + _exec:rw while its collect fragment converses with the founder (token request), runs next build, and calls the Vercel API; four of discover's dispatched fragments needed shell or network the rw worker (Read/Grep/Glob/Write/Edit) does not have; and the token, collected interactively and never persisted, could not reach a dispatched worker at all. Retrofit (the pattern proven on heroku/gcp live discovery, awslabs#148/awslabs#149): - prescan runs INLINE (_interactive: true, _exec removed) — its work is conversational + shell + network by nature; scope boundary corrected (the Tier 1 build-health attempt belongs to prescan-collect per Requirements 1.1-1.2) - NEW discover-capture.md: main-window pre-work owning ALL shell/network — adapter/manifest build capture, a GET-only 8-endpoint API whitelist (Vercel tokens cannot be scoped read-only, so the whitelist IS the read-only guarantee), env keys projected to names before touching disk, headers-only probe capture, manifest.json index; VERCEL_TOKEN lives in an env var in the main window only - discover-adapter/manifests/api/probe become parse-only fragments reading capture/; triggers keyed to the capture manifest; new postconditions (no env values/token material anywhere; coupling items carry confidence) - prescan-collect token request rewritten per Vercel's token docs: read-only tokens do not exist — ask for project-scoped + shortest expiration, state the GET-whitelist enforcement honestly, give the revoke command - Vercel MCP positioned as optional read-only enrichment (runtime logs, access links) with the purchase-tool caution — not the discovery backbone Validated: fixtures/vercel-capture/ replay set (manifest-fallback path, ISR cross-check, unclassified Route Handlers, skipped crons/usage, names-only env keys) + fresh-agent Discover replay: HANDOFF_OK, zero network/build/token, check_expected_discovery.py PASS. Also: shared:sync refreshed the stale vendored INTERPRETER.md this branch carried.
|
Pushed 8bc4db8 — an architecture retrofit applying the capture/parse pattern proven on the Heroku/GCP live-discovery PRs (#148/#149). Three contradictions made the skill unexecutable as written: 1. prescan declared |
…docs - Domains endpoint corrected to the documented /v9 (was /v5) - Env row note strengthened: the documented response schema carries value/ vsmValue/legacyValue fields even with decrypt=false — the key-name pipeline projection is the REAL protection, not the query param - Crons and storage rows reworded honestly: neither path is in the public REST reference; discover the GET endpoints via 'vercel api list' (the CLI's beta OpenAPI-backed api command), else record skipped — vercel.json and env-name/ dependency signals remain the fallbacks - Transport note added: prefer 'vercel api' (session/token auth, --paginate for the 100-per-page list endpoints, OpenAPI endpoint discovery), raw curl with manual pagination.next handling as fallback - Rate limits documented as a non-issue (200-1000 reads/min per endpoint vs this capture's small volume) - Fixture manifest endpoint strings synced
…fied whitelist The prior commit's message claimed this sync but the edit had failed on formatting drift — domains now /v9, crons/stores rows use the OpenAPI-discovered wording.
|
Doc-verification round (f9d60a0 + 0a1b2c…): checked the capture whitelist against Vercel's REST API reference and limits docs. Four corrections, one improvement: 1. Domains endpoint was wrong — corrected to the documented |
…iew follow-up: rows 6-7 (crons/stores) stay unpinned because Vercel does not document those paths -- pinning guesses would be fake precision that breaks silently. But the discovery step was looser than the pinned rows. New constraints: the resolved operation must be GET in the spec, its path must contain the row's subject segment (crons / stores|storage), the exact resolved path is recorded verbatim in the manifest entry with discovered_via: 'vercel api list' (audit trail), and no match means skipped -- never a close-enough substitute. Fixture manifest updated to model the audit fields.
|
Review follow-up pushed (1c1ceaa): constrained the OpenAPI-discovered capture rows (6–7, crons/stores). They stay unpinned deliberately — those paths are not in Vercel's public REST reference, and pinning guessed paths would be fake precision that breaks silently when they move. Instead the discovery step is now held to the same narrowness as the pinned rows: (1) the resolved operation must be |
…s The capture manifest's build.files names three .next build manifests (routes/prerender/app-path-routes) that were never committed -- the validated replay ran against scratch copies. Reconstructed exactly per expected-discovery.json and discover-manifests.md: /blog/[slug] in dynamicRoutes with two prerendered entries at initialRevalidateSeconds 3600 (genuinely ISR), / static, /dashboard absent from prerender-manifest (dynamic), /api/checkout only in app-path-routes-manifest (Route Handler, unclassified by page manifests -> LOW-confidence dynamic). Preview keys are labeled fixture placeholders, not secrets.
… repo-root .gitignore's global build/ rule silently dropped the three canned .next manifests from the previous commit. Scoped negation inside the fixture directory re-includes them — they are committed test data mirroring the real capture layout, not build output.
…endering Fresh-agent replay against the restored build-manifest fixtures surfaced the ambiguity: staticRoutes/dynamicRoutes distinguish fixed paths from [param] segments -- a fixed-path page that renders per-request still sits in staticRoutes, and the replay classified /dashboard as static because of it. The rendering disposition comes from prerender-manifest membership: numeric initialRevalidateSeconds = isr, false = static, absent = dynamic. Spelled out the resolution table and extended the never-infer rule to cover static-from-staticRoutes as well as isr-from-revalidate-exports.
…ase Caught by the new fixtures-check tool: the skill declares 6 phases (incl. scaffold) but the replay seed's .phase-status.json listed 5 -- phase-status.schema.json requires one entry per declared phase. (On the stacked generate-phase branch the seed already tracks that branch's estimate/generate phase set.)
|
The vercel-to-aws skill this PR introduced is now on Two things to flag:
Since the substance is merged and the headline |
Summary
Adds a new
migration-to-awsskill: an honest Vercel-to-AWS assessment for Next.js apps (deliberately not a full migration plan — Vercel billing data is too thin for that, see Out of Scope in the spec). Built on the phase DSL, same architecture asheroku-to-aws.What it does
scripts/validate-assessment-report.py+ pytest suite + reference/stub fixtures), enforcing an outcome-based findings filter, a reader-vocabulary rule (no check IDs/filenames in the executive summary), and a cost-labeling ruleassessment-state.json— a skill-owned resumability ledger, independent of the vendored.phase-status.json, so a founder can supply a log drain export a week later and only the affected findings recomputeWhy
Vercel's infrastructure (CloudFront-equivalent behaviors, function tuning, edge routing) isn't directly exportable the way GCP/Heroku Terraform is. This skill derives what it can from build output, source configs (
next.config.js,middleware.ts,vercel.json), and the Vercel API, and is honest about confidence — every sub-HIGH finding names the specific input that would upgrade it, and the report tells founders what they'd lose (PR previews first) alongside what they'd gain.Files changed
migrate/plugins/migration-to-aws/skills/vercel-to-aws/(SKILL.md, knowledge/, references/phases/{prescan,discover,clarify,recommend,report,scaffold}/, references/shared/, references/state/, references/vendored/)scripts/validate-assessment-report.py+tests/test_validate_assessment_report.py+fixtures/assessment-report-{reference,stub}.html+ 3 supporting reference JSON fixturesmigrate/README.md,migrate/plugins/migration-to-aws/README.md,.claude-plugin/marketplace.json, and the plugin's.claude-plugin/.codex-plugin/.cursor-pluginmanifests to register the new skill's triggers/keywords.kiro/specs/vercel-to-aws/{requirements,design,tasks}.mdUpdates since opening (review rounds folded in)
discover-capture.mdruns in the main window with a GET-only endpoint whitelist (doc-verified paths; the undocumented crons/stores rows resolve viavercel api listunder explicit constraints — GET-only, subject path-segment match, resolved path logged to the manifest as an audit trail); the discover fragments are parse-only.routes-manifest.jsonarrays classify path shape, not rendering; prerender-manifest membership decides static/isr/dynamic (a replay had mis-classified/dashboardunder the old wording).fixtures/vercel-capture/— canned captures (including the three.nextbuild manifests a repo-root.gitignorerule had silently swallowed; scoped negation added), mid-pipeline seeds,expected-discovery.json, and a stdlib asserter.Relationship to other PRs
Test plan
node tools/frontmatter-validator/validate.ts skills/vercel-to-aws— 0 problems (6 phase files checked)pytest tests/test_validate_assessment_report.py -q— 39/39 passingpython3 scripts/validate-assessment-report.py fixtures/assessment-report-reference.html—REPORT_OKpython3 scripts/validate-assessment-report.py fixtures/assessment-report-stub.html—REPORT_FAILwith actionable errors (regression guard)dprint checkandmarkdownlint-cli2clean across every touched/new fileVendored files (
INTERPRETER.md,phase-status.schema.json) confirmed byte-identical toskills/shared/canonical sourceFull live dry-run of the backbone (PreScan through Report) against a real, buildable Next.js 15.3.0 fixture, plus a separate live dry-run of the Scaffold checkpoint across all outcome shapes (A/B/C-A-shaped/C-B-shaped/unresolved-tiebreak) — real
next build, realterraform validate/fmt, realnode --checkon generated SST/CloudFront-Function code3 rounds of external review on the recommendation engine, with fixes verified after each round
Fresh-agent Discover replay from the committed fixture seeds (capture pre-done, zero network/build/token): route dispositions incl. the ISR cross-check and LOW-confidence Route Handlers, 10/10 preflight checks, names-only env capture —
python3 fixtures/vercel-capture/check_expected_discovery.py <run-dir>→ PASSfixtures:check(from ci(migrate): fixtures integrity check + pricing-cache staleness report #151) green against this branch's fixture setOut of scope (v1, documented in the spec)
Full cost estimation parity with the GCP skill's Estimate phase, a full "what Vercel provisions vs. what OpenNext provisions" infra diff, and Cloudflare/VPS migration paths (acknowledged in the report's honesty paragraph, never built).