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

Built with AI assistance; see how. Record as of October 2026. The tracker is Gamma Quest's own production system: its source code and data are private, and nothing on this page comes from client data except counts and the results of comparisons.

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.

PhaseWhat was doneResult
InventoryRead 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.
DesignTarget 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 recordingThe 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 replayThe new timer replays every step at the recorded moment, in memory and on PostgreSQL.102 row comparisons, no differences.
Pages on real dataThe 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.
WritesActions 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.
FindingsEvery 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.
ServerOne CloudFormation stack, a setup script, release and rollback scripts, backups and alarms.Live at tracker-new.gammaquest.com, in testing.
CutoverA 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

ToolVersionUsed for
.NET, ASP.NET Core10The new tracker: server-rendered pages with plain form posts, a small JSON API for the timer and planner
Entity Framework Core, Npgsql10Data access on PostgreSQL
ASP.NET Core Identity10Sign-in, with the users' existing passwords carried over
PostgreSQL16The new database, locally, in tests and on the server
xUnit2.9139 tests: replay of the recordings, approved changes, PostgreSQL, the migration tool, end-to-end pages
Jodit, HtmlSanitizer, AngleSharpThe rich-text editor for posts, cleaning posted HTML, reading the captured legacy pages
Windows Server, SQL Server Express, IIS2022The temporary machine that runs the old site for recordings
Windows PowerShell5.1Capture scripts on that machine, and the deployment scripts run from the owner's PC
AWS CloudFormationThe capture machine and the production stack, each created and removed in one step
Ubuntu, Caddy, systemd24.04The server: HTTPS with automatic certificates, the app as a service, nightly backup timer
GitHub ActionsA workflow that runs every test with PostgreSQL 16 and SQL Server 2022 services
PlaywrightScreenshots of the new pages on desktop and phone
AI coding assistantDrafting code, scripts, tests and documentation, verified against the recordings

AWS services

ServiceRole
Amazon EC2The temporary Windows capture machine, no inbound access, deleted after each run
Amazon LightsailThe production server: 2 GB, 2 vCPUs, static IP, firewall with SSH open to one address, daily snapshots kept 7 days
Amazon S3Attachments in a private, versioned bucket; nightly database dumps kept 35 days; release zips
Amazon SESNotification mail from the same sender as before, one mail per recipient
Amazon Route 53DNS: tracker-new.gammaquest.com for the new tracker; the old tracker keeps its own name
AWS IAMOne 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

  1. 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.

  2. 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.

  3. 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).

  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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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.

  11. 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.

  12. 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

ProblemCauseFix
The first timer recording gave wrong results around midnightAfter each clock jump, SQL Server's own clock lagged about a second behind the system clockRun discarded and kept for the record
The second recording was invalidA harness bug called the steps with no topicFixed; the third run is the reference
Replay off by one minute in one caseThe 1/300-second rounding of stored times, against rounding up to the next minuteReplay with the exact recorded moment of each step
Some refusals showed up as missing pagesWindows PowerShell 5.1 did not record redirects to the sign-in pageThe capture records redirects itself
Planning writes failed on the old siteThe test data lacked rows the real site has: a server row the master page reads, an active project for the quick-add formTest data extended; each case can bring its own setup
The sprint edit could not be capturedIts Update link is a postback control the script could not clickDeferred by the owner; unit tests meanwhile
A topic created from the plan dialog had a different due dateThe old code sets the due date to the current time, not emptyMatched: the new code does the same
Four team-only pages opened for clientsThe rule was in the old site's configuration file, not in its codeThe same rule in the new tracker
The production stack failed to createThe mail domain was already verified in SES from earlier workThe existing identity is reused by the setup script
The test name did not resolve after adding the DNS recordThe domain is registered at one company but its DNS is served by Route 53Record added in Route 53
The server could not get a certificateCaddy also asked for the real name, which still points at the old serverTest 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.

Read the case study · All demos

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.

Book an assessment call