Skip to content

feat(vercel-to-aws): add estimate phase + promote scaffold to full generate phase - #144

Closed
herosjourney wants to merge 25 commits into
awslabs:mainfrom
herosjourney:feat/vercel-to-aws-generate-phase
Closed

feat(vercel-to-aws): add estimate phase + promote scaffold to full generate phase#144
herosjourney wants to merge 25 commits into
awslabs:mainfrom
herosjourney:feat/vercel-to-aws-generate-phase

Conversation

@herosjourney

@herosjourney herosjourney commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a mandatory estimate phase and promotes the optional scaffold checkpoint into a mandatory generate phase producing production-ready Terraform — bringing vercel-to-aws to parity with the GCP and Heroku skills.
  • New backbone: prescan → discover → clarify → recommend → estimate → generate → report → complete (was: report after recommend, plus an optional scaffold checkpoint).

Motivation: the skill (introduced in #141) ran an honest assessment but stopped at a thin scaffold — founders got a report but no infrastructure to apply. The GCP and Heroku skills both produce full Terraform, migration scripts, and documentation.

Changes

Estimate phase (3 files)

  • Three-tier AWS cost projection (Premium/Balanced/Optimized) vs. current Vercel spend; pricing hierarchy cached → live MCP → fallback; complexity tier via shared complexity-tiers.json; financial recommendation path; Property-16 arithmetic invariant.
  • Unresolved [A, B] tiebreak handling (added during review): estimate is non-interactive, so it prices BOTH candidate outcomes — projected_costs = Outcome A, a tiebreak_alternative section = Outcome B with its own Property-16 check, and mandatory tiebreak honesty in the summary. The A-vs-B cost delta is itself a resolving input for the founder's pick at Generate.

Generate phase (10 files, promoted from scaffold)

  • baseline.tf always emitted (GuardDuty, CloudTrail, IMDSv2, EBS encryption, Access Analyzer, budgets, remote state, compliance-conditional Config/SecurityHub); full VPC; outcome-specific compute (SST/OpenNext for A, Fargate+ALB+CloudFront for B, API GW+Lambda / Fargate backend for C); peripheral Terraform (RDS/ElastiCache/S3/EventBridge); numbered migration scripts with dry-run defaults; MIGRATION_GUIDE.md; generation-warnings.json.
  • Best-effort terraform validate in the assembler (added during review; parity with heroku/gcp): static HCL checks, then terraform init -backend=false && terraform validate when the binary exists — failures/skips land in generation-warnings.json, never a halt.

Clarify extension

  • Q6 (Vercel spend) → estimate baseline; Q7 (database size) → confirms the discovery-measured band instead of asking cold when sizeBytes was captured (added during review — which also caught a "Postgres" vs "postgres" case mismatch in five files that would have silently skipped Q7, the DB migration script, and the has_databases complexity input); Q8 (compliance) → baseline.tf conditional section.

Report, SKILL.md, vendoring

  • Report repositioned after generate with cost-comparison + artifacts-generated sections; validator updated. Vendored shared files registered; scaffold files deleted; READMEs updated.

Key design decisions

  1. Report AFTER generate (references actual artifacts + costs). 2. No separate design phase — the Recommendation Engine outcome IS the design. 3. SST exception survives for Outcome A's app surface. 4. Outcome "stay" still generates baseline + peripherals. 5. Vercel cost baseline is best-effort and labeled honestly.

Relationship to other PRs

Test plan

  • Frontmatter validator OK; vendored byte-identity OK; pytest (report validator) 44/44; node test suite 57/57; markdownlint clean
  • Fixtures: seed-estimate/ and seed-generate/ harvested from a validated discover→clarify→recommend fresh-agent chain replay (discover asserter PASS)
  • Fresh-agent Estimate replay: $600 user-provided baseline (Q6 midpoint), both tiebreak paths priced, Property-16 on each — check_expected_estimate.py PASS
  • Fresh-agent Generate replay (founder picks Outcome A at the documented tiebreak ask): full artifact set, OpenNext/Fargate mutual exclusion, no placeholder tokens, terraform init -backend=false && terraform validate genuinely passed — check_expected_generate.py PASS
  • Fresh-agent discover→clarify replay validating the Q7 confirm-first flow (Q7 presented as a confirmation of band B from the ~3 GB store)
  • Full mise run build green

Type of Change

  • Enhancement to existing plugin/power/tool
  • New plugin/power/tool
  • Bug fix
  • Documentation update
  • Infrastructure/CI change

Logan Kleier added 3 commits July 14, 2026 17:20
…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.
@herosjourney
herosjourney requested review from a team as code owners July 15, 2026 19:45
Logan Kleier added 4 commits July 15, 2026 12:52
…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.
Logan Kleier added 6 commits July 18, 2026 23:11
…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.
Logan Kleier added 9 commits July 19, 2026 10:32
…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.
@herosjourney

Copy link
Copy Markdown
Contributor Author

Review follow-ups pushed (dfe017b, c8f8996, plus merges of the #141 branch): Estimate and Generate are now replay-validated like Discover, and the generated Terraform gets a best-effort terraform validate.

1. terraform validate parity (dfe017b). heroku and gcp generate phases both validate their Terraform; vercel's had static postconditions only. New assembler Step 6: static HCL checks (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, same degraded-offline convention as the other skills.

2. Estimate/Generate fixtures + asserters (c8f8996). seed-estimate/ and seed-generate/ are harvested from a validated fresh-agent chain replay (discover → clarify → recommend with documented-default answers; discover asserter PASS), so a phase can be replayed in isolation from committed, provenance-known seeds. check_expected_estimate.py pins the $600 user-provided baseline (Q6 "$200-1000" midpoint), tier positivity, Property-16, comparison arithmetic, peripheral coverage, and secret hygiene. check_expected_generate.py pins the artifact set, OpenNext/Fargate mutual exclusion, the ~> 5.80 provider pin, the no-placeholder scan, compliance-none baseline content, estimated-service coverage across terraform/scripts/warnings, and the Step 6 validate contract. Both asserters PASS against fresh-agent replays — and the generate replay's terraform init -backend=false && terraform validate genuinely passed (local provider cache), so Step 6's happy path is exercised too, not just the skip path.

3. Spec gap found and fixed while seeding: the [A, B] tiebreak reached Estimate undefined. Recommend legitimately produces the unresolved tiebreak array, and generate.md explicitly asks the founder to pick — but the estimate cost engine had no handling at all (it "reads recommendation.json.outcome to determine which services to price"). Estimate is _interactive: false, so it can't ask. It now prices both candidate outcomes: projected_costs (and everything downstream) reflects Outcome A, a new tiebreak_alternative section prices Outcome B with its own Property-16 check, and financial_summary/path_label must state the unresolved tiebreak with both balanced totals. The A-vs-B cost delta is itself a resolving input for the pick at Generate — in the replay: A ≈ $121/mo vs B ≈ $174/mo vs Vercel ~$600/mo.

Also folded in via the #141 merges (committed on that branch since they're Discover-side): the three capture/build/ manifest fixtures the capture manifest referenced but that were never committed (a root .gitignore build/ rule silently dropped them — scoped negation added), and a discover-manifests.md clarification the replay surfaced (routes-manifest arrays classify path shape, not rendering; prerender-manifest membership decides static/isr/dynamic — the replay had classified /dashboard as static under the old wording).

Replay observations left as-is (pre-existing, noted for the record): the pricing hierarchy has no "estimated" row though the schema and assembler template carry an estimated bucket (Lambda rates live only in the cost engine's formula constants; ECR has neither cache entry nor formula); generate-docs.md's step template assumes contiguous script numbering (04 is Outcome-B-only) and has no slot for the terraform-apply/sst-deploy provisioning step; SKILL.md lists estimate as an assessment-state writer but no estimate file specifies a write.

…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.
@herosjourney

Copy link
Copy Markdown
Contributor Author

Pushed 8cd312e — a replay-validated cleanup batch closing out the recorded observations: - Q7 now confirms instead of asking cold. When Discover captured the postgres store's sizeBytes, Clarify derives the band (decimal GB, sizeBytes / 1e9) and presents it for confirmation — one less cold question per run; a founder correction wins as Tier 3 input. The phase postcondition now states the rule precisely (no COLD question whose answer discovery determined; confirmation is the sanctioned form). Validated by a fresh-agent discover→clarify replay: Q7 arrived as a confirmation of band B from the ~3 GB store, both phases HANDOFF_OK. - Real bug folded back from that replay: a case mismatch. Five files matched peripherals[] against "Postgres" while the discover schema emits lowercase "postgres" — a literal string match would wrongly skip Q7, the database migration script (generate-scripts.md), and the has_databases complexity input. All five now use the schema's lowercase value with case-insensitive matching. - Pricing hierarchy row 4 estimated — the documented home of the services_by_source.estimated bucket the schema/assembler already carry (Lambda's formula constants are the canonical example). Mirrors the same change on #150 for heroku. - generate-docs: guide step numbers count the scripts that exist (the file set is conditional, so numbering gaps are normal and must be called out), plus a Provision Infrastructure slot between scripts 01 and 02 (terraform apply + sst deploy) — both were improvised correctly by the generate replay agent and are now spec. - SKILL.md: estimate/generate marked as read-only consumers of assessment-state.json; clarify.md: repaired the malformed declined-answer error-table row. Also merged the #141 branch (its scaffold seed fix; conflict on the seed .phase-status.json resolved toward this branch's estimate/generate phase set — verified by the new fixtures-check tool from #151).

icarthick added a commit that referenced this pull request Jul 20, 2026
* feat(migrate): add vercel-to-aws migration assessment skill Adds a new 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).

* chore: remove .kiro/specs/vercel-to-aws (local working scratch, not shipped) 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.

* feat(vercel-to-aws): add estimate phase + promote scaffold to full generate 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.

* fix(vercel-to-aws): complete generate-phase integration (SKILL.md, report, validator, READMEs, scaffold deletion)

* fix(vercel-to-aws): capture/parse split — align prescan/discover with 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, #148/#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.

* fix(vercel-to-aws): verify capture whitelist against Vercel REST API 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

* fix(vercel-to-aws): sync fixture manifest endpoints with the doc-verified 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.

* chore(vercel-to-aws): sync vendored aws-infra-pricing.json from canonical The merge from main brought the July 2026 canonical pricing update; the vercel skill's vendored copy was stale and failing shared:check.

* style: dprint formatting after merge

* fix(vercel-to-aws): constrain the OpenAPI-discovered capture rows Review 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.

* feat(vercel-to-aws): best-effort terraform validate in the Generate assembler 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.

* fix(vercel-to-aws): commit the missing capture/build manifest fixtures 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.

* fix(vercel-to-aws): un-ignore the fixture capture/build manifests The 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.

* fix(vercel-to-aws): routes-manifest arrays classify path shape, not rendering 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.

* test(vercel-to-aws): estimate+generate replay fixtures; price both tiebreak 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.

* fix(vercel-to-aws): seed phase list includes the declared scaffold phase 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.)

* fix(vercel-to-aws): Q7 confirms the discovered DB size; estimated rung; 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 #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.

* feat(vercel-to-aws): add post-Estimate what-if workshop checkpoint

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 (#144).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(vercel-to-aws): resume must not recompute Estimate mid-workshop

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>

* feat(vercel-to-aws): render what-if scenarios in assessment report

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>

* docs(vercel-to-aws): list scenarios/ in generated README artifacts

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(vercel-to-aws): rename assessment-report.html to migration-report.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>

* fix(vercel-to-aws): make the workshop branch pass mise build Pre-existing 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.

* feat(vercel-to-aws): shareable calculator.aws link per workshop scenario 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.

---------

Co-authored-by: Logan Kleier <lkleier@amazon.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Karthik Balasubramanian <carthick@amazon.com>
@icarthick

Copy link
Copy Markdown
Collaborator

This PR's work is fully on main#153 (merged 46d2472) was built on this branch and carried the estimate + generate phases with it. Verified: every vercel-to-aws file this PR adds is already present on main (comm shows zero missing files), and the scaffold → generate promotion + breaking-change note are live.

Closing as superseded — the estimate/generate phases and the workshop that sits on top of them are all merged. Nothing here is unmerged. Thanks!

@icarthick icarthick closed this Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants