feat(vercel-to-aws): post-Estimate what-if workshop checkpoint - #153
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.
…nerate phase Add a mandatory estimate phase (three-tier AWS cost projection vs. Vercel spend) and promote the optional scaffold checkpoint into a mandatory generate phase producing production-ready Terraform, including: - baseline.tf (GuardDuty, CloudTrail, IMDSv2, EBS encryption, budget alerts, Access Analyzer, remote-state backend, compliance-conditional Config/SecurityHub) - Full VPC with public/private subnets, NAT gateway, route tables - Outcome-specific compute: SST/OpenNext (A), Fargate+ALB+CloudFront (B), API Gateway+Lambda (C-A), Fargate backend (C-B) - Peripheral Terraform: RDS (Postgres), ElastiCache (KV), S3 (Blob), EventBridge (Cron) - Numbered migration scripts (01-06) with dry-run defaults - MIGRATION_GUIDE.md with phased timeline, verification, rollback, Go/No-Go gates - generation-warnings.json for unpriced/unmapped services Backbone changes: prescan -> discover -> clarify -> recommend -> estimate -> generate -> report -> complete (was: prescan -> discover -> clarify -> recommend -> report -> complete + scaffold checkpoint) Clarify extended with Q6 (Vercel spend), Q7 (database size), Q8 (compliance) to feed the estimate and generate phases. Vendored shared files: aws-infra-pricing.json, estimation-infra.schema.json, complexity-tiers.json — registered in sync-vendored-shared.ts auto-discovery. Breaking change: existing .phase-status.json files from the old backbone are not compatible; re-run from prescan.
…port, validator, READMEs, scaffold deletion)
…nerate-phase # 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
…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.
…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.
…-aws-generate-phase # Conflicts: # .claude-plugin/marketplace.json # migrate/README.md # 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
…ical The merge from main brought the July 2026 canonical pricing update; the vercel skill's vendored copy was stale and failing shared:check.
…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.
…-aws-generate-phase (rows 6-7 discovery constraints)
…ssembler Review follow-up + parity fix: heroku (generate-terraform.md Step 12) and gcp (validation-report degraded-offline convention) both validate their generated Terraform; vercel's generate had static postconditions only. New assembler Step 6: static HCL checks (syntax, reference integrity, variable completeness, output references), then terraform init -backend=false && terraform validate when the binary is available. Failures and skipped runs land in generation-warnings.json with a founder-facing action line -- never a generation halt, matching the degraded-offline convention of the other two skills.
…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.
…ebreak paths Review follow-up: Discover had replay fixtures and an asserter; Estimate and Generate were spec-only. Now the whole pipeline is machine-checkable. Spec gap found while seeding: estimate had no handling for the unresolved [A,B] tiebreak that recommend legitimately produces and generate explicitly asks the founder about. Estimate is non-interactive, so it now prices BOTH candidate outcomes: projected_costs (and everything downstream) = Outcome A, a tiebreak_alternative section = Outcome B with its own Property-16 check, and mandatory tiebreak honesty in financial_summary/path_label (both balanced totals + how it gets resolved). The A-vs-B cost delta is itself a resolving input. Fixtures: seed-estimate/ and seed-generate/ are harvested from a validated fresh-agent chain replay (discover->clarify->recommend under documented-default answers, Q6 spend $200-1000, genuine [A,B] tiebreak; discover asserter PASS). expected-estimate.json pins the $600 midpoint baseline, both-path pricing, and comparison arithmetic; expected-generate.json pins the artifact set, OpenNext/Fargate mutual exclusion, provider pin, no-placeholder scan, compliance-none baseline, estimated-service coverage, and the Step 6 validate contract (pass = no warning; fail/skip = exactly one entry with a founder action). Both asserters PASS against fresh-agent replays of their phases (the generate replay's terraform init -backend=false + validate genuinely passed via a local provider cache). seed/.phase-status.json gains the estimate/generate phases this branch declares.
…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.)
…; seed keeps this branch's estimate/generate phase set
…g; docs/spec cleanups Review-observation batch, replay-validated: - Q7 confirm-first: when discovery captured the postgres store's sizeBytes, Clarify derives the band (decimal GB) and presents it for founder confirmation instead of asking cold -- one less question per run, and the founder's correction wins when given. The phase postcondition now states the rule precisely: no COLD question whose answer discovery determined; confirmations are the sanctioned form. Validated by a fresh-agent discover->clarify replay (Q7 presented as a confirmation of band B from the ~3 GB store; HANDOFF_OK both phases). - Case fix folded back from that replay: five files matched peripherals[] against 'Postgres' while the discover schema emits lowercase 'postgres' -- a literal match would wrongly skip Q7, the DB migration script, and the has_databases complexity input. All now lowercase + case-insensitive. - Pricing hierarchy row 4 'estimated' (mirrors the heroku change on awslabs#150): the documented home of the services_by_source.estimated bucket; Lambda's formula constants are the canonical example. - generate-docs: guide step numbers count existing scripts (the file set is conditional, so numbering gaps are normal and must be called out), and a Provision Infrastructure slot between scripts 01 and 02 (terraform apply + sst deploy) -- both folded back from the generate replay. - SKILL.md: estimate/generate are read-only consumers of assessment-state.json (no phase file specifies a write; none should be improvised). - clarify.md: repaired the malformed declined-answer error-table row.
Freeze discovery/coupling/preflight and reprice traffic/outcome/region/arch via Recommend+Estimate inner runs with scenario snapshots. Outcome overrides use fired_rule workshop_override (declared contract) with transcript workshop_note provenance. Stacked on the generate-phase skill (awslabs#144). Co-authored-by: Cursor <cursoragent@cursor.com>
Port INTERPRETER deferred-advance checkpoint resume and vercel SKILL workshop resume rule so warm starts never re-run Estimate while workshop is unresolved. Co-authored-by: Cursor <cursoragent@cursor.com>
Fresh-agent replay + resume fixResume rule (
|
Add conditional what-if-scenarios section (workshop compare table) and enforce it in validate-assessment-report.py when scenarios/index.json has ≥2 entries. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Stakeholder deliverable: what-if section in assessment report
Commit: |
….html Unify the stakeholder HTML filename with GCP and Heroku. Validator script name unchanged (validate-assessment-report.py) to avoid colliding with GCP's validate-migration-report.py. Co-authored-by: Cursor <cursoragent@cursor.com>
Filename unifyVercel Report now writes Validator remains |
…ting CI failures on this branch, all fixed: (1) eight files committed with dprint drift (fmt:check); (2) markdownlint MD025/MD029/ MD031 — the fixtures README second H1, report-render's scenario table breaking an ordered list, workshop-assemble's fenced block splitting a list, workshop-refresh's unspaced fences; (3) shared:check — the workshop schema additions (estimation-infra workshop metadata) and the Fargate/EC2 arm64 rates were edited in the VENDORED copies instead of the canonical skills/shared files. Ported vendored -> canonical and ran shared:sync (heroku's vendored copies pick up the same additive changes). mise run build now exits 0.
|
Verified + fixed on this branch (64176ca): fixtures-check and the workshop asserter pass, and the scenario render spec correctly sources display columns from per-scenario copies. Branch now passes |
…rio workshop-refresh 6b: after each snapshot, best-effort build_estimate via the aws-pricing-calculator MCP -> shareable calculator.aws URL stored as estimation_summary.calculator_url (PRIMARY outcome's Balanced services; tiebreak_alternative skipped; null + one chat note on failure; never blocks). Compare table and report scenario section render the link. AWS computes regional prices server-side when opened -- the honest region answer. Server registration (.mcp.json) rides the heroku workshop branch; degradation here is silent-null when unconfigured.
|
Pushed 6d7e7d2 — shareable calculator.aws link per workshop scenario (refresh step 6b: best-effort |
Resolves README conflicts by UNIONING both sides: kept main's newer content (agent-advisor sections from awslabs#147, the awslabs#148 live-Heroku-discovery corrections, tf-best-practices) AND re-applied vercel-to-aws's unique additions (intro block, assessment triggers, Vercel to AWS mapping tables, trigger-table row, requirement, and the DSL-architecture resumability-ledger note). dprint-formatted. Also fixes two pre-existing CI failures on the branch: - workshop fragment _ids (workshop-sheet/-refresh/-compare) now match workshop.md's declared _ids (sheet/refresh/compare) per the house convention (frontmatter validator). - shared:sync'd vercel's vendored estimate schema + pricing to canonical (picks up awslabs#150's cached_stale enum + arm64 rates that landed on main).
awslabs#159 changed canonical INTERPRETER.md (_check_phase_completed honors skill-declared resolved statuses); shared:sync propagated it to all vendored copies. shared:check + both DSL validators + full build green.
Union-resolve fixtures/README.md registry: keep both gcp-workshop (this branch) and vercel-workshop (awslabs#153, now on main) entries. Full build green.
Brings the branch up to current main (through awslabs#153/awslabs#154/awslabs#159). Resolutions: - fixtures/README.md: union — keep heroku-workshop + vercel-workshop registry lines. - estimation-infra.schema.json (canonical + vendored): took main's generic workshop- metadata description (the 'heroku-to-aws pilot' note is stale now that vercel/gcp share the field); shared:sync keeps copies byte-identical. Full build green.
…ode, vendored Merge-last PR: built from awslabs#152 + awslabs#153 + awslabs#154 (plus awslabs#148 via awslabs#152's lineage). After those merge, three near-identical copies of the workshop contract and the estimate Step 0 pricing-mode block would exist -- the exact drift pattern that produced the cached_stale enum bug and the vendored-edited-instead-of-canonical arm64 rates. - skills/shared/workshop/workshop-invariants.md (canonical): frozen discovery + fingerprint, checkpoint state semantics incl. the warm-start rule, the inner-runs allowed/forbidden contract, scenario store rules (max-5 warn-and-name eviction, working-tree==active, estimation_summary core fields), region honesty, and the calculator link procedure. Vendored into all three skills; gcp gains a references/vendored tree (now 4 skills under shared:check). - skills/shared/estimate/pricing-mode.md (canonical Step 0): cache staleness, MCP retry ladder, display modes, and the 5-rung pricing hierarchy incl. the estimated rung. heroku + vercel cost engines now execute the vendored copy as Step 0 instead of restating it; vercel keeps its skill-specific Step 0a-workshop knobs section. - Skill workshop files keep knobs/artifact names/engine refreshes and defer to the invariants file for every invariant (workshop.md hard rules note the canonical wins on disagreement; refresh inner-runs and 6b calculator sections are pointers with skill-specific lines only). - READMEs unioned across the three branches; canonical estimation-infra schema takes the workshop-metadata side + shared:sync. Validated: shared:check green across 4 vendored trees, fixtures-check green (123 json / 8 asserters / 10 seeds), all three workshop asserters PASS on the merged tree, frontmatter validation green, full mise build green.
…ode, vendored Merge-last PR: built from #152 + #153 + #154 (plus #148 via #152's lineage). After those merge, three near-identical copies of the workshop contract and the estimate Step 0 pricing-mode block would exist -- the exact drift pattern that produced the cached_stale enum bug and the vendored-edited-instead-of-canonical arm64 rates. - skills/shared/workshop/workshop-invariants.md (canonical): frozen discovery + fingerprint, checkpoint state semantics incl. the warm-start rule, the inner-runs allowed/forbidden contract, scenario store rules (max-5 warn-and-name eviction, working-tree==active, estimation_summary core fields), region honesty, and the calculator link procedure. Vendored into all three skills; gcp gains a references/vendored tree (now 4 skills under shared:check). - skills/shared/estimate/pricing-mode.md (canonical Step 0): cache staleness, MCP retry ladder, display modes, and the 5-rung pricing hierarchy incl. the estimated rung. heroku + vercel cost engines now execute the vendored copy as Step 0 instead of restating it; vercel keeps its skill-specific Step 0a-workshop knobs section. - Skill workshop files keep knobs/artifact names/engine refreshes and defer to the invariants file for every invariant (workshop.md hard rules note the canonical wins on disagreement; refresh inner-runs and 6b calculator sections are pointers with skill-specific lines only). - READMEs unioned across the three branches; canonical estimation-infra schema takes the workshop-metadata side + shared:sync. Validated: shared:check green across 4 vendored trees, fixtures-check green (123 json / 8 asserters / 10 seeds), all three workshop asserters PASS on the merged tree, frontmatter validation green, full mise build green. (#157) Co-authored-by: Logan Kleier <lkleier@amazon.com>
Per product decision, remove the vercel-to-aws migration skill entirely. This is a forward-removal (not a git-revert): vercel landed via shared/cross-skill commits (#153/#157/#162/#155/#152/#176) that also carried heroku/gcp/sidebar work, so reverting those merge commits was not viable. Removed: - skills/vercel-to-aws/ (full skill, 57 files incl. its references/vendored/ copies) - fixtures/vercel-capture/ + fixtures/vercel-workshop/ (replay fixture sets) - Orphaned vercel report validator + its test + golden fixtures (lived at plugin level, not under the skill dir): scripts/validate-assessment-report.py, tests/test_validate_assessment_report.py, fixtures/assessment-report-{reference,stub}.html, fixtures/tier1-signals-reference.json (vercel prescan fixture, no consumers) - Deregistered from all 3 plugin manifests (.claude/.codex/.cursor-plugin): dropped vercel/next-js/nextjs/opennext keywords, the 'Vercel' mentions in descriptions, and the codex 'Assess my Vercel migration' example prompt - Stripped vercel from both READMEs (intro bullets, triggers, Vercel→AWS mapping table, trigger-table row, requirement bullet, DSL-architecture section: 'five skills'→'four', removed the resumability-ledger note) + fixtures/README registry line - Dropped vercel asserter entries from tools/run-asserters.py (10→6 asserters) - Stripped stale vercel cross-refs: heroku SKILL.md workshop-arch note, gcp schema-workshop-scenarios arch-defaults line, canonical workshop-invariants {SKILL_LABEL}/{INVENTORY} placeholders (shared:sync propagated to vendored copies) PRESERVED (heroku + gcp depend on these — #157's shared architecture): canonical skills/shared/workshop/workshop-invariants.md + skills/shared/estimate/pricing-mode.md and their heroku/gcp vendored copies. shared:check drops 4→3 trees cleanly (the sync tool auto-discovers consumers). Left legit non-skill refs: gcp's vercel.json detection signal, 'Vercel AI SDK' framework name, historical PR-context notes. Verified: shared:check OK (3 trees byte-identical), fixtures:assert 6/6, all validators green (heroku/gcp/agent-advisor), dprint + markdownlint clean, full mise run build green.
Summary
vercel-to-aws: freeze discovery, edit traffic/outcome/region/Multi-AZ/arch knobs, refresh Recommend (or record an explicitfired_rule: workshop_override) + Estimate, snapshot up to 5 scenarios, compare side-by-side.workshop_notepreserves the founder's original answer; recommend reasons prefix knob-sourced signals with "workshop assumption:").separableand requires a backend-shape pick, so Generate is never handed an undispatchable recommendation.migration-report.html(cross-skill consistency) and renders the scenario table in it; per-scenario shareable calculator.aws links (best-effort, never blocks).Motivation: same as #152 — Transform-style what-if for a live SA workshop, without re-running discovery. Vercel's twist is that the traffic knob legitimately changes the recommendation (A vs B), so the refresh re-runs the precedence engine, not just the cost engine.
Relationship to other PRs
.mcp.json) rides feat(heroku-to-aws): post-Estimate what-if workshop checkpoint #152; behavior here degrades to silent-null when unconfigured.estimation-infra.schema.json(this branch predates itscached_staleenum addition) — trivial resolution either direction.Test plan
fixtures/vercel-workshop/(seed → outcome-B/x86 reprice after-state);python3 check_expected_workshop.py after-outcome-b-x86→ PASSfixtures:checkgreen on the branch (seeds carry the full declared phase set)mise run buildgreen (branch previously failed fmt:check/markdownlint/shared:check — fixed here, including moving the arm64 rates + workshop schema additions from vendored copies into canonicalskills/shared)Made with Cursor