AWS Builder Center
Kiro closes the last mile and finishes the migration

Kiro closes the last mile and finishes the migration

Series: Rewriting a PHP monolith to Python with AWS Transform custom + Kiro. Part 3 of 3: Kiro closing the last mile, completing the features the harness can't reach, verifying them with unit tests and a shadow run, and shipping an honest, proven migration.

Series: Monolith Modernization - Kiro+AWS Transform (3 articles)

  1. 3
    Kiro closes the last mile and finishes the migration This article
Part 1  built the yardstick: a black-box behavioral harness that proves what the app does, covering 74% of Kanboard's 104 features (77 of them). Part 2  pointed AWS Transform custom at Kanboard and, validated by that harness, drove the Python rewrite to 100% green against the harness: every layer, the whole JSON-RPC contract, all the UI flows.
But the harness measures 74% of the app. The other 27 of the 104 features are the ones it can't check over a network boundary (external integrations like email or LDAP, CLI-only tools, and similar), and that's where a behavioral test suite stops and an interactive developer-in-the-loop agent like Kiro takes over. Part 3 is Kiro finishing those features, verifying them, and shipping the migration.

What was left

The 27 features the harness couldn't prove, grouped by why:
  • Externally bound: LDAP / OAuth / two-factor sign-in, email and webhook delivery, reverse-proxy trust, inbound email-to-comment.
  • CLI-only: CSV export of tasks/subtasks/transitions, database maintenance.
  • Browser-login mechanisms: account-locking, remember-me, password-reset.
  • Config side-effects and partials: project duplication (and the permission/action duplication it drives), the automatic-actions engine, custom filters, application settings, color palette, subtask reordering, session validation, @mentions, notification types.

How Kiro closed it

The original harness stays untouched, since it's what proved the 74%. Instead, Part 3 adds a separate set of Python unit tests for the new app, using test doubles: stand-ins for the real external systems (a fake mail server, a local webhook receiver, a mock LDAP/OAuth endpoint, and so on) so each integration can be tested without needing the real service running.
Kiro then implemented every one of the 27 features listed above in the Python app and unit-tested it. The result: all 104 features are now migrated to Python and verified, with 77 proven by the harness, 26 by the new Part-3 unit tests, and one internal behavior (per-request action deduplication) carried forward without its own test, because it has no observable effect. The Part-3 suite is 44 tests, all green. (Refer to appendix A for prompt reference)

Verifying it works

Two things give confidence beyond the harness: the unit tests and a shadow run.
Unit tests. Every migrated feature has a Python unit test (44 in all, green), and the external integrations are checked against the doubles above, so their logic is proven end to end without a live provider.
Shadow run. Run the PHP original and the Python rewrite side by side on the same data and compare their responses directly. 11 of 14 read methods match exactly; the three differences are cosmetic (a version string, and a couple of numbers rendered as "1" instead of 1), with no behavioral difference.
And the original harness still passes against the Python app: 159 of 160 API checks match. The single miss is a data inconsistency in the test seed, not a regression.

What it cost

Closing the last mile with Kiro, implementing the remaining features, wiring the test doubles, writing the 44 unit tests, and running the shadow diff, cost about 201 Kiro Power credits (Claude Opus 4.8).
Across the whole series, Kiro's share came to roughly 653 credits (389 building the harness in Part 1, 63 for the validation-skill glue in Part 2, 201 here), alongside AWS Transform custom's 4,857 agent-minutes for the bulk rewrite in Part 2. For current pricing, see Kiro pricing  and the AWS Transform pricing page .

The bottom line

The migration is successful. All 104 features are migrated and verified, and the Python app behaves like the original where it counts: 159 of 160 API checks, all 93 UI flows, and a clean shadow run, over the same database. It is a behavioral rebuild, not a pixel-for-pixel copy of the UI, and that is the honest scope.
The same board as Part 1, now served by the Python rewrite: same columns, same seeded tasks, same backend contract, with a light stylesheet on top. It's clean and functional, though not a pixel-for-pixel copy of Kanboard's own theme.
pyapp board

The approach, repeatable for any app

Strip away Kanboard, PHP, and Python, and what's left is a cycle you can run on any app you're afraid to touch:
  1. Understand it. Extract the business rules and a full feature inventory from the code, so you know what the system actually does, not what the docs claim.
  2. Build a yardstick. Turn that inventory into a black-box behavioral test harness that captures today's behavior. This is the safety net the rewrite must not break.
  3. Transform against it. Rewrite in bulk with the harness wired in as the validation gate, so every change is checked against the yardstick as it happens, not after.
  4. Close the last mile. Finish what the yardstick can't reach and verify it with unit tests, doubles, and a shadow run.
That loop is the same whether you're doing a language port, a framework upgrade, or a re-platform, and the tools are your choice. I reached for AWS Transform custom for the repeatable, at-scale transformation and Kiro for building the yardstick and finishing the job by hand. And a human stayed in the loop throughout, reviewing each step before trusting it: the extracted business rules, the harness spec, the transformation skill, and every coverage number. That split, bulk automation plus human-in-the-loop judgment, is what carried the migration end to end. Capture behavior first, transform against it, then finish by hand, and you get a migration you can prove rather than one you hope is right.
lifecycle

Appendix A, the prompt to close the last mile

The harness-unreachable features were finished interactively with Kiro, not from a single saved spec. Here is a reproducible version of the prompt that drove it, generic enough to adapt to your own rewrite: point Kiro at the app, name the features the oracle can't grade, and insist every one is proven with a unit test that stands in the external system with a double.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
CONTEXT
Our PHP-to-Python rewrite (FastAPI + SQLModel, in kanboard/pyapp/) already passes the
black-box behavioral harness: 100% green against it, which covers the subset of
Kanboard's 104 features the oracle can grade over its network boundary (77 of them,
74%). The rest were scoped out because the black-box boundary can't reach them. Your
job is to DERIVE that gap set from the BRE inventory and the harness coverage map,
implement it in the Python app, and prove each feature with a unit test.

STEP 1 - DERIVE THE GAP LIST FROM BRE + THE HARNESS (do this first; do not assume it)
- Take the business-rules-extraction (BRE) feature inventory from Part 1 as the
denominator: the full list of 104 features across all domains, the same map the
harness used as its coverage checklist.
- Read the harness coverage map (artifacts/feature_coverage.json), which marks each
BRE feature Covered, Partial, or scope_out WITH a written reason.
- Compute the gap list = every BRE feature NOT marked Covered. Group it by the
recorded scope-out reason (config-fixed external, CLI-only path, headless-hostile
UI control). Emit that list and confirm it with me before building; do not invent,
add, or drop items. Against this codebase it comes out to 27 features.

HARD RULES
- Do NOT modify the frozen harness. It is what proved the 74%; it stays untouched.
- Add a SEPARATE Python unit-test suite (pytest) under kanboard/pyapp/tests/.
- For anything that depends on an external system, stand it in with a TEST DOUBLE
(not the real service): a local SMTP sink, an in-process webhook receiver, a fake
LDAP/OAuth endpoint, an RFC-6238 TOTP generator, a trusted-proxy header. Each
integration must be provable end to end without a live provider.
- Assert user-observable outcomes and resulting DB rows, not internals.
- Keep the same JSON-RPC contract and the same SQLite schema.

FOR REFERENCE, deriving against Kanboard yields these 27, grouped by scope-out reason
(your Step 1 output should match this set):
- Externally bound: LDAP / OAuth / two-factor sign-in; email and webhook delivery;
reverse-proxy trusted-header auth; inbound email-to-comment.
- CLI-only: CSV export of tasks / subtasks / transitions; database maintenance.
- Browser-login mechanisms: account-locking, remember-me, password-reset.
- Config side-effects and partials: project duplication (and the permission/action
duplication it drives); the automatic-actions engine; custom filters; application
settings; color palette; subtask reordering; session validation; @mentions;
notification-type configuration.

WORKFLOW
1. Derive the gap list (Step 1) from the BRE inventory + the harness coverage map,
and confirm it before building.
2. Implement each feature in kanboard/pyapp/, in focused modules (auth, integrations,
external_auth, notifications, projects, mentions, actions, partials, cli, ...).
3. Write a unit test for each, using the doubles above; run `pytest` until green.
4. Do not fake a pass: if a feature genuinely has no observable effect to assert,
say so and carry it forward explicitly rather than inventing a test.

VERIFY (beyond the harness)
- Full pytest suite green.
- Shadow run: start the PHP original and the Python app on identical seeded data,
call the same read methods against both, and diff the responses; explain any
difference.
- Re-run the frozen harness against the Python app to confirm no regression.

DONE WHEN
All 27 features are implemented and unit-tested (one internal, no-effect behavior
carried forward without its own test), the suite is green, the shadow run shows no
behavioral divergence, and the frozen harness still passes.
A human stayed in the loop the whole way: I reviewed each feature's implementation and its test before accepting it, and read the shadow-run diff myself rather than trusting a green checkmark.

Series: Monolith Modernization - Kiro+AWS Transform (3 articles)

  1. 3
    Kiro closes the last mile and finishes the migration This article
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article