Demo · Gamma Quest Tracker · Engineering record
How Gamma Quest Tracker was modernized, step by step.
Every step, tool and check behind the Gamma Quest Tracker case study, in the order it was done: what was recorded, how it was replayed, what came out, and what went wrong on the way. Written for engineers who want to know exactly how the results were produced.
- lines of C# inventoried
- 21,200
- recorded timer cases
- 20
- pages captured on real data
- 250
- recorded write cases
- 31
- approved changes
- 28
1 · Overview
The method in one table
A rewrite, not a conversion, so the old site's recorded behavior is the specification. Each slice is recorded on the old site, replayed on the new code, and compared until the only differences left are the approved ones.
| Phase | What was done | Result |
|---|---|---|
| Inventory | Read every page, handler and class; listed the database schema without reading any rows; mapped which code uses which tables; listed risks with file and line references. | 144 C# files, 105 pages, 63 tables; slices proposed, unused parts identified. |
| Design | Target architecture and decisions, approved by the owner one by one: database, hosting, mail, files, what to retire. | .NET 10, PostgreSQL 16, one Lightsail instance, S3 and SES. |
| Timer recording | The old site on a temporary Windows machine with test data; the clock set for every step; responses and rows saved. | 20 cases, 71 steps. |
| Timer replay | The new timer replays every step at the recorded moment, in memory and on PostgreSQL. | 102 row comparisons, no differences. |
| Pages on real data | The production backup restored on the temporary machine; pages saved for 8 sampled users; the same pages computed by the new code from a PostgreSQL copy. | 137 of 137, then 250 pages with the dashboards. |
| Writes | Actions performed through the old pages on test data; twelve tables and the outgoing mail saved after each step; replayed on the new code. | 30 of 30 cases, 1 deferred. |
| Findings | Every defect a rewrite could fix by accident written up and decided: fix as an approved change, or keep. | 28 approved changes, each with its own test. |
| Server | One CloudFormation stack, a setup script, release and rollback scripts, backups and alarms. | Live at tracker-new.gammaquest.com, in testing. |
| Cutover | A written runbook: rehearsal, migration with a data comparison, DNS switch, way back. | Real data loaded October 24: 35 of 35 tables, 151,748 rows identical. DNS switch next. |
Built with AI assistance
The work was directed and reviewed by Legacy Parity, with an AI coding assistant writing much of the code, scripts, tests and documentation. Its output is held to the same standard as everything else here: the old site's recorded responses, rows and mails are the reference, and the new code passes only when it reproduces them. Every change from the old behavior was approved by the owner before it went in. Client data never left Gamma Quest's own machines and AWS account; comparison reports that leave the machine name users only by alias. More in AI and data handling.
2 · Tools
What it was built with
| Tool | Version | Used for |
|---|---|---|
| .NET, ASP.NET Core | 10 | The new tracker: server-rendered pages with plain form posts, a small JSON API for the timer and planner |
| Entity Framework Core, Npgsql | 10 | Data access on PostgreSQL |
| ASP.NET Core Identity | 10 | Sign-in, with the users' existing passwords carried over |
| PostgreSQL | 16 | The new database, locally, in tests and on the server |
| xUnit | 2.9 | 139 tests: replay of the recordings, approved changes, PostgreSQL, the migration tool, end-to-end pages |
| Jodit, HtmlSanitizer, AngleSharp | The rich-text editor for posts, cleaning posted HTML, reading the captured legacy pages | |
| Windows Server, SQL Server Express, IIS | 2022 | The temporary machine that runs the old site for recordings |
| Windows PowerShell | 5.1 | Capture scripts on that machine, and the deployment scripts run from the owner's PC |
| AWS CloudFormation | The capture machine and the production stack, each created and removed in one step | |
| Ubuntu, Caddy, systemd | 24.04 | The server: HTTPS with automatic certificates, the app as a service, nightly backup timer |
| GitHub Actions | A workflow that runs every test with PostgreSQL 16 and SQL Server 2022 services | |
| Playwright | Screenshots of the new pages on desktop and phone | |
| AI coding assistant | Drafting code, scripts, tests and documentation, verified against the recordings |
AWS services
| Service | Role |
|---|---|
| Amazon EC2 | The temporary Windows capture machine, no inbound access, deleted after each run |
| Amazon Lightsail | The production server: 2 GB, 2 vCPUs, static IP, firewall with SSH open to one address, daily snapshots kept 7 days |
| Amazon S3 | Attachments in a private, versioned bucket; nightly database dumps kept 35 days; release zips |
| Amazon SES | Notification mail from the same sender as before, one mail per recipient |
| Amazon Route 53 | DNS: tracker-new.gammaquest.com for the new tracker; the old tracker keeps its own name |
| AWS IAM | One user for the app, limited to its two buckets and to sending from the company domain |
3 · Step by step
What was done, in order
-
Inventory the system
Every page, handler, user control and class in the site was read and counted: about 21,200 lines of C# in 144 files, 105 Web Forms pages, 10 HTTP handlers, 6 master pages. The database was inventoried from a schema-only script, without reading a single row: 63 tables, of which 35 are the tracker's, 11 belong to ASP.NET Membership and 17 to two Microsoft starter kits; 148 stored procedures, none for the tracker's own tables; no foreign keys, and 50 relationships inferred from the joins in the code.
The inventory also showed what would make a conversion hard: the site is a Web Forms "web site" with no project file, compiled by IIS at run time, and it depends on third-party controls and handlers. Part of the code turned out to be unused: a payment starter kit, a Silverlight client, test pages. One page did not even compile against the shared code in the copy we had.
-
Decide the target, one decision at a time
The design listed each choice with options, costs and a recommendation, and the owner decided: PostgreSQL rather than staying on SQL Server, one Lightsail instance rather than a load balancer and managed database, Amazon SES for mail, a private S3 bucket for attachments, the chat-bot notifications retired. The rewrite keeps the existing tables and columns, so every old topic, post and time entry is there on day one and new ones look exactly like old ones.
-
Build a machine that runs the old site
A CloudFormation stack creates a Windows Server 2022 machine with SQL Server 2022 Express in our own AWS account, with no inbound ports. Its startup script sets the time zone to Eastern, as on the old server, installs IIS with ASP.NET 4.8, builds the database from the schema script, loads test data and deploys the site unchanged apart from its configuration. Two test users sign in through a test-only page that answers local requests only.
Three things had to be added for the old site to run at all: seven compiled starter-kit libraries that the cleaned copy of the source had left out, without which the shared code does not compile; a configuration switch for the IIS pipeline mode; and, later, rows the real site has and the test data lacked (see section 4).
-
Record the timer with the clock under control
For each step of each case the script sets the system clock, calls the timer endpoint as the browser does, and saves the response, the clock it used and every time-entry row afterwards. Twenty cases cover starting and stopping, switching topics, the 15-hour cap, midnight, both daylight-saving changes, topics with the same name in two projects, two users on one topic, and names with an apostrophe. A last case runs a sequence and saves the three report pages it produces.
-
Replay the timer, to the millisecond
The new timer replays each step with the clock set to the moment the old one saw. Two normalizations are allowed and listed in the report: row ids are compared by order of creation, and start times within 100 ms, because the old handler reads the clock several times. Everything else must match, ten fields per row, in memory and on PostgreSQL through Entity Framework.
Exact replay mattered. SQL Server stores times rounded to 1/300 of a second, but the old code rounds elapsed time up to the next minute using the unrounded clock, so a one-millisecond difference changes a stored total by a whole minute. The replay uses the exact recorded times.
-
Capture pages on the real data
For reading, the same machine restores the production backup instead of the test data, signs in as eight sampled users (four team members, four clients) and saves the pages they see, with the time each was fetched. Settings choose the users, three projects each, recent and busy topics, a subtopic, an internal topic, and pages each user must not be able to open. The pages come back to the owner's PC and the machine and its bucket are deleted.
-
Compare what pages say
A command in the migration tool reads each captured page, computes the same page with the new code from a local PostgreSQL copy converted from the same backup, and compares facts: projects and topics shown and their order, the cells that matter, post ids, authors, types and times, page counts, subtopics, and refusals. Legacy columns are read by their headers, so hidden columns do not shift the cells. Topics with the same last-post time may come in either order, as they may in SQL Server. Planner pages are computed with the clock pinned to the fetch time.
It writes two reports: a shareable one with user aliases and counts, and a detailed one with the differing values, which stays on the owner's PC. First run: 137 of 137 (129 the same, 8 refused by both), after three rules were added to the new code. Second run, with the dashboards: 250 pages, 245 the same or refused by both, one an error page in the old site, and four team-only pages the new tracker had shown to clients. That rule lived in the old site's configuration file rather than in its code; the new tracker now applies it.
-
Record and replay the writes
Write actions run on test data only. Each case signs in, sets the clock, performs the action through the old page's own form posts, and saves twelve tables and every mail file after each step. Thirty-one cases: eighteen for topics (add, reply, status and assignment, take it, hide, subscribe, the edit page, priority order, the description, a refused reply) and thirteen for planning (the plan dialog, moving a day, the topic panel, copying, closing on the board, epics, sprints).
The new code replays each case on PostgreSQL. The comparison applies the approved changes that the case names, and nothing else. Result: 30 of 30 reproduced. The thirteenth planning case, editing a sprint, was deferred by the owner because the capture script could not drive that grid's update link; it is covered by unit tests until the next capture run.
-
Turn findings into decisions
Every behavior that a rewrite might quietly change was written up with the case that shows it, and the owner decided: fix it (an approved change, numbered F1 to F28, each with its own test) or keep it. Kept on purpose, among others: a client's reply goes back to the last other assignee, the 15-hour cap applies only to the automatic stop, time past midnight stays on the starting day, and topic numbers can repeat after a delete. The new timer keeps a legacy mode that reproduces the old rounding and daylight-saving behavior exactly, which is how the recordings are replayed.
-
Migrate the data, and check it
The migration tool converts the schema, copies the tables that stay in use, and compares the result with the source by row counts and checksums. Text columns that SQL Server compared without case use a case-insensitive collation in PostgreSQL, because topics and time entries are still linked by name. Users keep their passwords: the old Membership passwords are decrypted once with the old site's key and stored with a modern hash, under the same user ids and roles.
-
Build the server
One CloudFormation stack creates the Lightsail instance, static IP, firewall, the two buckets and the app's IAM user. A setup script installs PostgreSQL, the .NET runtime and Caddy, creates the service user, tunes PostgreSQL for a small machine, installs the systemd service and the nightly backup timer, and hardens SSH. The app's secrets live in one root-only file. A release script unpacks a versioned zip next to the previous one, applies any database scripts, switches a link and checks the health endpoint; rollback switches the link back.
-
Give it the company's look
The new pages use Gamma Quest's own design: the company's colors, self-hosted open-source fonts and its mark, with the current page marked in the menu and every page usable on a phone. Because the page comparisons check what pages say rather than how they look, the new design did not touch the proof: all tests pass on the restyled build.
In October 2026 the look was redesigned again: a dark sidebar menu, topics shown as cards on the status board, and a new logo drawn from the original Gamma Quest artwork. The redesign changed only the layout and the stylesheet, never the page markup the comparisons read, and all 139 tests, page comparisons included, stayed green.
4 · Problems on the way
What went wrong, and what it taught
| Problem | Cause | Fix |
|---|---|---|
| The first timer recording gave wrong results around midnight | After each clock jump, SQL Server's own clock lagged about a second behind the system clock | Run discarded and kept for the record |
| The second recording was invalid | A harness bug called the steps with no topic | Fixed; the third run is the reference |
| Replay off by one minute in one case | The 1/300-second rounding of stored times, against rounding up to the next minute | Replay with the exact recorded moment of each step |
| Some refusals showed up as missing pages | Windows PowerShell 5.1 did not record redirects to the sign-in page | The capture records redirects itself |
| Planning writes failed on the old site | The test data lacked rows the real site has: a server row the master page reads, an active project for the quick-add form | Test data extended; each case can bring its own setup |
| The sprint edit could not be captured | Its Update link is a postback control the script could not click | Deferred by the owner; unit tests meanwhile |
| A topic created from the plan dialog had a different due date | The old code sets the due date to the current time, not empty | Matched: the new code does the same |
| Four team-only pages opened for clients | The rule was in the old site's configuration file, not in its code | The same rule in the new tracker |
| The production stack failed to create | The mail domain was already verified in SES from earlier work | The existing identity is reused by the setup script |
| The test name did not resolve after adding the DNS record | The domain is registered at one company but its DNS is served by Route 53 | Record added in Route 53 |
| The server could not get a certificate | Caddy also asked for the real name, which still points at the old server | Test name only until the switch; the runbook adds the real name |
5 · Limits
What this does not prove
- Not switched over. The migration tool has converted the real backup on the owner's PC for the page comparisons; the new tracker is live for testing next to the old one, and loading the real data onto it and the switch are still ahead.
- Eight users. Pages were compared for a sample of users and their busiest projects, not for everyone.
- Facts, not looks. The comparison checks what pages say; layout and styling are new on purpose.
- Writes on test data. Write cases run on a seeded database, not the production one.
- One case deferred: editing a sprint.
- Test environment, not production. SQL Server 2022 Express on the capture machine, while the old site runs on its own server; behavior that depends on that server's exact setup was not recorded.
- Features waiting for a decision: invoices, documents and the calendar.
Want this kind of evidence for your system?
A two-to-three-week assessment maps your system, finds the risks and gives you a fixed price for the first slice.