Skip to content

feat(gcp-to-aws): decision report — shared exec core rendered at gate A (stacked on #185) - #186

Open
herosjourney wants to merge 17 commits into
awslabs:mainfrom
herosjourney:feat/decision-report-core
Open

feat(gcp-to-aws): decision report — shared exec core rendered at gate A (stacked on #185)#186
herosjourney wants to merge 17 commits into
awslabs:mainfrom
herosjourney:feat/decision-report-core

Conversation

@herosjourney

@herosjourney herosjourney commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #185 (decision gate) — merge that first; this branch contains its commits. For this PR's isolated diff: compare feat/decision-gate...feat/decision-report-core.

This PR now carries four stacked concerns that all rewrite the same executive-section rules in the new shared renderer (report-decision-core.md), which is why they land together rather than as sequential conflicting PRs: (1) the decision report itself, (2) timeline/effort honesty, (3) scenario deltas, (4) track-scoped outcomes. Each has its own plain-English problem statement below.


1. Decision report — shared exec core rendered at gate A

The problem, in plain English: #185 gives the founder a decision moment, but the decision pack lives only in chat — close the window and the verdict, costs, and assumptions are gone. The durable report today is built inside Generate, so the only way to get a shareable artifact is to accept 20 Terraform files you didn't ask for. This gives gate choice A ("done for now") a real deliverable: decision-report.html — the thing a founder emails their co-founder — plus a Slack-friendly DECISION.md, without a second report engine to drift.

  • references/shared/report-decision-core.md (new, extracted not written): the report's entire executive summary — verdict typography (feat(gcp-to-aws): decision outcomes (go/conditional/defer/stay) + executive trust panel #173), cost comparison with the baseline-quality badge and not-comparable rules (feat(gcp-to-aws): Design summary card + cost baseline-quality labels #175), combined run rate, migration shape, risks, assumptions panel, Activate wording — moved out of generate-artifacts-report.md Step 1 into a shared renderer spec with two modes. Full-mode rules relocated unchanged; decision-mode overrides are new normative text written inline next to each affected rule. Decision mode (gate A): exec sections only, pre-Generate fallbacks, a required decision-cta section, and the DECISION.md twin. Full mode (Generate Step 1): loads the same file — Step 1 is now a pointer with a do-not-restate rule, the canonical-vendored discipline this repo adopted after the drift bug. generate-artifacts-report.md shrinks 769 → 568 lines.
  • estimate.md gate A renders the pack, validates it, then sets the decide-complete state — the closing message names both files.
  • Replace, never patch: when Execute runs after a Decide run, migration-report.html renders fresh from the JSON artifacts; splicing or extending decision-report.html is explicitly forbidden, and the artifacts catalog notes the full report supersedes it.
  • validate-migration-report.py --mode decision: required set = exec sections + decision-cta; any appendix-* section is a hard failure. Full mode's required set and REPORT_OK output format are byte-identical.
  • New golden fixture (gcp-decision-gate/after-decide-complete): locks the decide-complete terminal state from feat(gcp-to-aws): post-Estimate Decision gate — Generate becomes opt-in #185 and a validator-passing decision pack. Registered in run-asserters.py.
  • Also on this branch: report style conventions (intensifier + slash-date gates, active-voice and denominator rules), WCAG AA contrast floor + interaction discipline in the visual contract, hero-first opening and type scale.

2. Timeline/effort honesty — remove hours, demote weeks to structure + drivers (5caccbd)

The problem, in plain English: the report labels every dollar figure with its provenance and accuracy band — but presented "480–720 engineering hours" and "12–16 weeks" with no label at all, as if measured. They aren't: the hours were weeks × 40 × an assumed utilization factor, the weeks came from authored tier buckets with no calibration data, and nothing downstream computed with the hours — they existed purely for display. These are the numbers most likely to be pasted into a budget or board deck, and the least defensible in the report.

  • Effort-hours removed everywhere (generate-infra.md, generate-billing.md, generate-ai.md, report core, fixtures): no hour fields in any artifact schema, no effort metric cards. Legacy artifacts' hour fields are dropped outright — never rendered, even with a label.
  • Timelines demoted from estimates to structure: migration_plan.total_weeksduration_drivers[] + tier_bound_by; stage templates keep their order (ordinal = real information) but lose week numbers; migration-complexity.md gets a Provenance section and its Timeline/Effort tables become Approach + Duration Drivers tables. Operational time policies (24–48h watch, observation windows, maintenance windows) are explicitly kept — procedure, not estimates.
  • Preview: timeline_hint renamed duration_hint with path-shape phrasing — renamed rather than semantically aliased; report core accepts the legacy key.
  • Report core: hero thesis and metric hierarchy use migration shape ("Phased · long pole: database cutover") instead of "N weeks"; legacy artifacts carrying total_weeks render only with the visible label "Legacy planning heuristic (uncalibrated): N weeks."
  • Validator: two new readability gates — any effort-hours range, and any bare week-range not preceded by the uncalibrated label. The week gate caught this PR's own decision-report golden and a test scaffold on first run.
  • Calibration starts: the feedback decision-check gains an optional "how long did your migration actually take?" question (chat-only, not in trace.json; trace's total_weeks kept nullable with an owner note).

3. Scenario deltas — itemize what Premium/Optimized concretely change vs Balanced (80cace8)

The problem, in plain English: the tier labels answer "are these three different builds?" (no — same architecture, three pricing scenarios) but not the decision question: what would I buy at Premium, and what would I give up at Optimized? The generic subtitle "reservations, Spot, or storage trade-offs assumed" hides real architectural consequences — on a live test stack, the Optimized total silently dropped the NAT Gateway (~a third of the Balanced total), a network-posture change, not just savings. Readers quote the lowest number to their board without knowing it embeds a security change and a 1-year commitment.

  • schema-estimate-infra.md: projected_costs.scenario_deltas (required with three tiers) — per non-Balanced scenario, each concrete difference vs Balanced with its monthly delta; architectural deltas must state their operational consequence, commitment savings must name the commitment. Absent field (pre-extension artifacts) → column omitted, never invented.
  • estimate-infra.md: the engine records the deltas while computing the tier totals (it already priced them); rule: never present an Optimized total whose embedded architectural changes are not itemized. Present Summary tier table lists them.
  • report-decision-core.md: tier table gains a "vs Balanced, for this stack" column rendered verbatim from the artifact, with an explicit guard against expanding generic tier definitions instead.
  • Fixtures: estimation-infra-reference.json models the field; migration-report-reference.html renders the column; the decision golden deliberately exercises the absent-field path.

4. Track-scoped outcomes — a failing secondary track demotes the track, never the stack (66567cc)

The problem, in plain English: stay_if is a flat, stack-scoped list, but a hybrid migration has separable tracks — compute+DB, AI text, AI image, analytics — with different answers. A real report render surfaced the failure: "Stay if: Imagen quality cannot be matched after the Stability AI evaluation" presented a single AI workload's quality eval as a reason to keep Postgres and compute on GCP. The plugin already rejects exactly this pattern for BigQuery ("deferral is not a reason to stay unless analytics must cut over in the same window") but never generalized the principle.

  • schema-estimate-infra.md: recommendation.track_outcomes[] ({track, outcome, note}, same outcome enum; required for multi-track stacks) plus a scope rule — whole-stack stay_if is reserved for stay-entirely factors; track-scoped factors go in conditions[] with a track prefix and must never render as stack-level stay reasons.
  • estimate-infra.md: triggers apply per track; a failing/unproven secondary track demotes that track only (e.g. failed image eval → that track keeps the current provider via the adapter; compute+DB stay go). The stack outcome must not be worse than the compute/database track's outcome on account of a secondary track's condition. Chat summary gains the per-track line.
  • report-decision-core.md: required per-track disposition line under the verdict when track_outcomes exists; silent omission for single-track and pre-extension artifacts.
  • Fixtures: reference JSON and report model the field and the line; the decision golden exercises the absent-field path.

Why

Completes the Decide/Execute split designed with #185 — one pipeline, two exit ramps, each with a durable artifact — and applies the same provenance discipline dollars already had to the two remaining unlabeled number families (time/effort, tier deltas). The extraction approach (shared core, two render depths) was chosen over a sibling generate-decision-report.md specifically to avoid two copies of the #175 rules drifting apart; concerns 2 and 3 then land here because they rewrite the extracted rules.

Test plan

  • dprint check — clean
  • mise run lint:md — 0 errors (806 files); lint:frontmatter — OK (both skills); shared:check — OK, 3 trees
  • pytest tests/ — 51 passed (36 pre-existing + 4 decision-mode + validator-gate updates; includes reference-fixture-passes and stub-fails against the extended validator)
  • run-asserters.py — PASS, 7 asserters (decide-complete golden + both workshop goldens)
  • New validator gates verified live: the bare-week-range gate caught the decision golden and a test scaffold before they were updated
  • Acceptance greps: zero effort_hours / total_weeks outside legacy-handling text; zero bare hour/week ranges in any fixture HTML
  • End-to-end render check: a full-mode report built from a real run's artifacts (gcp stack with BigQuery deferral, inventory-only baseline) passes REPORT_OK | sections=10/10 under the extended validator, including the not-comparable baseline callout and the assumptions panel
  • Manual: run to Estimate, choose A → decision-report.html + DECISION.md written, validator passes in decision mode, state lands decide-complete
  • Manual: after A, say "generate the Terraform" → full migration-report.html rendered fresh (decision report untouched), full-mode validator passes

Out of scope

Review note

Reviewers who prefer this split: concerns 2–4 are cleanly separable as commits (5caccbd, 80cace8, 66567cc) stacked on the report-core extraction — say the word and we'll split. All three edit the same tier-table/exec-section rules in report-decision-core.md, which is why they stack here.

Revision history

  • 4ff253127aa5dd — decision report core + style/visual/a11y conventions (review rounds 1–2)
  • 5caccbd — timeline/effort honesty (drafted, reviewed by a second model, amended: verdict formula, duration_hint rename, tiered validator gates, labeled legacy fallback)
  • 80cace8 — scenario deltas
  • 66567cc — track-scoped outcomes (user-reported flaw in a live report render)

Logan Kleier added 3 commits July 23, 2026 22:19
… product, Generate is opt-in

- estimate.md: workshop offer becomes a 3-option Decision gate (done for
  now / what-ifs / generate). Verdict, cost (with baseline-quality label),
  timeline band, and deferred items presented from existing estimate
  artifacts. Option A ends the run decision-complete; C opts into Generate.
- SKILL.md: 'Generate is opt-in' hard rule (gate C, resume acceptance, or
  explicit Terraform request — never auto-chain); generate state-machine row
  now requires consent; new decide-complete row with resume offer; ordered
  evaluation respects consent; feedback checkpoint re-anchored to fire after
  gate resolution (decision-check lands right after the actual decision).
- workshop: exits return to the Decision gate, not Generate
  (workshop-assemble, workshop.md branch + decline, sheet action label);
  canonical workshop-invariants exit rule generalized (skill may define a
  post-Estimate gate; heroku/vercel behavior unchanged) + vendored resync.
- schema-phase-status.md: optional run_mode field (decide |
  decide_and_execute); decide-complete documented as terminal-unless-asked
  (generate stays pending — no 'skipped' status).
1. State-machine workshop row: 'workshop offer' -> Decision gate (stale
   wording would regress agents to the old Enter/Proceed prompt)
2. Estimate scope boundary: carve out the gate's one-line timeline band;
   full schedules stay Generate-only
3. Every Execute path (gate C, resume Yes, explicit ask) sets
   run_mode: decide_and_execute BEFORE loading generate.md, so a death
   mid-Generate resumes as an Execute run
4. Consent guard at the top of generate.md — refuses and re-presents the
   gate when loaded cold without consent
5. Canonical invariants exit rule tightened: skills with a gate NEVER
   auto-advance to Generate (vendored resynced)
6. handoff-gates.md: decide-complete documented as a valid terminal state,
   not a failure to repair
7. Soft: after gate A, feedback folds into the closing line as a one-line
   Yes/No instead of a separate phase-like prompt
…d at gate A (stacked on decision gate)

- Extract the report's executive summary (Sections 0-8: verdict, TCO, cost
  comparison with baseline-quality/not-comparable rules, timeline, risks,
  assumptions) into references/shared/report-decision-core.md — single
  source of truth rendered in two modes: decision (gate A) and full
  (Generate Step 1 loads it; do-not-restate rule)
- estimate.md gate choice A now writes decision-report.html + DECISION.md
  from the core (no appendices, CTA footer, pre-Generate fallbacks for
  timeline/diagram), validated before presenting
- Replace-never-patch: Execute renders migration-report.html fresh from
  artifacts; never splices the decision report's HTML
- validate-migration-report.py --mode decision: exec required set +
  decision-cta, appendix-* sections forbidden; full-mode contract and
  REPORT_OK format byte-identical (4 new tests; 36 pass)
- New golden fixture gcp-decision-gate/after-decide-complete locking the
  decide-complete terminal state (current_phase complete + run_mode decide
  + generate pending) and validator-passing decision pack (7 asserters
  pass)
@herosjourney
herosjourney requested a review from a team as a code owner July 24, 2026 05:42
Logan Kleier added 2 commits July 23, 2026 22:46
…ss call

mise run build failed at security:bandit (B404/B603, low severity) on the
new fixture asserter. Applied the house pattern from run-asserters.py:
inline nosec with justification (list args, no shell, committed validator
path via sys.executable only). Full mise run build now exits 0.
…round 2)

The preamble declared decision fallbacks while the moved body still
commanded Generate-first sources — under-specified mode overrides, not a
verbatim move. Fixes:

- Inline '_Decision mode:_' overrides next to each affected rule, declared
  authoritative in the preamble: decision-summary item 4 + Section 6
  timeline (timeline_hint -> complexity band, labeled 'if you execute',
  never invent weeks, omit effort hours), Section 2b (diagram from
  aws-design.json clusters only, omit cluster_order), Section 7 risks
  (ordered decision sources: recommendation conditions/would_flip_if ->
  deferred rows -> material defaulted assumptions; never leave exec-risks
  empty when those exist)
- Decision-inappropriate Generate copy split by mode: cost footnote ('if
  you generate the execution pack...'), security teaser Appendix G
  pointers, startup-program validate script marked full-mode-only
- Anti-stub / don't-truncate-appendices prose marked full-mode-only for
  the appendix clauses
- estimate.md gate A validate command uses absolute $PLUGIN_ROOT /
  $MIGRATION_DIR paths (cwd not load-bearing)
- Golden asserter now locks content, not just structure: verdict-headline
  (awslabs#173), the not-comparable sentence (awslabs#175), and the 'if you execute'
  timeline label
@herosjourney

Copy link
Copy Markdown
Contributor Author

Round-2 fixes landed in 1ba9ffb — main ask and all four smaller items, plus the description correction:

Fallback problem (main ask): conceded fully — "under-specified mode overrides" is the right diagnosis, and the preamble-only approach was the mistake. Fixed per your suggested patch, inline next to each rule:

  • Timeline (§0 item 4 + §6): decision mode never invents weeks — timeline_hint, else the migration-complexity.md tier band, else omit; always labeled "if you execute"; effort hours explicitly omitted ("they don't exist before Generate; do not estimate them").
  • Architecture (§2b): decision mode renders from aws-design.json clusters only, cluster order omitted; Source line split by mode.
  • Top risks (§7): your exact source ladder — recommendation conditions/would_flip_if → deferred-specialist rows → material chosen_by: "default" assumptions with cost/HA impact — with "never leave exec-risks empty when those exist." The fixture's risks are now law, not lore.
  • Preamble rewritten to declare the inline overrides authoritative instead of legislating from a distance.

Smaller items: (1) cost footnote, security teaser, and the startup-program script line split by mode ("If you generate the execution pack, its Terraform will align with the Balanced scenario…"); (2) anti-stub and don't-truncate-appendices prose marked full-mode-only for the appendix clauses; (3) golden asserter upgraded from state-lock to content-lock — asserts verdict-headline (#173), the not-comparable sentence (#175), and the "if you execute" label; (4) estimate.md gate A validate command now uses absolute $PLUGIN_ROOT/$MIGRATION_DIR paths.

"Verbatim" overclaim: agreed and corrected in the PR description — full-mode rules relocated unchanged; decision-mode overrides are new normative text, now inline per section.

Re-verified: full mise run build exit 0 (including the bandit fix from adbe9a5), 36 pytest, 7 asserters (decide-complete golden now content-checking), vendored trees in sync.

Logan Kleier and others added 4 commits July 24, 2026 00:02
Make the richer card-based shell a validated contract so full and decision reports cannot regress to sparse, unstructured output.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep recommendations and supported savings visually distinct, and attach the official Activate application link directly to the executive benefit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Name recurring cloud charges accurately, enforce a scannable glossary, and prevent AI coexistence from inflating infrastructure effort.

Co-authored-by: Cursor <cursoragent@cursor.com>
… metric hierarchy, data-first, risk matrix, nav aids

Validated on a real generated report (previewed against run 0724-0602,
user-approved) before speccing. Re-applied on top of the visual-contract
commits (f4bc5bf..e1a7758) that landed on this branch in parallel; three
collisions resolved in favor of the approved preview:

- Tables: horizontal rules only (never vertical cell borders — grid +
  zebra + hover is three redundant separation cues) + thead 2px anchor;
  new td.num/th.num right-align + tabular-nums, REQUIRED on every
  currency/hours/weeks/percentage column (readability convention 11)
- Metric grid: .metric-hero for the 1-2 primary decision metrics (run
  rate, timeline) so the reader never ranks the numbers themselves;
  max ~5 cards; Activate is NEVER a metric card (call-to-action, not a
  measurement) — always the callout, apply link inside (supersedes the
  parallel commits' Activate-card guidance; keeps their link-required
  validator check satisfied via the callout inside decision-summary)
- Data first, explanation adjacent (convention 10): tier reading guide
  becomes details.reading-guide AFTER the table; mandatory not-comparable
  caveat renders as a .chip-warn pill on the GCP metric, not a paragraph
  between heading and numbers
- Corrections in disclosures (convention 12): corrected value + one
  clause visible; how-we-got-here narrative in details.why
- Top Risks: Impact / Likelihood as separate badge columns, never
  'Critical impact (low probability)' prose
- Nav aids: nav.toc id=toc + per-h2 'up contents' toplink (print-hidden)
- Golden fixture demonstrates all conventions it locks (42 tests,
  7 asserters, full mise build exit 0)
@herosjourney

Copy link
Copy Markdown
Contributor Author

Added: report readability conventions (3bd3292) — previewed against a real generated report (run 0724-0602) and user-approved before speccing. Seven conventions, all applying to both render modes via the shared core:

  1. Numeric alignmenttd.num/th.num (right-align + tabular-nums) required on every currency/hours/weeks/percentage column (new readability convention 11)
  2. Metric hierarchy.metric-hero for the 1–2 primary decision metrics (run rate, timeline); max ~5 cards; the reader shouldn't have to rank the numbers
  3. Data first, explanation adjacent (convention 10) — the tier reading guide becomes a details.reading-guide after the table; the mandatory not-comparable caveat renders as a .chip-warn pill on the GCP metric card instead of a paragraph between the heading and the numbers
  4. Risk matrix — Top Risks splits Impact/Likelihood into separate badge columns, never "Critical impact (low probability)" prose
  5. Corrections in disclosures (convention 12) — corrected figure + one clause visible; derivation narrative in details.why
  6. Nav aidsnav.toc#toc + per-<h2> "↑ contents" toplink, print-hidden
  7. Vertical cell borders explicitly banned — the CSS contract already implied horizontal-only; observed renders deviated, so it's now stated

Note for reviewers — this commit reconciles with the parallel visual-contract commits (f4bc5bf…e1a7758) that landed on this branch while it was being written. Two of their choices were superseded in favor of the user-approved preview:

  • Activate is never a metric card. The parallel commits allowed an Activate card with an embedded link; the approved design treats a credit offer as a call-to-action, not a measurement — it renders as the 💡 callout (inside decision-summary, so _validate_activate_link still passes) with the apply link inside. The metric-grid guidance and the fixture were updated accordingly.
  • Tables went back to horizontal-only borders per the pre-existing contract; the parallel commits' "1px border" wording was ambiguous enough to permit full grids.

Everything else from those commits (visual-contract validator checks, card-based section styling, responsive rules) is preserved and the new conventions build on it. Verified: 42 tests, 7 asserters, dprint/markdownlint clean, full mise run build exit 0.

…ation principle

Extracted from Anthropic's frontend-design skill where its principles fit a
validator-enforced credibility document (its distinctiveness/aesthetic-risk
core deliberately rejected — every report should look like the same
trustworthy product):

- The hero is the thesis: page opens title -> decision-summary (verdict +
  hero metrics) -> TOC -> rest. Previously an 18-link two-column menu sat
  between the title and the verdict — the first screenful was navigation
  chrome, not the decision. Encoded in the core's TOC placement rule, the
  HTML sketch, and the decision-mode shell; fixture reordered to match.
- Deliberate five-step type scale (~1.25 ratio) documented in the CSS
  contract typography block — new elements pick the nearest step, no
  invented in-between sizes.
- 'Structure is information, not decoration' added as the governing
  principle above the readability conventions — the test future devices
  must pass, with the existing rules named as its applications.
@herosjourney

Copy link
Copy Markdown
Contributor Author

Added (4ff2531): hero-first opening + type scale + governing principle — extracted from Anthropic's frontend-design skill, taking only what fits a validator-enforced credibility document and explicitly rejecting its distinctiveness/aesthetic-risk core (every report should look like the same trustworthy product, not "a visual identity that could not be mistaken for anyone else's").

  1. "The hero is the thesis." Real finding: generated reports open with title → an 18-link two-column TOC → and only then the verdict — the reader's first screenful is navigation chrome, not the decision. New opening order (core TOC-placement rule + HTML sketch + decision-mode shell): title → decision-summary (verdict headline + hero metrics) → TOC → rest. This is the feat(gcp-to-aws): post-Estimate Decision gate — Generate becomes opt-in #185 "decision is the product" philosophy applied to the report's own layout. Fixture reordered to demonstrate it.
  2. Five-step type scale (~1.25 ratio: 2rem display → 1.85 title → 1.3 headings → 1.05 subheads → 0.88 data → 0.78 captions) documented in the CSS contract — the existing sizes already sat near this scale; now it's a rule ("pick the nearest step") instead of a coincidence.
  3. "Structure is information, not decoration" added as the governing principle above the readability conventions — the one-sentence test every future structural device must pass, with the existing rules (no decorative numbering, badges for real classifications, position for real priority) named as its applications.

Verified: 42 tests, 7 asserters, dprint/markdownlint clean, full mise run build exit 0.

… in the visual contract

Two guardrail lines (no visible change to current renders — they convert
passing-by-luck into passing-by-rule, same move as the type scale):

- Contrast floor: every text/background pair meets 4.5:1 (3:1 large
  display text), incl. muted captions and badge text on tinted
  backgrounds; never trade contrast for a softer look
- Interaction discipline: cursor:pointer + 150-300ms hover/focus
  transitions on interactive elements; entrance/scroll/ambient animations
  explicitly banned (AI-slop tell in a document; no-op in print)
@herosjourney

Copy link
Copy Markdown
Contributor Author

Added (c88d424): two guardrail lines in the visual contract, extracted from a second frontend-design skill review — the 4.5:1 WCAG AA contrast floor (3:1 for large display text; current palette passes, now it's a rule instead of a coincidence) and interaction discipline (cursor: pointer + 150–300ms hover/focus transitions on interactive elements, with an explicit ban on entrance/scroll/ambient animations — an AI-generated tell in a document and a no-op in print). No visible change to current renders; both prevent drift in future agent renders. The same review's other suggestions (Google Fonts, extreme aesthetic direction, dominant palette, motion libraries) were rejected as violations of the no-external-resources rule and the every-report-looks-like-the-same-product posture; the emoji→SVG icon suggestion was deliberately deferred by the maintainer. Verified: 42 tests, 7 asserters, dprint/markdownlint clean.

Logan Kleier added 2 commits July 24, 2026 16:08
… gates, active-voice and denominator rules

Encode writing-style rules into the report pipeline:
- Spec: five new readability conventions (13-17) — no vague
  intensifiers, ISO dates only, color never the sole signal,
  active voice with named actor, denominators on progress metrics
- Validator: enforce the two mechanically checkable rules
  (intensifiers, N/N/YYYY slash dates) as readability gates
- Tests: 4 new cases incl. discovery/recovery/every substring
  false-positive guard
@herosjourney

Copy link
Copy Markdown
Contributor Author

5caccbd adds the timeline/effort honesty work to this PR (per review with Grok — it lands here rather than on main because this branch owns the exec timeline rules in report-decision-core.md, and its decision-mode week-band fallback was one of the surfaces being removed).

What changed:

  • Effort-hours removed everywhere (generate-infra.md, generate-billing.md, generate-ai.md, report core, fixtures): no hour fields in any artifact schema, no effort metric cards, no "~200 engineering hours" lines. They were display-only (nothing computed with them), uncalibrated (weeks × 40 × guess), and the number most likely to be pasted into a budget.
  • Timelines demoted from estimates to structure: migration_plan.total_weeksduration_drivers[] + tier_bound_by; stage templates keep their order (ordinal = real information) but lose week numbers; migration-complexity.md gets a Provenance section (heuristics, not calibrated) and its Timeline/Effort tables become an Approach + Duration Drivers table. Operational time policies (24–48h watch, observation windows, maintenance windows) are explicitly kept — they're procedure, not estimates.
  • Preview: timeline_hint renamed duration_hint with path-shape phrasing ("shortest path — single model swap") —- Timelines demoted from estimates to structure: migration_plan.total_weeksduration_drivers[] + tier_bound_by; stage templates keep their order (ordinal = real information) but lose week numbers; migration-complexiks"; decision-summary item 4 and the exec-timeline section rewritten for both modes; **legacy artifacts** carrying total_weeksrender - **Preview**:timeline_hintrenamedduration_hintwith path-shape phrasing ("shortest path — single model swap") —- **Timelines demoted from estimates to structure**:migration_plan.total_weeksduration_drivers[]+tier_bound_by; stage templates keep their order (ordinal = real information) but lose week numbers; migration-complexiks"; decision-summary item 4 and the exec-timeline section rewritten for both modes; legacy artifacts carrying total_weeks render - **Preview*onal "how long did your migration actually take?" question (chat-only, not in trace.json; trace's total_weeks kept nullable for contract stability with an owner note).

Checks: dprint clean, lint:md 0 errors (806 files), frontmatter OK, shared:check OK (3 trees), 51 pytest (was 36 — includes the 4 decision-mode tests, all updated), 7 asserters PASS including the decide-complete golden. Acceptance greps: zero effort_hours/total_weeks outside legacy-handling text; zero bare hour/week ranges in any fixture HTML.

Grok's six review amendments are all in: verdict formula (approach + binding driver, no weeks/hours/bare tier), relative_effort dropped, rename-not-alias, week-range validator gate, labeled legacy fallback, expanded acceptance greps.

@herosjourney

Copy link
Copy Markdown
Contributor Author

One more fold-in (same honesty thread as the timeline commit): scenario_deltas — the tier table now itemizes what Premium and Optimized concretely change vs Balanced, per stack.

Problem: the tier labels answer "are these three different builds?" (no — same architecture, three pricing scenarios) but not the decision question: what would I buy at Premium / give up at Optimized? The generic subtitle "reservations, Spot, or storage trade-offs assumed" hides real architectural consequences — e.g. on a live test stack, the Optimized total silently drops the NAT Gateway (~a third of the Balanced total), which is a network-posture change, not just savings. Readers quote the lowest number to their board without knowing it embeds a security change and a 1-year commitment.

Changes:

  • schema-estimate-infra.md: projected_costs.scenario_deltas (required with three tiers) — per non-Balanced scenario, each concrete difference vs Balanced with its monthly delta; architectural deltas must state their operational consequence, commitment savings must name the commitment. Pre-extension artifacts tolerated (field absent → column omitted, never invented).
  • `estimate-infra.m
    Changes:
  • schema-estimate-infra.md: projected_costs.scenario_deltas (required with three tiers) — per non-Balanced scenario, each concrete differencehanges are not itemized. Present Summary tier table lists them.
  • report-decision-core.md: tier table gains a "vs Balanced, for this stack" column rendered verbatim from the artifact; Balanced cell is the anchor marker; explicit guard against expanding the generic tier definitions instead (the reading-guide already covers same-architecture semantics).
  • Fixtures: estimation-infra-reference.json models the field; migration-report-reference.html renders the column (NAT-drop example with the posture consequence). Decision golden unchanged — it exercises the absent-field path.

Checks: 51 pytest, 7 asserters PASS, dprint/lint/shared:check clean. Scope note: this is the third concern on this PR (decision report core + timeline honesty + scenario deltas) — happy to split if reviewers prefer, but all three edit the same tier-table rules in report-decision-core.md, which is why they stack here.

@herosjourney

herosjourney commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Fourth fold-in (user-reported flaw in a live report render): track-scoped outcomes.

Problem, plain English: stay_if is a flat, stack-scoped list, but a hybrid migration has separable tracks (compute+DB, AI text, AI image, analytics). A real report rendered "Stay if: Imagen quality cannot be matched" as a whole-stack stay reason — holding a Postgres migration hostage to an image-quality eval. The plugin already rejects exactly this pattern for BigQuery ("deferral is not a reason to stay unless same-window cutover is required") but never generalized it.

Changes (66567cc):

  • schema-estimate-infra.md: recommendation.track_outcomes[] ({track, outcome, note}, same outcome enum; required for multi-track stacks) + a scope rule: whole-stack stay_if is reserved for stay-entirely factors; track-scoped factors go in conditions[] with a track prefix and MUST NOT render as stack-level stay reasons.
  • estimate-infra.md Part 7: triggers apply per track; a failing or unproven secondary track demotes that track, never the stack (AI image eval fails → that track keeps the current provider via the adapter; compute+DB stay go). Stack outcome must not be worse than the compute/database track's on account of a secondary track's condition. Chat summary gains the per-track line.
  • report-decision-core.md: required per-track disposition line under the verdict when track_outcomes exists ("By track: compute+DB go; AI text go; AI image conditional — does not gate the rest; analytics deferred"); silent omission for single-track/pre-extension artifacts.
  • Fixtures: reference JSON models track_outcomes; reference report renders the track line; decision golden unchanged (absent-field path).

Checks: 51 pytest, 7 asserters PASS, dprint/lint/shared:check clean. This is concern 4 on this PR — same offer stands to split (it edits the same verdict rules in report-decision-core.md, which is why it stacks).

@leon1418 leon1418 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🤖 AI review 🤖]

Review Summary — PR #186

Scope: Four stacked concerns on the decision-report shared core: (1) report-decision-core.md extraction + decision gate rendering, (2) timeline/effort honesty (remove hours, demote weeks to structure+drivers), (3) scenario deltas (itemize tier differences), (4) track-scoped outcomes. 35 files, +1677/−555.

Empirical verification:

  • Validator: REPORT_OK | mode=decision | sections=7/7
  • Validator (full mode on reference fixture): REPORT_OK | structure=complete | sections=10/10
  • pytest: 46/46 passed ✅
  • Decision-gate asserter (check_expected_decide.py): PASS ✅
  • Acceptance greps: zero stray effort_hours/total_weeks outside legacy-handling; zero bare TCO in gcp-to-aws exec scope ✅

CI: All checks green (build, gitleaks, bandit, semgrep, checkov) as of 2026-08-04.

Design: The extraction approach (shared core, two render depths via mode flag) is the right call — it avoids two drifting copies of the #175 baseline-quality rules and #173 verdict typography, uses the same validator script for both, and keeps the canonical-vendored discipline. The "Generate is opt-in" hard rule is a genuine product improvement: the decision IS the product for many users; execution artifacts are a second explicit product.

Findings: Clean. No blocking issues. Two nits and one FYI below.

Merge recommendation: Merge #185 first (as noted), then this. No conflicts with #180#184 that aren't covered by their BEHIND/MERGEABLE status. The vendored workshop-invariants changes (3 copies) correctly use the "skills that DEFINE a decision gate" conditional wording, which makes the heroku skill future-compatible without requiring it to implement the gate now.

(
r"Rubric:",
'internal scoring trace ("Rubric:") — drop it or gate behind a '
'<details> "Why this mapping?" block',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🤖 AI review 🤖]

Nit: The bare-week-range lookbehind uses two fixed-width alternatives ((?<!uncalibrated\):\s)(?<!uncalibrated\): )) to approximate "preceded by the uncalibrated label". This handles exactly one space or one \s, but double-space or tab after the colon would be a false positive. Unlikely in practice since the label is prescribed text, but a single-pass approach like first stripping all labeled instances before scanning would be more robust.

# Alternative: pre-filter labeled instances
cleaned = re.sub(r'uncalibrated\):\s+\d+\s*(?:–|-|to)\s*\d+\+?\s*weeks', '', html)
# Then scan cleaned for bare ranges

Not blocking — the current approach works for all plausible inputs.


> ⚠️ **Specialist engagement required:** [service name] does not have an automated AWS mapping from this plugin. Engage your AWS account team and/or a data analytics migration partner to evaluate the best AWS analytics path. This does **not** block phased migration of other services; exclude [service name] from the combined estimated AWS monthly run rate until the target architecture is defined.

**Startup credits callout (decision summary / verdict):**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🤖 AI review 🤖]

Nit: The numbering under Section 0 content uses 1a. twice (Recommendation callout and Per-track disposition line both labeled 1a.). The second should be 1b. (or the current 1b. Confidence pointer becomes 1c.). This is an authoring-reference label only (not rendered), but could confuse future editors of this spec.

## What REPORT_OK means

`REPORT_OK | structure=complete` means required sections, TOC links, appendix depth, readability rules, and artifact-driven cost/TCO checks passed. It does **not** verify that every dollar figure in the HTML matches the JSON — verify numerics manually or in a future accuracy gate before executive sign-off.
`REPORT_OK | structure=complete` means required sections, TOC links, appendix depth, readability rules, and artifact-driven cloud-service run-rate checks passed. It does **not** verify that every dollar figure in the HTML matches the JSON — verify numerics manually or in a future accuracy gate before executive sign-off.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🤖 AI review 🤖]

FYI: The top-level README.md (line 40) still mentions "combined TCO" in the feature table. That's outside this PR's scope (different file, describes the whole repo), but worth a follow-up cleanup to stay consistent with the terminology change landed here.

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