← the whole book on one page

Chapter 11: Capstone — One Change, from Legacy Core to Production

A change is not finished while the old way still works somewhere.

For about four decades, from the 1950s into the 1980s, programmers stored the year as two digits. Storage was scarce and expensive, the century seemed far off, and “67” meant 1967 to every program that read it. The US General Accounting Office (GAO) later put the consequence in one plain sentence of its 1997 guide: in a two-digit field, the year 2000 cannot be told from 1900. The article usually credited with turning years of quiet warnings into public alarm is Peter de Jager’s “Doomsday 2000”, in Computerworld on 6 September 1993. What followed was a single, small change that had to be made in nearly every computer system in the world, much of it in code whose authors had retired, moved on or died, and all of it against a date that could not be moved. The US Senate’s Special Committee on the Year 2000 Technology Problem, chaired by Robert Bennett with Christopher Dodd as vice chair, described the effort in its final report of 29 February 2000 as “one of the most complex and vast management challenges in recent history”, and dated it plainly: “From roughly 1995–1999, private and public organizations … mobilized huge resources.” Nobody knows what it cost in total, and the committee said so; the estimates it collected for the United States alone ranged from about $100 billion (the Commerce Department) to $225 billion (Gartner).

The way the work was organized deserves more attention than the money. The GAO’s Year 2000 Computing Crisis: An Assessment Guide, published as an exposure draft in February 1997 and in final form that September, set out five phases that agencies and, in practice, most large organizations followed: awareness; assessment, which meant finding and listing every system and deciding which mattered; renovation, which it summarized as “Convert, replace, or eliminate”; validation; and implementation. It told agencies to “plan and manage the Year 2000 program as a single large information system development effort”, and it reported, with some alarm, that “only half of the 24 departments and major agencies reported that they completed the assessment phase by the end of August 1997”. The guide also warned that “the testing and validation process may consume over half of the Year 2000 program resources and budget.” Martyn Thomas, who led Deloitte’s international Y2K services, adds a second lesson in his 2017 Gresham College lecture. Organizations that chose to replace old systems “could not resist adding new features”, and where those projects met the deadline, “testing was cut short, leading to later failures that were rarely publicised as having been Y2K related.” Under a fixed date, the rewrite was the risky path.

Then the date arrived, and almost nothing happened. The Senate report recorded that “hundreds of computer problems have been reported since January 1, 2000, but most have been quickly corrected and none have caused serious disruptions.” Its appendix lists the kind of thing that did go wrong, such as 800 slot machines in Delaware that shut down after reading the date as 1 January 1900. In Britain the worst case surfaced later: a hospital pathology system had miscalculated mothers’ ages from January 2000, and more than 150 pregnant women were wrongly told they were at low risk in screening for Down’s syndrome. The quiet invited an argument, never settled, about whether the work had been needed at all, and Section 11.5 returns to it, because the review of any successful change meets the same argument in miniature.

Encore’s version of the problem was smaller and its date was its own, but the shape was the same. Chapter 1 described a ticket identity derived from the order and the seat, written by the original nine engineers in the first year, recomputed in four other places since and, as Chapter 5’s migration would find, read out of the barcode at 60 more, and a product that could not exist until that identity could change. This capstone follows that one change, the ability to reissue a ticket under a new identity, from the first characterization test in the barcode core at month 4 of the bet to the engineering review held at month 24, a year after launch. The Products book’s capstone (its Chapter 10) follows the same 18 months as a bet: why Encore made it, whether it paid, and what the month-18 review of the memo decided. This chapter follows the code and the engineering system that changed it, and reports what the practices of Chapters 2 to 10 yielded when applied to one change at once. Figure 11.1 lays the change out in time.

Month What changed in the code and the engineering system Taught in
2 Partners told the barcode format will change; old format accepted on the partner API until month 14 The Products book, Section 9.4
4 Resale team formed; 23 dependencies planned; first Monte Carlo forecast; Resale repository created from the template Sections 1.3, 9.2, 10.3
5 Reissue-ownership request for comments (RFC) closes in an architecture decision record (ADR); pairing on the barcode core begins Sections 9.5, 2.3
6 Characterization suite of 1,400 cases in continuous integration (CI); format campaign begins Sections 2.4, 5.2
7 Inventory publishes reissue with a contract test; the Resale–Inventory collaboration ends Sections 9.2, 3.3
8 Legacy barcode parser deprecated; 56 of 60 call sites moved Sections 2.5, 5.3
9 Walking skeleton end to end; partner API in sandbox; dependencies down to 14 Sections 3.2, 6.4, 10.2
10–12 Six weeks on trunk behind flags; reissue drill on a test event; Rowmate certified Sections 4.5, 8.3
12 Launch in France; the first resold ticket scans in Lyon Section 11.4
13–15 Issue variants deleted; last parsers moved; most flags removed; Tessera live Sections 2.3, 4.5
24 Engineering review: codebase, platform, teams Section 11.5

Figure 11.1 — One change through two years. The middle column is what this chapter reports; the right-hand column is where each practice is taught, so that the chapter can report it without teaching it again.

11.1 The Change

What reissue asks of the code

A change that crosses a dozen teams is easiest to grasp by asking what must be true once it is done, and for reissue there were four things. A ticket must be voidable and reissuable as one atomic step, so that at no moment are there two live tickets for one seat, or none. The seller’s old identity must stop working at every gate, including gates with no network when the crowd arrives. The buyer must receive the new ticket, which at Encore means the rotating barcode in the app first and the wallet pass where one exists, because wallet updates are best effort and Apple Wallet passes at Encore do not rotate. And the money and the record must agree: both legs of the reissue in the event stream, and the fee split in the finance ledger that is reconciled daily. Section 8.2 explained why reissue is the trust mechanism of the whole marketplace; here the four requirements are a route through code.

Traced through the services, the route is longer than the product description suggests. The Resale service, new, holds the listing and starts the purchase. Orders & Payments runs the resale purchase saga through the primary payment service provider’s (PSP’s) marketplace product, which splits the money four ways. Seat Inventory & Ticketing, owned by the Inventory team, performs the reissue inside the barcode core, and publishes TicketReissued and TicketRevoked. Venue Entry turns the revocation into an entry on the lists its scanners hold. Fan Messaging replaces passes and tells both fans what happened. The Mobile App team’s ticket view must show a reissued ticket correctly in every app version still in use. Billing & Payouts meters the fee and pays the seller five days after the event. Every service but the first already existed, and each was built on the implicit assumption that a ticket’s identity never changes once issued. Like the two-digit year, it was a representation decided once, early, for good reasons, and then left visible to every piece of code that cared to read it.

Who owns reissue

Section 9.5 printed the opening of RFC-0412, the request for comments that asked whether Resale or Inventory should own reissue, and the ADR that settled it three weeks later: the Inventory team owns issuance and revocation, and Resale calls it. What the RFC could not show was the consequence. The objection raised most often in its comment period came from inside Inventory. The team was being asked to own the hardest new code in the bet, in a module that one of its members, Lena, understood and nobody else did, while it also carried the on-call load of the seat ledger. The ADR answered by making the objection a condition. Inventory would own reissue, and the plan would fund making the module understandable by more than one person before the first production reissue, tracked as a dependency in its own right. The ownership decision and the knowledge problem of Section 11.2 were, in the end, one decision.

The map and the forecast

Chapter 1 showed the 23 dependencies of the first quarterly plan, drawn in month 4 and grouped by the team that had to deliver, with a column, added by a Resale engineer, recording the age of the oldest code each one touched. Over the next five months, during building, the plan cut them to 14. Section 10.2 gave the rule: remove a dependency by moving the work, contract it where the interface is stable, and coordinate only what is left. Figure 11.2 shows where the nine went, and every one of them went by moving work.

Where nine dependencies went Verified Resale's cross-team dependencies by the team that must deliver, at the first quarterly plan (month 4) and at month 9, drawn as one square per dependency: solid squares remained, dashed squares were cut, lettered by how the work moved (R taken over by Resale, P moved to the platform, F folded into a change already planned). Inventory: 3 to 2; Proof of a Gate purchase built by Resale from existing TicketIssued events instead of a new call. Venue Entry: 3 to 2; The revocation feed folded into Inventory's reissue change as TicketRevoked, which Venue Entry's list builder consumes. Orders & Payments: 2 to 2; —. Bot Screening: 2 to 1; Per-buyer limits written by Resale in the rules engine, reviewed by Bot Screening. Billing & Payouts: 3 to 3; —. Partner APIs: 3 to 1; Webhook delivery moved to the platform; listing endpoints written by Resale. Organizer Console: 1 to 1; —. Mobile App: 2 to 1; The Resale module in the fan app taken over by Resale; the release train and version floor remain. Fan Messaging: 2 to 1; The two messages written by Resale on Fan Messaging's templates; pass replacement remains, as a contract. Design System: 1 to 0; Badge and capped-price field contributed by Resale through the contribution path. Security Engineering: 1 to 0; Partner credentials became self-service on the platform's partner-facing path. Of Inventory's two that remained, one is VR-01, the void and reissue in one step inside the barcode core, the dependency that blocked. Total: 23 at month 4, 14 at month 9; the nine cuts were six taken over by Resale, two moved to the platform and one folded into a change already planned. TEAM THAT MUST DELIVER MONTH 4 4 → 9 HOW THE DIFFERENCE WENT Inventory R 3 → 2 Proof of a Gate purchase built by Resale from existing TicketIssued events instead of a new call Venue Entry F 3 → 2 The revocation feed folded into Inventory's reissue change as TicketRevoked, which Venue Entry's list builder consumes Orders & Payments 2 → 2 — Bot Screening R 2 → 1 Per-buyer limits written by Resale in the rules engine, reviewed by Bot Screening Billing & Payouts 3 → 3 — Partner APIs P R 3 → 1 Webhook delivery moved to the platform; listing endpoints written by Resale Organizer Console 1 → 1 — Mobile App R 2 → 1 The Resale module in the fan app taken over by Resale; the release train and version floor remain Fan Messaging R 2 → 1 The two messages written by Resale on Fan Messaging's templates; pass replacement remains, as a contract Design System R 1 → 0 Badge and capped-price field contributed by Resale through the contribution path Security Engineering P 1 → 0 Partner credentials became self-service on the platform's partner-facing path Total 23 → 14 Taken over by Resale 6 · moved to the platform 2 · folded into a change already planned 1 Remained at month 9 Cut: R by Resale · P platform · F folded VR-01, reissue in the barcode core: the one that blocked

Figure 11.2 — Verified Resale’s dependencies at the first quarterly plan and at month 9, one square for each, by the team that had to deliver. Three ways of moving work account for all nine cuts (six taken over by Resale, two moved to the platform, one folded into a change already planned), and none of them was a decision to skip the work; VR-01, inside the barcode core, was among the 14 that stayed.

Each of the three moves is a general one. A requesting team doing the work itself, in its own code or in another team’s code under that team’s review, is the commonest, and inside another team’s code it is InnerSource at the scale of a single pull request (Section 8.4); it works only where the owner has written down how it accepts contributions, and it is easiest where the requester can build on an event the owner already publishes, as Resale did with TicketIssued, provided the event’s schema is registered and its consumers hold message contracts (Section 3.3). The platform taking over a capability removes the dependency for every later team as well as this one, which is why Chapter 6 treats the partner-facing path as an investment. And folding a request into a change the owner was making anyway removes a coordination point without adding work. Of the 14 that remained, eight were contracts consumed through documented interfaces and six needed real coordination; the hardest of the six, reissue inside the barcode core, is the subject of Section 11.4.

The forecast is where this chapter can show what no other chapter can: how wrong it was, and in which direction. In month 4 the Monte Carlo simulation that Section 10.3 builds put the 85th percentile of the launch at month 14, two months past the appetite. The scope cuts that the Products book’s Section 10.3 records (Tessera after launch, festival passes deferred, the organizer dashboard replaced by a statement) and the one engineering added (no tour-wide rule templates) were agreed against that number at month 4, and as the weekly re-runs absorbed them the 85th percentile came back to month 12 by month 6 and stayed there. The launch came in month 12. The date was about right, and much of what lay beneath it was not. The backlog for launch grew by about 40%, from about 120 items in month 4 to about 170 by month 9, as work was split and discovery added things nobody had planned, such as pairs listed together; that was inside the 30–65% growth the outside view had supplied, which is why the date held. And the item that came closest to breaking the date was one the simulation could not see clearly. It drew on Inventory’s past throughput, in which the barcode core hardly figured, so it could not foresee the queue that formed when at least 14 changes arrived at the core’s one reviewer within a few months. Waiting of that kind leaves no trace in a throughput history until it has happened, and Section 11.4 tells how a different instrument showed it.

The plan that was not a rewrite

Section 2.4 gave Encore’s reasons for changing the barcode core in place: Bellotti’s counsel on momentum, Feathers’s algorithm, and a definition of done written in one sentence before the work began. What the Y2K record adds is a sharper view of the alternative. Thomas lists three remediation methods: expanding dates to four digits, windowing two-digit years, and replacing the system. Many organizations repaired their code in place, and those that chose replacement under the deadline, in his account, often cut testing short. The rewrite, which feels like the thorough option, was the one most exposed to a fixed date, because it had to reproduce all of the old behavior before it could ship any of the new. Encore’s date was its own, the 12-month appetite, with the autumn contract season as a step in the cost of delay (Section 10.1), and the same reasoning applied.

The narrower decision was which pattern to use, and the Architecture book’s Section 3.4 supplies the terms. The strangler fig replaces a system piece by piece behind a facade; Martin Fowler, who named it in 2004 and revised the description in 2024, judges that “the reduced risk and earlier value from the gradual approach outweigh its costs”. But the barcode core was a library inside one service, called in-process, and the tool for replacing a component inside a codebase is branch by abstraction, the strangler’s smaller relative. Encore kept the strangler in reserve, as its plan said, and never needed it. The choice was right for one reason above the others: the code could be put under test. Feathers’s procedure assumes that, and where it is false, where the old code cannot be characterized because its behavior depends on state nobody can reproduce, the strangler’s facade and a parallel run become the safer course.

What engineering promised

The funding of the bet, in tranches tied to what each stage would learn, belongs to Section 10.4 and the Products book’s Section 10.1, and this chapter does not retell it. Engineering’s part of the promise fitted in three lines of the month-4 plan. It gave the forecast as a range with its percentile stated, never as a date. It kept the preparation of the barcode core, the characterization tests and the spreading of knowledge beyond Lena, as its own line on Inventory’s protected debt allocation (Section 10.4), about a fifth of Inventory’s capacity for two quarters, apart from the reissue work the bet funded, so that it could not be squeezed out of the reissue estimate. And it reserved capacity after launch for cleanup, on the ground that a change whose cleanup is unfunded leaves two systems running. That last line was the one most often questioned; Section 11.4 shows it was needed.

Recap. Reissue asked four things of the code, and every service on its route had been built on a ticket identity that never changed. The ownership verdict became a condition: Inventory would own the hardest new code, and the plan would pay to spread its knowledge first. The dependency map shrank because work moved to the teams that needed it, to the platform, or into changes already planned, and the forecast was about right on the date while missing the one queue that nearly broke it. The plan repaired the old code in place because the old code could be put under test.

Exercise 11.1. For a change you are planning, write the one-page plan: what must be true of the code afterward, stated as properties a test could check; who owns each part; why you are not rewriting; and the line item for cleanup after launch. Then take your dependency list and, for each item, ask which of Figure 11.2’s three moves of work could remove it.

11.2 Preparing the Old Code

What the survey found

Section 2.4 described how the Inventory team built a characterization suite for the barcode core by replaying a year of issuance requests, with personal data removed, through the issuer in a test harness. By month 6 the suite held 1,400 cases and ran in CI, the first visible win the plan had promised. What Section 2.4 could not report was the result of reading it. The replay, together with the property tests that followed it, surfaced 31 behaviors that no document, ticket or living memory specified, and the team had to decide, for each, whether reissue would preserve it.

Nineteen were kept on purpose, because something outside the module depended on them. Festival passes carry the festival’s code in place of any day’s event code, and the door-scanning app relies on that to admit a pass holder on any day of the festival. A rescheduled event keeps its original event code, and the Support Tools console had been built around that fact. Accidents had become promises the moment another team leaned on them, and each such test was renamed as a specification naming the team that relied on it. Nine more belonged only to the old format, among them the older check-digit scheme carried by tickets issued before the year-four split into services. Every event those tickets were for had long since passed, so the behavior could be deleted with the format, and Section 11.4 reports that it was. The last three were bugs. The most serious was the one Section 3.3’s property test found in its first week, an interleaving in which the old code wrote the new ticket before revoking the old one, so that for one step two tickets were live for the same seat. It had never mattered while tickets were issued only once.

Of 31 surprises in eight-year-old code, three were bugs and 19 were load-bearing. A team that treated the module’s oddities as defects to clean away would have been wrong almost two times in three. The GAO’s warning that validation could consume more than half of a Y2K program’s budget is the same lesson in money. Most of the cost of changing old code safely lies in finding out what it does and who depends on it, and only a small part lies in the change.

The seam and the parallel run

Thomas, closing his lecture, relays an observation of Peter Neumann’s that states the root cause better than any account of two-digit fields: “systematic use of concepts such as abstraction, encapsulation, information hiding, and object-orientation could have allowed the construction of efficient programs in which the representation of dates could be changed easily when needed.” That is David Parnas’s 1972 criterion for decomposing systems into modules, hide each decision likely to change behind an interface, which the Architecture book takes up in its Section 1.3. Y2K was expensive because the representation of a date had no seam around it. Changing it meant finding and changing every program that read it. A seam, in Feathers’s sense, is a place where behavior can be altered without editing the code at that place; seen against Y2K, it is information hiding added late, at the cost of finding everyone who had been reading what should have been hidden.

The barcode core’s seam was the identity. Before the change every caller computed a ticket’s identity or parsed it from the barcode; after it, every caller asked one interface. Two tidyings of the kind Beck describes in Tidy First? came before the seam could be cut: the identity computation, repeated inside the issuer’s 19 public variants, was moved into the pinch point Section 2.4 found, the one method every variant finally calls, and the module that held both formatting and persistence was split in two, so that the format could change without touching the code that wrote to the ledger. Neither changed behavior, and together they made the seam a small change. Figure 11.3 shows the identity before and after it.

The identity, before and after the seam Two panels. Before the change: every reader reaches the ticket identity derived from order and seat directly. The issuer computes it in each of its 19 public variants; four other places in Encore's code recompute it; 60 call sites in 11 repositories owned by seven teams parse it out of the barcode; and copies outside Encore's repositories read it too, among them a partner's parser of the old event-code prefix and a vendor's turnstile firmware that computes it from order and seat. After the seam: the issuer's computation sits in the one pinch point every variant calls, the four other places and the migrated call sites ask one identity interface instead of computing or parsing, and partners receive an opaque ticket_id through the API while the turnstile vendor ships corrected firmware. Behind the interface sits the opaque identity with its lookup. Before it was trusted, a parallel run in month 8 computed the identity both ways on every issuance for three weeks, served the old result and logged two mismatches, both for tickets issued while an event was being rescheduled. BEFORE · EVERY READER REACHES THE REPRESENTATION AFTER · EVERY READER ASKS THE SEAM Issuer 19 public variants compute it Four other places recompute it 60 call sites 11 repos · 7 teams · parse it Outside the repos partner parser · turnstiles Derived identity order + seat, readable by anyone Issuer in the one pinch point Four other places ask, never recompute 60 call sites 56 by month 8 · last 4 by m15 Outside the repos API ticket_id · fixed firmware Identity interface the seam Opaque identity + lookup PARALLEL RUN · MONTH 8 · 3 WEEKS both ways · old result served 2 mismatches, both in reschedules Encore code Code outside Encore's repositories The representation The seam

Figure 11.3 — The barcode core’s identity before and after the seam. Before, every reader, including code outside Encore’s repositories, reached the representation itself; after, each asks one interface, and the representation behind it can change.

Characterization tests describe the past, and the team wanted evidence from real traffic before it trusted the new path with every ticket’s identity. The best public precedent is GitHub’s: in December 2015 Vicent Martí described how GitHub replaced the merge implementation behind its merge button, which had shelled out to Git, with one built on libgit2. Its Scientist library ran the old and new implementations side by side on real requests, always returned the old result, and logged every mismatch. The experiment began at 1% of requests and found, among other things, two bugs in Git itself; in one, 768 conflicts were reported as none, because a shell keeps only the low eight bits of an exit code. The new code ran at 100% for 24 hours with no mismatches before the old code was removed. Encore ran the same pattern on identity, as Section 4.5 describes. For three weeks in month 8, every issuance computed the identity both ways, the derived identity and the new opaque one with its lookup, served the old result and logged any case where the lookup disagreed with what the old derivation implied. There were two mismatches, both for tickets issued while an event was being rescheduled, and both were cases the characterization suite had pinned for the old code without anyone asking how the new code should treat them. A parallel run of this kind is a characterization test run against the present, and it finds exactly the cases that a replay of the past has not yet seen.

From one to four

Thomas tells of a moment in the late 1990s when COBOL programmers came out of retirement and commanded high salaries. That is a truck factor paid for at the last minute, at the market’s price. Encore paid for its own early. From month 5 the Inventory team paired two engineers, one of them Jonas, with Lena on every change to the barcode core, and Jonas began the written guide to the module that Section 2.3 mentions. Ines, the Inventory staff engineer who sponsored the call-site migration in Section 5.2, learned the module from the outside in, by reading every characterization test that failed during the migration and asking Lena why. Once, in month 7, the whole Inventory team spent a week on the module as an ensemble, all of them working on the seam at one screen, an Encore choice drawn from practitioner writing such as Woody Zuill and Kevin Meadows’s Software Teaming, with no controlled study behind it.

The guide is worth describing, because it was the most durable of the four remedies. A stage manager keeps a prompt book: the script annotated with every cue, every blocking change and the reason behind each, so that the show can be run by someone who was not at the rehearsals. Jonas’s guide was a prompt book for the barcode core. It recorded each of the 19 kept behaviors with the team that relied on it, the history of each format, and the questions reviewers had asked most often, with Lena’s answers. The analogy breaks at the moment of reading. A prompt book is read during the show by someone who knows they need it; a guide to code is needed by whoever is about to change the code, often without knowing it, so the guide was linked from the template of every pull request that touched the core.

Section 2.3 cautioned that truck-factor algorithms measure authorship, which is only a proxy for understanding, so Encore measured the truck factor the same way before and after, in two parts: the knowledge map of authorship and review from version control, and a simple test of understanding, whether each person had made or reviewed a change to the core in the last quarter without needing Lena. By month 12 four people passed both: Lena, Ines, Jonas and the second engineer paired with Lena. The plan’s definition of done had asked for at least three. The best evidence, though, was behavioral. A change to the core had taken a median of eleven days from first commit to production in Section 2.3’s analysis, mostly in the queue for one reviewer; by launch it took six.

The format change as a campaign

Every Y2K program began with an inventory, and Thomas recounts how often that step failed: in one FTSE 100 group, none of 60 departmental IT directors “was confident that they could rebuild their key systems without introducing errors.” Encore’s inventory of everything that read the old identity came from its code index and the change-coupling analysis of Section 2.3, and it found 60 call sites in 11 repositories owned by seven teams. Section 5.2 described how an agent generated one change per call site and a ladder of checks decided which landed, and Section 5.3 told of Venue Entry’s rejection of a change that was exactly wrong for a scanner with no network. This chapter adds only the outcome. Fifty-six of the 60 had landed by month 8, six weeks after the campaign began. The legacy parser was then deprecated, with an allowlist naming the four callers that remained (Section 2.5): Venue Entry’s offline path, which needed a new design; the Support Tools console; a Sales Analytics job; and the Finance platform team’s reconciliation report. The long tail took seven more months, which is the usual shape of such work, because each of the last callers had a reason of its own to be last.

The partner side ran on a different clock. Partners had been told in month 2 that reissued tickets would carry a new, opaque identity, and the old format was accepted on the partner API until month 14, which kept the promise of 12 months’ notice. Under Verified Resale partners never see a barcode at all. They receive an opaque ticket_id through the API, as Section 2.5 and Section 3.3 explained, so the notice protected only integrations left over from Partner Listings. Rowmate’s was one of them. Its parser of the old event-code prefix had survived the move to rotating barcodes in year eight unnoticed, because rotation left the identity alone, and it was found and replaced during Rowmate’s sandbox certification in month 9, where the sandbox’s deliberately different ticket_id format made it fail on Rowmate’s own schedule.

Y2K supplied one more practice, and it was the most useful thing on the campaign’s single page. Thomas describes the four rules for how software should handle dates that the British Standards Institution published in January 1997, quotes the fourth of them, “Year 2000 must be recognized as a leap year”, and adds that the rules “were increasingly referenced in contracts and audit standards.” Such a definition turns “are you compliant?” into a checklist a team can sign. Encore wrote its own, called it reissue-ready, and asked each of the 12 teams to sign its copy. Inventory’s read:

REISSUE-READY (each of the 12 teams signs its own copy)
1. No code we own derives a ticket's identity from order and seat
2. No code we own parses a barcode; event and holder come from the identity lookup
3. Every consumer we own of TicketReissued and TicketRevoked holds a message contract
4. A ticket may be reissued more than once; a retried reissue with the same key is a no-op
5. Old-format barcodes keep working until their events have passed
6. Our part of the reissue drill passed on the test event
7. Copies of our code running outside our repositories are listed, with owner and version
Team: Inventory      Signed: Lena, Ines      Month: 10 (line 7 added month 11)

The seventh line was not in the first version, and Section 11.4 explains how it got there.

Debt paid and debt taken

The most widely used Y2K remedy was a deliberate debt. Windowing kept years as two digits and interpreted them through a pivot: in the example Thomas gives, “years 00 to 19 would be treated as 2000 to 2019 whereas years 20 to 99 would be treated as 1920 to 1999.” It was cheaper and simpler to test than expanding dates to four digits, and the last of the disadvantages Thomas lists is the one that matters here: “it was not a permanent solution.” The Senate’s final report warned that remediated software “may encounter problems with the approach of the ‘pivot year’”, and that organizations “must undertake permanent software fixes where temporary patches were utilized.” Windowing was technical debt in Cunningham’s sense, whose metaphor is Chapter 2’s touchstone, and it came with a built-in trigger: the pivot year. Where the record of the trigger was kept, the debt was repaid; where it was lost, the trigger fired anyway.

Encore paid one debt and took another, and wrote both down. The debt paid was INV-DEBT-017 from Section 2.3, the 19 public issue variants, whose trigger required repayment before the first production reissue; Section 11.4 reports how that trigger was moved, in writing, and the debt repaid. The debt taken was Encore’s own pivot year. Tickets sold before launch carry the old, derived identity, and they must keep working until their events have passed, so every gate validator and the issuer itself carry two paths until the last of those events. The team entered it in the register in month 6, before the campaign began:

- id: INV-DEBT-031
  where: venue-entry validator; ticketing/barcode/LegacyFormat.kt
  what: old-format tickets (identity derived from order and seat) still accepted
  why taken: tickets sold before launch must work until their events pass
  interest: two validation paths at every gate; every scanner test runs twice
  owner: Venue Entry (validator), Inventory (format)
  trigger: the day after the last event holding an old-format ticket (month 29)
  also: partner API accepts old-format references until month 14 (12-month notice)
  repay: delete both old paths and their replay tests in one change
  decided: month 6, Year 9

The trigger is a date computed from data, the latest event for which an old-format ticket was sold, and it is written where the owners will see it every quarter. That is all the Y2K record asks of a temporary fix. It does not ask that the fix be avoided, only that its expiry be written down by someone who will still be there when it arrives, which is why the entry names teams. Figure 11.4 sets the two pivot years side by side.

Two pivot years Schematic in two parts. Top, Y2K windowing in the example Martyn Thomas gives: a year stored as two digits, 00 to 99, is read through a pivot at 20. Stored years 00 to 19 are read as 2000 to 2019, and 20 to 99 as 1920 to 1999, so the stored order and the century order no longer match. Cheaper than expanding to four digits and simpler to test, it was not a permanent solution: the pivot year is its built-in trigger. Bottom, Encore's pivot year on a month scale from 0 to 30. The partner API accepts old-format references from the notice in month 2 until month 14, twelve months' notice. INV-DEBT-031 is entered in the debt register in month 6. From launch in month 12, old-format tickets (identity derived from order and seat) are still accepted, so every gate validator and the issuer carry two paths; the engineering review at month 24 finds the debt still open, as planned. The trigger is the day after the last event holding an old-format ticket, month 29, when both old paths and their replay tests are deleted in one change. Y2K WINDOWING · THOMAS'S EXAMPLE · SCHEMATIC ENCORE'S PIVOT YEAR · INV-DEBT-031 · MONTHS 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 PIVOT 20 DEBT ENTERED LAUNCH REVIEW TRIGGER · MONTH 29 12 months' notice Old-format tickets still accepted STORED two digits READ AS through the pivot 00 – 19 20 – 99 1920 – 1999 2000 – 2019 not a permanent solution: the pivot year is the trigger Partner API old format accepted Gates + issuer two paths, old and new

Figure 11.4 — Two pivot years. Windowing read two-digit years through a pivot, and Encore accepts old-format tickets until a dated trigger; both are temporary fixes with a built-in trigger, and the trigger fires whether or not anyone kept a record of it.

Recap. Reading the characterization suite found 31 unspecified behaviors, of which 19 were load-bearing and three were bugs, so most of the work of changing old code lay in learning what it did. The seam was information hiding added late, as Y2K’s root cause suggests, and a parallel run in production checked it against the present. Knowledge of the core spread to four people by pairing, an ensemble week and a guide kept like a prompt book. The format campaign moved 56 of 60 call sites in six weeks and the rest in seven months, under a written definition of done; the old format stays, as debt with a dated trigger.

Exercise 11.2. Write the first characterization test for the oldest code your next feature must change, before any new code. Then, for the first three surprising behaviors your tests reveal, find out who depends on each, and decide in writing whether it is a bug, a promise, or something to delete with the old path.

11.3 Building the New Parts

Twenty-three days to the first deploy

The Resale service was created from the golden-path template on the day the team formed, and in the sense that matters to the template it existed from that day: a repository, a pipeline, a registry entry and a scorecard. Its first production deploy that served real traffic came 23 working days later, and Section 6.2 mapped why in its Figure 6.3. Fifteen of the 23 were waits: nine for partner credentials, which could then be issued only through two tickets to Security Engineering, and six for a security review of a webhook sender that the team should never have had to write. Neither wait was anyone’s failure. The platform had no road for either need, so the team took the only roads there were. The platform group built the two missing roads as the partner-facing path, and the second partner-facing service to start after shared webhook delivery reached general availability, the Festivals team’s festival feed, reached its first production deploy in four working days (Section 6.5). The Resale service was born on the golden path and still waited three weeks, because the path ended before its needs did, and the cost of those weeks was paid once.

Once deployed, the service grew as Section 3.2 described, from a walking skeleton that ran one test fan’s resale from a stub of Rowmate’s integration through Resale, Orders & Payments and Inventory to a revocation list on one venue’s scanners and a new ticket in the app. The skeleton ran end to end in month 9, as planned, with none of the plan’s slack left: it went almost entirely on the step that crossed into the barcode core, where the entry point had to be written behind characterization tests that did not yet cover reissue. That is the skeleton doing its job: it forced the hardest cross-team question, how a reissue enters the barcode core, into month 5, when it was cheap to answer.

Contracts, simulators and the invariants

Chapter 3 taught the testing of Verified Resale; this subsection gives the counts. By launch the contract broker held 26 consumer contracts against the services on the resale path, nine of them message contracts on events; the Inventory team’s reissue endpoint alone had three consumers within a month of existing, and the broker showed all of them on every pull request Inventory opened. One of the simulators of Figure 3.5 caught the most expensive bug of the build. In the PSP simulator’s fee-split mode, a split rejected by the provider left the resale saga waiting for a confirmation that would never come, holding the buyer’s payment authorization and the seller’s listing for the full hold window. No end-to-end test would have found it, because the real provider’s sandbox never rejects a split. The fix was a compensating step in the saga, and the simulator mode stayed in the suite.

Section 1.3 called the five fairness invariants an engineering contract, and Section 3.3 promised that they would run as property tests on every build. Here they are, in the file the Resale team hosts and every contributing team’s build runs:

// Runs in CI for Resale, Inventory, Orders & Payments, Partner APIs and the rules engine.
// Changed only through the host team's review (Section 8.4).
class FairnessInvariants {
    private val world = ResaleWorld.inMemory()   // real rules engine; simulated neighbors

    @Property fun `Partners never buy primary tickets`(@ForAll("partnerCalls") s: Script) =
        world.run(s).primaryPurchasesBy(PARTNER).isEmpty()
    @Property fun `Only tickets bought through the Gate, within the per-fan limit, can be listed`(@ForAll("listings") s: Script) =
        world.run(s).listings.all { it.boughtThroughGate && it.withinPerFanLimit }
    @Property fun `Organizer caps and rules can only tighten`(@ForAll("ruleEdits") s: Script) =
        world.run(s).ruleHistory.zipWithNext().all { (before, after) -> after.isNoLooserThan(before) }
    @Property fun `Every resale buyer passes Bot Screening and the per-buyer limits`(@ForAll("purchases") s: Script) =
        world.run(s).resales.all { it.buyerScreened && it.withinPerBuyerLimit }
    @Property fun `Partners never see sales-velocity data`(@ForAll("partnerReads") s: Script) =
        world.run(s).partnerResponses.none { it.revealsVelocity() }
}

Each test’s name is the invariant, word for word, so that a failing build reports the broken promise in the language of the product. Between month 9 and launch the five properties failed 11 times on pull requests. Seven failures were in tests or generators, and four were real: two rule contributions that could, in combination, loosen a limit, one listing path that skipped the per-fan check for tickets bought in a presale, and one partner endpoint whose error message, on a rejected listing, quoted a remaining-inventory count precise enough to estimate velocity. Each was fixed before it merged. None of the four came from the code the team was most worried about.

Partners, tenants and entitlements have their homes elsewhere: organizer rules are typed configuration built on the entitlement machinery of Section 7.3, and the partner program is Section 8.3’s. One part of that program belongs in a chapter about tests. The certification suite is versioned with the partner API, and each partner runs it against the sandbox from its own code, again on every major version and after any change to how it handles listings or payments (Section 8.3). It is the only test of Verified Resale that runs in someone else’s code. It is where Rowmate’s old prefix parser was found, and where Tessera’s stale price cache was caught when an organizer tightened a cap in the middle of a run (Section 8.3).

Six weeks on trunk

Verified Resale’s pieces went to trunk behind release flags from month 4, and for the last six weeks before launch the complete flow ran dark in production behind one of them, resale-verified-flow, as Section 4.5 described; in the fan app the Resale module shipped dark on eight release trains before any fan saw it. Cross-team reviews, which had stalled for two days early on, settled under the latency targets Section 4.3 describes once the team made its changes smaller.

The detail of the loop that matters for the rest of this chapter is a field. Every release flag Verified Resale created, 17 across the services and the app, was created with an owner and a removal date, in the way Section 4.5 teaches. The date for resale-verified-flow, the main one, was month 14, after the second launch cohort. Writing a date into a flag costs nothing on the day it is created, and Section 11.4 shows what it was worth a year later.

The assistant, briefly

The organizer rule assistant, Verified Resale’s one AI feature, was designed in the Products book’s Section 6.5 and evaluated by the criteria and golden set that Section 3.5 describes. For this chapter its one engineering point is structural. The assistant drafts a typed rule set, and the type cannot express a loosening, so invariant 3 is enforced by the schema and checked by the property test above, whatever the model says; the organizer confirms every draft before it applies.

Recap. The Resale service was born on the golden path and still waited 23 days, because the path ended before its needs did; the platform closed the gap once, for everyone. The skeleton surfaced the hardest cross-team question early. Contracts, simulators and five properties named after the invariants caught real defects before they merged, and the certification suite carried the same discipline into partners’ code. Every flag was born with a removal date.

Exercise 11.3. For one feature you are building, write each invariant it must never break as a sentence a customer would understand, then name the test that would fail if it broke. Where no test exists, write the property’s name and its generator before writing the code it protects.

11.4 Shipping Across Forty Teams

Integration without a big bang

Chapter 1 named the big-bang integration as one of the ways large changes fail, with TSB’s migration of 2018 as the public case, and Encore’s defense was the ordinary one: every team’s work met every other team’s on trunk, daily, behind flags, from the walking skeleton onward. The defense worked. No integration between two services on the resale path surprised anyone after month 9, because contracts, simulators and the hourly synthetic resale had exercised each of them long before launch. Chapter 6 told the platform’s part whole: the partner credentials and webhook delivery that the Resale team would otherwise have built for itself were built once by the platform group and adopted by every partner-facing team, and the three home-grown webhook senders were retired in a campaign that ran from month 9 to month 11.

One integration did surprise everyone, and it came from outside the repositories. In month 11 Encore ran a reissue drill on a test event at each design-partner venue, an exercise in the spirit of the clock-forward tests that Y2K programs ran on their plants and the rollover drills the Senate report mentions: scripted fans resold tickets to one another, and real devices at real gates tried both the old and the new barcodes. At one venue, an arena whose turnstiles came from an access-control vendor, the turnstiles refused the buyer’s new ticket. The vendor’s firmware did not use Venue Entry’s validator. It had been built years earlier to an Encore integration specification, maintained through the Venue Integrations team’s connector, and it computed a ticket’s identity from the order and the seat, exactly as the year-one code had done. To that firmware, a revoked ticket for seat B-14 meant that seat B-14 was revoked, whoever now held it. The code index had not found it because it was not Encore’s code, and the reissue-ready definition had asked each team only about code it owned.

The GAO’s guide had warned of exactly this in 1997: agencies “must not only test Year 2000 compliance of individual applications, but also the complex interactions between scores of converted or replaced computer platforms … and interfaces.” A touring crew learns the same thing from the advance, the visit before the show when the production manager walks the venue and checks the house’s own equipment against the rider, because the band’s gear has been rehearsed and the house’s has not. The analogy breaks at the scale. An advance covers one building, while a revocation list is consulted by every device that has ever been integrated with the format, and Encore had no list of those devices in one place. The vendor shipped corrected firmware in four weeks, and Venue Entry agreed that the arena’s events would not open to resale until it was installed, which it was, a week before launch. And the reissue-ready definition gained its seventh line, which asked every team to list the copies of its logic running outside its repositories, with owner and version. The Venue Integrations team’s list turned up two more vendor integrations built to the same old specification, at venues not yet in any launch cohort.

The dependency that blocked

Chapter 1 predicted a dependency that would block for a quarter, and Section 10.2 named Verified Resale’s: VR-01, the void and reissue in one step inside the barcode core, whose entry Chapter 1 printed. It did hold the bet for about a quarter, though not in the way the plan had feared. The fear was that the work would never reach the top of Inventory’s backlog. It reached the top at once, because the ADR had made it Inventory’s, and from month 4 Resale and Inventory worked on it together, as Section 9.2 describes. What held it was the order Feathers’s procedure imposes and the queue in front of one person. Nothing in the core could change until the core was under test, the characterization suite reached CI only in month 6, and in the first weeks every change that touched the core waited for Lena, at a median of 11 days from first commit to production (Section 10.1).

The instrument that showed how long it was taking was work item age, which Section 10.5 introduces: for a dependency, the time since it was requested. Encore charts the age of every open cross-team dependency against the ages at which past dependencies of the same kind closed. By day 20 VR-01 had passed the 85th percentile of that history, and the chart showed it at the next fortnightly operating review. For three more weeks nobody changed anything, which is the honest part of the story. The chart could not say whether the item was stuck or merely large, and each team read it as the other’s to act on: Resale assumed Inventory had the matter in hand, and Inventory assumed that a collaboration meant Resale would say if it was not. The review then made two decisions. Inventory set aside review time each day and widened the circle of reviewers through the pairing already under way (Section 4.3), and the item was split, so that the narrow entry point the walking skeleton needed could land behind the first characterization tests while the full operation waited for the rest. Inventory published reissue with a contract test in month 7, 61 working days after the dependency was requested, and from then on Resale consumed it as a service.

The lesson the teams drew was about the chart. A dependency’s age is the earliest warning a plan has, and it is useful only if the plan says, before any item is stuck, who acts when an item crosses the line; Encore’s plans now name that person for each coordinated dependency. Of the other five, Mobile App’s came closest to trouble, since the reissue-aware ticket view had to ride the weekly release train and old app versions linger; the soft floor on app versions was raised three months before launch, as the Products book’s Section 8.2 describes, so that nearly every active phone could show a reissued ticket by launch day.

The first resold ticket, through the code

The first resold ticket was a Lyon fan’s: a seat at a sold-out Saturday concert promoted by one of the design-partner organizers, with a face value of €80. The Products book’s Section 10.4 follows it through the product, as the fans and the partner experienced it. Figure 11.5 follows it through the code, naming at each step the service and operation involved, the team that owns it, and the test that would fail if that step broke.

Step When Code path Owner Test that protects it
1 Tuesday evening Rowmate calls POST /listings with validateOnly, then creates the listing Partner APIs, Resale Rowmate’s certification suite; listing contract
2 Tuesday evening Rules engine checks Gate purchase, per-fan limit, window and the €80 cap Resale Invariants 2 and 3 as properties
3 Thursday lunchtime Buyer scored by Bot Screening; per-buyer limit applied Bot Screening, Resale Invariant 4; shadow mode for new rules
4 Thursday lunchtime Resale purchase saga; €88 through the primary PSP’s split product Orders & Payments PSP simulator in split mode; the money property
5 Thursday lunchtime reissue with an idempotency key; TicketReissued, TicketRevoked Inventory Stateful reissue property; characterization suite
6 Within 30 s Revocation reaches the venue’s lists; scanners sync Venue Entry Hourly synthetic resale with a scripted scanner
7 Minutes later Seller’s pass voided, buyer’s ticket in the app, pass where offered Fan Messaging, Mobile App Wallet simulator’s stale-pass mode; app ticket-view tests
8 Minutes later Signed listing.sold webhook to Rowmate, which fetches the result Partner APIs (platform delivery) Partner simulator’s duplicate and late webhooks
9 Thursday ResaleCompleted metered: €3.20, €2.40, €2.40; ledger reconciles Billing & Payouts Message contract on ResaleCompleted; daily reconciliation
10 Saturday evening Buyer’s rotating code validated at the gate, offline if need be Venue Entry The resale end-to-end journey; the reissue drill

Figure 11.5 — The first resold ticket, through the code. Ten steps, nine owning teams, and a test for every step; the seller’s €80, paid five days later by Billing & Payouts, is covered by the money property of step 4.

Read down the last column and a pattern appears that no single chapter could show. Seven of the ten tests run inside Encore’s own repositories on every build. Three are different. Rowmate’s certification suite runs in a partner’s code; the hourly synthetic resale runs in production; and the reissue drill ran on real devices at real gates. Those three are the only tests on the route that reach code Encore does not build or environments it does not control, and the drill was the only one of all ten that could have found the turnstile firmware. A test strategy for a change like this one is mostly a matter of pushing tests down to where they are cheap, as Chapter 3 teaches, and then deliberately keeping the few that reach outward, because the parts of the route nobody owns in a repository are where a change’s surprises live.

Cleaning up

The Senate’s final report closes its domestic findings with an item that was easy to skim in February 2000: organizations “may need to address non-mission critical systems where repairs were postponed”, and “internationally, most fixes were temporary clock rollbacks that ultimately must be fixed permanently.” It is the long tail, named by the official record of a change everyone remembers as finished on 1 January. Uber’s published account shows the scale of the problem in our own field. Its engineers built a tool called Piranha to remove stale feature flags automatically, described it at the International Conference on Software Engineering in 2020, and reported on the company’s blog that it had been used to remove around two thousand stale flags.

Encore had funded its cleanup in the month-4 plan, and it went as follows. Of the 17 release flags, 15 were removed by month 16. resale-verified-flow, due at month 14, came out in month 15, two weeks late, because its removal waited for Tessera’s certification; Section 4.5 records what its deletion took with it, and what went with Inventory’s ticket-identity-opaque. Two were kept deliberately as operational kill switches, resale on or off per market, owned and documented as permanent, a legitimate change of kind because it was recorded as one. INV-DEBT-017 did not meet its own trigger. It said the 19 issue variants would be collapsed before the first production reissue, but in month 11 Inventory judged that collapsing them weeks before launch was a greater risk than carrying them, since Reissue reached the old issuer only through the pinch point the tests covered, and it re-dated the entry to month 13 in the register, with that reason beside the new date. The variants were collapsed into one issue() with a typed request in month 13, and the old check-digit path went with them. The last four callers of the legacy parser moved by month 15, the Finance platform team’s reconciliation report last, and the parser was deleted the same week. One piece of the old world remains, on purpose: INV-DEBT-031, the acceptance of old-format tickets at the gates, waits for its trigger in month 29.

Recap. Integration on trunk worked as designed, and the one surprise came from code outside every repository, found only by a rehearsal on real devices. The dependency that blocked, reissue inside the barcode core, was visible on an aging chart three weeks before anyone acted, and it closed after a quarter once review time was reserved and the item was split. Traced through the code, the first resold ticket crossed ten steps and nine teams, and only the three tests that reached outside Encore’s repositories could have found what the others missed. Cleanup was funded in advance and finished, except for the one debt whose trigger has not yet arrived.

Exercise 11.4. For one transaction of your product, list every place its code runs: your own services, and also a partner’s system, a device, a vendor’s firmware, an old app version. For each place outside your repositories, name the test, drill or inventory that would find a fault there before a customer did, and mark the places where the honest answer is none.

11.5 What the Engineering System Learned

The review at month 24

The Products book’s Section 10.5 records the review of the bet at month 18, about six months after launch, which asked whether Verified Resale had worked as a product and decided to widen it. The engineering review came six months later, at month 24, a year after launch, for a reason the two reviews’ subjects explain. Product metrics answer within months, while the effects of a change on a codebase, a platform and an organization take a year or more to show, and a review held too early would have measured only the construction. It compared the system with the plan of month 4 and the definition of done of Section 2.4, under one rule above the rest: every number had to be measured by the same instrument as its baseline, or it was left out. A developer survey with new questions, or a truck factor computed by a different algorithm, cannot show change, only difference.

The codebase a year later

Meir Lehman’s second law of software evolution, which Chapter 1 quoted, holds that a program’s complexity increases as it is changed “unless work is done to maintain or reduce it.” The month-24 review is, among other things, a test of that clause, and Figure 11.6 sets out the result for the barcode core and the new code beside it.

Measure Month 4 Month 12 (launch) Month 24
Barcode core, lines of Kotlin about 11,000 about 12,300 about 7,600
Public issue variants 19 19 1
Tests on the core a handful 1,400 characterization cases, plus properties about 450 specification tests; replay kept for the old format
Median days, first commit to production, for a core change 11 6 2
Truck factor, by knowledge map and review test 1 4 4
Callers parsing the barcode 60 2 0
Verified Resale release flags — 17 2, kept as kill switches
Top hotspot in resale code — Resale purchase saga Rules engine

Figure 11.6 — The codebase at month 24, measured by the same instruments as at month 4. The core grew during the change and shrank after it; the hotspot moved into the new code.

The core’s line count tells Lehman’s story in two movements. It grew by about 1,300 lines during the change, as Reissue was sprouted beside the issuer and the seams were cut, and it fell by about 4,700 after launch, when the 19 variants collapsed into one and the old check-digit path was deleted. Both movements were planned, and the second happened only because it had been funded. The median time for a change to the core fell from eleven days to two, the same as the rest of Seat Inventory & Ticketing, the most useful single measure of a repaired legacy core. Lena still works on it. She reviewed fewer than one in five of its changes in the last quarter before the review, where at month 4 she had reviewed all of them.

The new code is where Lehman’s law reasserts itself. The hottest file in the resale code at month 24 was the rules engine’s evaluator, which six teams now contribute to (Section 8.4), and its complexity had risen with every contributed rule type. The festival-pass cap kind alone added a third to the evaluator’s branching. Six teams adding reasonable rules to one evaluator is exactly the condition under which the second law operates, and the review’s response was the one the law’s final clause implies: an item in the debt register, owned by the host team, with a trigger set at the next rule type contributed, to restructure the evaluator before it grows further.

The platform a year later

What the platform absorbed from this change is easy to state, since Chapter 6 tells how. Partner credentials became self-service, webhook delivery became a platform capability with one implementation instead of three, and both were packaged as the partner-facing path. By month 24 three more partner-facing services had been created on it, and their median time to first production deploy was five working days, against the Resale team’s 23. Section 6.5 cautioned that one service is not a trend; three is the beginning of one. The rules engine is the change’s other contribution to shared code, an InnerSource project with six contributing teams, Resale as host with Orders & Payments, Inventory, Trust & Safety, Pricing & Presales and Festivals, which is how festival passes reached it.

The quarterly developer survey of Section 1.2, its ten core items unchanged and still answered by about two thirds of engineers, gave the review its other comparison. “Building plumbing the platform should provide, such as partner webhooks” had been one of the six commonest answers in Figure 1.3, named by 24%, and by month 24 it had dropped out of the six. Waiting for CI on the shared repository had not. Test selection and a remote cache had brought the build of encore-core down from 31 minutes to nine, as Section 4.2 describes, but engineers who worked in the oldest repository still ranked its feedback time among their pains, and the review said plainly that the change had made the barcode core easier to change without making its repository faster to build.

The teams a year later

The organization belongs to the review of the bet, and the Products book’s Section 10.5 records it: the Resale team, grown from its first seven engineers to 15 people by month 18, split after that review along the line between the fan and partner flows and the organizer’s rules, as its reteaming plan had said from the start (Section 9.2). What the engineering review adds is what the split and its neighbors did to the flow of knowledge. Two of the original volunteers took up the reteaming FAQ’s promise and returned to Orders & Payments after a year, taking a working knowledge of the resale saga with them, and the staff engineer lent from Orders & Payments returned on schedule after nine months.

The interaction modes changed in the way Section 9.2 recommends, from collaboration toward service. Inventory and Resale worked as one group in the barcode core from month 4 to month 7, and since then Resale has consumed reissue as an operation behind a published interface with an owner, an objective and a contract test, without meetings; at month 24 neither team could name a change to the interface that had needed one. Planning changed in one small, durable way, and it came from the column a Resale engineer added to the dependency list in month 4. Encore’s quarterly plan template now asks, for every cross-team dependency, for the age of the oldest code it touches and the number of people who understand that code, and planners read those columns before the estimates.

What AI changed and what it did not

Where assistance measurably helped this change is narrow, and Chapter 5 documents it: in the format campaign an agent generated the changes for 60 call sites, and a ladder of checks caught what it got wrong. The published accounts have the same shape. Google’s 2025 paper on migrations with large language models reports that about 74% of the changes and 69% of the edits in 39 migrations were generated by a model, with time savings the developers estimated, and Airbnb’s account of moving nearly 3,500 test files in six weeks against an estimate of a year and a half rests on the existing test suite to judge each change. In every published success of this kind, the machine wrote the edits, and tests, compilers and human owners decided which ones landed.

Where assistance did not change the work is where most of the work was. The four callers that took seven months were not slow because their edits were hard to write. They were slow because each needed a decision only its owner could make, such as how a scanner with no network should learn which tickets are valid. The 31 behaviors in the characterization suite were classified by people who asked Lena, and the turnstile firmware was found by a drill. Engineers used assistants constantly to read unfamiliar code and draft tests, and reported that it helped, but no controlled study measures how much assistance speeds the understanding of legacy code, and Encore’s own evidence on that point is testimony.

Y2K offers a precedent whose outcome is known, which is more than any recent anecdote can claim. Thomas reports that repair costs estimated in 1996 at “$1 to $2 per line” fell to “only a few pennies per line because automated Y2K tools became extremely accurate and efficient at fixing the code”, and that organizations which started late “were able to make up the lost time.” Automated transformation made the change itself cheap, and testing was still expected to take more than half of the money. That is the honest forecast for AI-assisted change: the edit gets cheaper, while the proving, the deciding and the finding of what the inventory missed stay where they were.

The Crisis Moved

The argument about Y2K began almost as soon as the date passed, and it has two serious sides. The Senate committee concluded that “the level of effort was justified, and the expenditures of the public and private sectors were indeed necessary”, and Thomas called the episode “a signal event: a near miss”. John Quiggin, writing in 2005, argued the opposite: that “most money spent specifically on Y2K compliance exercises was wasted”, that a strategy of fixing failures as they appeared would have served, and that countries such as Italy, which did far less, suffered little. The Senate’s own report holds a rebuttal: late countries “greatly benefited from the lessons learned by countries leading the world” and often “simply rolled back computer clocks”. Neither side can win outright, because the counterfactual cannot be audited. A prevention that succeeds looks exactly like one that was not needed. The only defense is the one Encore’s review used: write down beforehand what failure would look like, so that its absence can be judged. Encore’s plan said, in month 4, that a failed reissue would show as a revoked ticket admitted or a valid one refused at a gate. In its first year there was neither, and the drill had found the one place that would have produced the second.

Chapter 1 opened at Garmisch in 1968, with a conference that named a gap between what organizations needed to build and what their methods could build, and promised that this last chapter would return to ask how much of the gap has closed. One change at Encore gives a partial answer. Every practice this chapter has reported, from characterization tests and seams to contracts, platforms, flow measures and agent-written migrations, closes part of the gap, and each worked here roughly as its chapter promised. And the gap was still there, because it had moved. It was no longer in writing code that works, which Encore’s teams did well and its tools did faster. It was in knowing what the old code did and who depended on it, in the one queue a forecast could not see, in firmware that no repository held, and in the patience to delete what had been replaced. The crisis Garmisch named has not ended. It has moved from construction to understanding, and from the program to the organization that changes it.

I have come to think that this is what an engineering system is for. Y2K’s root cause was a representation decided once and never hidden, and its remedy was mostly inventory and testing, by organizations discovering under a deadline how little they knew about their own systems. Encore’s barcode identity was the same kind of decision, and Encore had the time and the practices to change it without a crisis. Neither the time nor the practices came free. They came from an organization that had built, deliberately, the second system this book has been about, the one that builds the first. That system does not make large changes easy. It makes them ordinary, which at 400 engineers is the most that can be asked of it.

The Resilience book picks up where this one stops, with what it takes to keep the system standing when a million fans arrive at once, and its Chapter 9 covers the readiness review that every launch, including this one, has to pass. The Operations book takes the morning itself, when the system and the people who run it meet the load together.

Recap. The engineering review came a year after launch, with every number measured by its baseline instrument. The barcode core grew during the change and shrank after it, as the second law allows when work is done, while the new rules engine became the hotspot. The platform absorbed what the change needed, the Resale team split as planned, and AI made the edits cheap without making the understanding cheaper. The crisis that Garmisch named has moved from writing code to understanding and changing it, and an engineering system is how an organization keeps up with it.

Exercise 11.5. Write a review of a large change your organization finished at least a year ago, against what was planned and measured by the same instruments as the baseline. Before reading any results, write down what failure would have looked like, so that its absence can be judged. Then list what the change left behind (flags, old paths, compatibility code, debt with triggers), with an owner and the date on which each will go; any item that has neither is the review’s first finding.

Kata: The Lease That Changes Hands

Your brief: “Lintelby.” Lintelby is a software-as-a-service company for long-term residential lettings, with 220 engineers in 25 teams; about 4,000 letting agencies in six EU countries run some 900,000 active leases on it. Tenants who must leave early today find a replacement, end the lease and sign a new one, which takes weeks and loses deposits in transit. The board wants lease transfer: a departing tenant hands the lease to a new tenant found through a partner letting agent, the landlord approves, the deposit moves, and the new tenant is referenced and screened like any other. The tenancy core was written in the company’s first year, eleven years ago. Its lease identifier is built from the property reference and the start date, and 70 places in Lintelby’s code, plus the integrations of 25 partner agents and three deposit-protection schemes, read the property reference out of it. One engineer understands the core well, and she begins parental leave in three months. The deposit ledger must reconcile to the cent every night, partner terms promise nine months’ notice of breaking changes, and the busiest moving season of the year begins in eight months.

Deliverables:

  1. The change plan — One page: what must be true of the code once a lease can change hands, written as properties; who owns each part; why you will repair the tenancy core in place, and what would make you switch to a strangler; and the cleanup budget after launch.
  2. The characterization and knowledge plan — The replay or survey that will characterize the core, how you will classify each surprise as bug, promise or deletable, and how you will raise the core’s truck factor to at least four, measured the same way before and after.
  3. The dependency map and forecast — The cross-team dependencies at the start, each with the age of the oldest code it touches, and for each the kind of cut that could remove it; a Monte Carlo forecast from supplied throughput, stated as a percentile; and the aging chart you will use for what the forecast cannot see.
  4. The identity change — The new lease identity, an inventory of every reader inside and outside your repositories, a definition of done each team signs, the notice timeline for partners and schemes, and a dated debt-register entry for the old identity you must keep accepting.
  5. The contract and invariant tests — Each invariant (for example, one live lease per tenancy, deposits conserved to the cent, a landlord’s approval before any transfer) as a named property, the contracts and simulators on the transfer path, and the rehearsal that will exercise what runs outside your code.
  6. The one-year review template — The measures you will compare, each with its baseline instrument, a statement written now of what failure would look like, and the list of everything the change will leave behind with its removal date.

Rubric:

Criterion The question to ask yourself
Old code first Does the plan find out what the tenancy core does, and who depends on it, before changing it?
Inventory reach Would your inventory find a reader in a deposit scheme’s software or a partner’s integration, or only in your own repositories?
Invariants as tests Could a single pull request that breaks a tenant’s or landlord’s promise merge without a named test failing?
Honest forecast Does the forecast say what it cannot see, and does the plan say who acts when an item ages past the line?
Finishing Does every flag, old path and accepted old identity have an owner and a date, and is the cleanup funded?
Coherence Do the plan, the tests, the rollout and the review describe one change, so that the review could be written from the plan alone?

Where you now stand

You can now take one change through an engineering system of forty teams: write down what the code must be afterward, decide ownership with its consequences in view, cut dependencies by changing who does the work, characterize old code and read what the characterization says, cut a seam and check it against production, spread knowledge beyond one person, run the change as a campaign with a definition of done, test the invariants, rehearse what lies outside your code, finish the cleanup, and review the result a year later by the instruments you began with. That completes the Software book. The Resilience book takes up what such a system must withstand under load, and the Operations book what it takes to run it on the morning that matters.

References