an open-source simulation, built on the jetway airline messaging gateway · Go · MIT

One day of the world's airline network, on the wire.

Every carrier is its own reservations and departure-control system. Three distribution systems sell into them. One switch relays between them. Nothing moves except a real Type B or EDIFACT message over a real socket, and the globe draws only what the messages say.

jetway
Everything under the globe is jetway, an open-source gateway that is also a GDS, a seat inventory, a departure-control system, a fare engine, a datalink and AFTN endpoint and a switch. Type B/AIRIMP, EDIFACT/PADIS, NDC, MATIP; PNRs, tickets, EMDs, queues, IROPS; PNL/ADL, bags, loadsheets, MVT, ACARS, flight plans. Meant to be run for real; wholesky is what proves it can.
The globe turning once through the synthetic world's evening: eight and a half thousand aircraft over the Americas, the Atlantic, Europe, Asia and the Pacific, with the bar reading what the aircraft in the air were sold for and what has been bought since boot

The whole sky at 21:50 on the synthetic day, once round: 8,500 aircraft over 518 carriers' networks. Every aircraft is a movement message that crossed the switch; the bar at the top is what the aircraft in the air were sold for.

518carriers, each a reservations system and a DCS
103,688flights a day in the synthetic world
22,889US flights on the recorded day, from BTS
16,000/smessages through one switch at the departure banks
1.4Mpre-sold bookings in Postgres for the recorded day
$719Mthe recorded day, priced at a synthetic tariff
why it is different

Not an animation of aviation. The systems, running.

Flight-tracking sites draw positions; airline simulators draw aircraft. wholesky draws nothing it was not told over the industry's own wire. Under the globe is a working airline IT stack, and that changes what the picture means.

Every mark is a message

An aircraft appears because a Type B movement message crossed the switch and lands because the arrival did. A passenger is on a name list because reservations sent it to the airport. A red halo is a closed airport sized by the real queue items an agent now has to rework. If the message did not happen, neither did the picture.

The state separation is real

Every carrier has its own book of record, its own locator space, its own seat inventory and its own console. The distribution systems hold their own copies under their own locators. Nothing is shared but the wire. A region can die and rejoin; the switch cannot tell whether a peer is across a loopback or across a continent.

The engine is meant for real

jetway is not a prop built for the demo. It is a gateway, GDS, DCS and switch with a spool, a lease, retention, rate limits, metrics and a production plan, and everything sim-only stays in wholesky. When the world finds a bug, the fix goes upstream with a test that was watched to fail.

It says what it actually knows

Most airline formats live in paid documents that were not bought. Every package says whether its layer is specified by a public standard, inferred closely from published examples, or inferred. The passengers, aircraft types and fares are synthetic and labelled so; the schedule, tails, delays, cancellations and diversions on the recorded day are not.

A recorded day, at real time

The Wednesday before Thanksgiving 2025 is flown from the BTS record: 22,889 flights on their real tails, delayed for their recorded reasons, cancelled and diverted as they were, with 1.4 million pre-sold bookings at a holiday load and a price on every one. It takes a day, because it took a day.

It audits itself while it flies

The invariant suite is the release gate: no oversell across selling channels, message conservation at the switch, interline convergence, and a live check any shard can be asked for. Its first run against the recorded day found 88 oversold cabins and a filler that had never looked at the aircraft. The failures are real too, and they are all in the changelog.

the system

Every part is a system the industry actually runs

The state separation is real: nothing is shared between nodes except a message. Pick a component to see what it does, what it speaks, and how much of it is specified by a public document rather than inferred.

The eye

An orthographic globe drawn from the switch's own message bus. An aircraft appears because its carrier's MVT departure message crossed the switch; it lands because the arrival did. Sparks are messages in flight to and from a carrier's home; a red halo is a closed airport, sized by the real queue items an agent now has to rework.

Click any aircraft and you get what its carrier holds: the schedule and, on a recorded day, what really happened to it; departure control's clock, the name list's parts and amendments, seat map by cabin, connections, the load and loadsheet; the operations desk's callsign and flight plan and what the towers said back; the manifest name by name; and the bookings behind the names under the locators the selling channels issued, each with its fare.

modes
sky the globe · net the logical web of who converses with whom, laid out by its own springs
controls
pause the day, run it at ten hours a minute, close an airport, cut a carrier's circuit
instruments
/stats messages, movements, bookings, sold per second · /fleet every node and its console
The flight panel: one departure as its carrier holds it, from the schedule down to the loadsheet

One departure as its carrier holds it, from the schedule down to the loadsheet.

Carriers: a reservations system and a departure-control system each

Each carrier is a jetway gateway with its own book of record, its own seat inventory, its own locator space and its own console. It answers sells from its cabins, files its schedule changes, and runs its airports: reservations sends the airport the name list at T−180, the counter fills in waves with seat assignments and bag tags to sortation, the ADL goes at T−60, the counter closes, the gate boards, the sortation system reports the hold, the door closes at T−10 -- not over a loaded bag whose passenger is not on board; those are named and pulled first -- and the final sales, transfer, service and load messages and an AHM 560-method loadsheet go out.

The reservations and departure-control halves talk over the network like the separate systems they usually are. A carrier hosted on another machine is asked through federation; the switch cannot tell the difference.

Type B · IATA whitepaperEDIFACT · ISO 9735AIRIMP · inferred profilePNL/ADL · RP 1708 familyPSM PTM LDM CPM · worked examplesMATIP · RFC 2351
A carrier's own console, one of 522

Ryanair's own console, one of 522. Every node has one.

Distribution: three global distribution systems

1G, 1S and 1A each run a jetway gateway with queues, an availability cache and its share of demand. A booking is made at the GDS; the sell goes to the carrier as AIRIMP over Type B or PADIS over EDIFACT, the carrier answers KK, US or UC from its inventory, and the record carries every carrier's locator for the same booking. Interline itineraries settle across carriers; tickets are issued and advised; a slice of demand arrives as NDC orders on the GDS's HTTP endpoint.

What the carriers said is what the record shows. A carrier that cannot be reached lands the booking on the divergence queue, each item naming the gap that caused it.

queues
confirmation · waitlist · unable · divergence · schedule change · ticketing
books
bounded memory, or Postgres with one node view per system
IROPS
a cancelled flight queues every booking on it; reaccommodation protects on own metal, then on the codeshares the carrier markets under its own number, then interlines, and waits for each carrier's answer
A flight's bookings, priced: 65 records under the selling channels' locators, each with its fare and fare basis

United 2408 Denver to Washington: 65 records under the selling channels' locators, each priced.

The switch

A full jetwayd in relay mode: 500-odd TCP listeners, real framing, relay by Type B address line and EDIFACT UNB recipient, a write-ahead spool, an outbox per link that refuses rather than stalls when a peer stops reading, per-peer rate limits with a shared cap, and a lease so a standby takes over. It is the only piece every message crosses, which is why the globe can be drawn from its bus alone.

The sky is loud but not big. Real global reservations traffic is a few thousand messages a second; the switch peaks above 16,000 a second on one 4-vCPU machine during the departure banks. Topology is the interesting part, not throughput.

invariants
capture precedes interpretation · raw bytes are never regenerated from a parse · nothing undecodable is discarded
metrics
jetway_outbox_depth · jetway_outbox_congested_total · jetway_ingress_links · jetway_spool_depth
The net mode: the world's interline conversations laid out by their own springs

Net mode: who converses with whom, laid out by its own springs.

Fares and inventory

jetway's pkg/fare is the structure of a filing: fare basis codes, rules for advance purchase, stay, season, change and refund, taxes by kind, passenger types at their discounts, and a pricing step every booking passes through. It carries no fare of its own, because ATPCO's are licensed. wholesky files a synthetic tariff from the schedule's distances, fourteen booking classes to a market, US-shaped taxes, and prices every booking as of its purchase date.

pkg/inventory is leg-based seat control: cabins per leg, serial nested class authorisations, waitlists a tenth of the cabin deep. A full cabin waitlists, a closed class with seats still in the cabin refuses, and the last seats go at the higher fares. The inventory is rebuilt from the book of record at every boot, because anything that remembers sold seats in memory oversells after a restart.

the recorded dayvalue
whole day, 29,967 legs$719M
average per passenger-leg$255
median per passenger$204
full-fare Y · business · deep discount$341 · $750–785 · $93–160
taxes15%

Synthetic and labelled as such. The shape of a real Thanksgiving Wednesday, not anybody's filing.

The instrument panel: messages, wire formats, traffic classes, bookings, airborne, movements, undeliverable and queues

The instrument panel. The number that should be zero is labelled as such.

Operations: datalink, the AFTN, and when the day goes wrong

Aircraft report OUT/OFF/ON/IN over a datalink provider (ARINC 620 shapes, from OAG's published tables); operations derives the MVT from the report instead of asserting it. Flight plans go to the towers over the AFTN (ICAO Annex 10) and the towers send DEP and ARR back. A diversion sends its DIV to the field it really landed at.

When a flight is cancelled, every booking on it is queued; the IROPS engine works the queue the way a desk does: the next flights over the same city pair, own metal first, free sale where the availability cache offers it and a request to the carrier where it does not, each request waited on until the carrier answers. A confirmed seat drops the dead leg with a real sell and a real cancel on the wire; a waitlist is kept and named; what nothing can carry stays on the queue for a person, while the airport offloads whoever had checked in and pulls their bags.

AFTN · Annex 10ATS messages · FAA reproductions of Doc 4444ACARS OOOI · ARINC 620, OAG tablesMVT/DIV · AHM 780/781, OAG examples
The fleet view: 522 nodes across six machines, into a carrier mid-departure-bank, down to the raw telex, and a circuit cut and restored

522 nodes across six machines, down to the raw telex, and a circuit cut and restored.

The filler and the demand

The weeks before the day. internal/fill reads the schedule and writes each carrier's book of record before the day runs: parties weighted the way holiday travel is, itineraries that connect on legs that actually connect, booking classes placed in cabins that have room for them on every leg, children and infants, special service requests at their real rates, a ticket per name, the carrier's locator and the selling channel's, purchase dates spread over the months before and priced as of that date. Deterministic from a seed, straight into Postgres with COPY. Southwest's day alone is 260,000 records, written in 24 seconds. One party in three thousand has a cello, and the cello has a seat.

The demand generator then rides on top as what it is on the day of travel: the late trickle, at hundreds of bookings a minute, with connections, interline, ticketing, cancellations, divides and a slice arriving as NDC orders.

holiday load
0.85: 1,400,987 records, 3.1 million seats held before the first flight, 3.5 GB
what it found
a Type B line folded by the first family of four; the 60-line envelope overrun by a filled 737; a link deadlocked by both ends answering from inside their read loops. All fixed upstream, with tests watched to fail first.
The recorded day: the Wednesday before Thanksgiving 2025, every US flight on its real tail

The Wednesday before Thanksgiving 2025 at 17:00Z, every aircraft on its real tail.

The audit: the world answers for its laws while it flies

The invariant suite is the release gate. In process, internal/sim/invariants_test.go proves no oversell across selling channels, message conservation at the switch, interline convergence and that a cancelled flight queues everyone. Live, every shard reports its inventories, the core federates them, and skycheck exits non-zero on a cabin holding more than it has or a shard that did not answer, because a machine being down must not read as a pass.

$ go run ./cmd/skycheck https://wholesky-demo.fly.dev
6 shards, 8074 cabins, 126321 seats sold, 0 oversold

Its first run against the recorded day found 88 oversold cabins, 83 of them business cabins on Hawaii legs holding up to 54 passengers in 32 seats: the filler had drawn booking classes without looking at the aircraft. The gate paid for itself on its first run.

What is real, what is synthetic

Real: the wire formats and their published rules, the state separation, the failure modes, the schedule on the recorded day, its tails, delays by cause, cancellations and diversions.

Synthetic and labelled: the passengers, the aircraft types inferred from carrier and stage length, seat counts, and the fares. There is no real tariff in this, only the shape of one.

architecture

Two shapes, one codebase

Single-box (-role all, the default and the test bed): one OS process, every jetway instance a library assembly, everything between them crossing real TCP on loopback. Multi-machine (the deployed demo): the same assemblies spread across six machines, dialled together over a private network. The switch cannot tell the difference, which is the point.

gds1g · 1Gdistribution system + demand
gds1s · 1Sdistribution system + demand
gds1a · 1Adistribution system + demand
← 1 TCP link each · AIRIMP + PADIS →
core · the switchjetwayd in relay mode · 500+ listeners
core · the instrumentseye · fleet · stats · the only public HTTP
register + heartbeat every few seconds ↑
region0 · ~260 carriersreservations + DCS each · flies its slice of the day
region1 · ~258 carriersreservations + DCS each · flies its slice of the day
datalink · ANSPACARS provider · towers over the AFTN

Federation is deliberately dumb

Peers register with the core and heartbeat every few seconds. The reply carries the switch's link addresses, the current warp and the closed-airport set, so liveness, time control and chaos propagate on one pulse. The fleet board merges every peer's rows and proxies drill-downs and consoles to whichever machine owns the node. A region can die and restart while the rest of the sky keeps flying; rejoining is registering and dialling back in.

What crosses the wire

Sells and their answers, availability broadcasts, name lists and their amendments, check-in and bag messages between reservations and the airport, load and passenger lists at door close, movement messages from the datalink provider, flight plans and tower replies over the AFTN, schedule changes, ticket control. Nothing else. Observers are bus taps only.

The engine

Everything importable lives in jetway: the codec packages (typeb, edifact, airimp, padis, avs, ssim, ndc, matip, pnr, pnl, baggage, mvt, dcs, aftn, ats, acars, fare) and the application packages (gateway, store, inventory, irops, node, queue, ingress, egress, transport, spool, api). jetway is meant to be usable as a real gateway, DCS or message router; sim-only behaviour stays in wholesky. Each package's doc says whether its format is specified by a public document or inferred from free reproductions, and the roadmap names the paid documents whose absence costs what.

the recorded day

The Wednesday before Thanksgiving 2025, at real time

worldc reads one day of BTS on-time data — every US scheduled flight, real tails, real delays by cause, real cancellations and diversions — and skyd flies it at warp 1, so the day before Thanksgiving takes the day before Thanksgiving.

22,889flights · 20 carriers · 351 airports
5,313airframes on their real tails
64 · 44cancellations · diversions, as recorded
1,400,987pre-sold bookings at 0.85 load
$255average fare per passenger-leg

The MVTs carry the record's delay codes. Departure control opens each flight three hours out and closes it at the door; the manifest, load and loadsheet are the day's own. Nothing in the recorded schedule is invented; what is synthetic — passengers, fares, aircraft types where BTS does not say — is labelled in the manifest. At the day's wrap the books are purged by daily partition and refilled deterministically from the seed. It runs at wholesky-thanksgiving.fly.dev.

deploying

Run it on a laptop, or across six machines

Locally

git clone https://github.com/adamf/wholesky
cd wholesky && go test ./...

# compile a world, then fly it
go run ./cmd/worldc -countries "United Kingdom,France,Germany" -carriers 30 -o europe.json
go run ./cmd/skyd -world europe.json -carriers 12 -demand 8 -warp 240
# console on http://127.0.0.1:8080 · the eye at /eye

worldc compiles a deterministic manifest — airports, carriers, daily flights — from the vendored OpenFlights snapshot; same seed, same world. Pass -bts and -date for a recorded day. skyd boots the switch, the tenants and the distribution systems on loopback and runs the flight day at -warp.

The deployed shapes

appmachineswhat
wholesky-democore · gds1g · gds1s · gds1a · region0 · region1the synthetic world at warp 6, carriers' books in bounded memory
wholesky-thanksgivingone machine, -role allthe recorded day at warp 1, books of record and distribution systems' books in Managed Postgres

For a carrier's host or a switch carrying real traffic, the SRE plan is its own page: deploy to production — topology, the lease, database provisioning, DR, the alerts, and load testing as the release gate.

Both from fly.toml and fly.thanksgiving.toml. Roles: core, gds, region -shard N -shards M, or all. Peers find the core with -core-url and advertise themselves with -self-url. Stores are memory by default, or Postgres with -tenant-dsn and -gds-dsn; records are partitioned by their retirement day and dropped when the day wraps.

Lessons the deployments taught, all now in the code: a filled 737's name list is three parts; a link's outbox must refuse rather than stall; a seat inventory must be rebuilt from the book at boot; two worlds must not share one small database; and a partition that cannot be created must not stop the write.

contributing

How the work is done here

The rule that matters most: say what you actually know

Most airline formats are defined in paid IATA documents that were not bought. Every package says which layer it is: specified (ISO 9735, RFC 2351, Annex 10, the Type B whitepaper, the PNRGOV guide, NDC schemas), inferred closely (built and tested against verbatim published examples), or inferred. Never write a doc comment implying conformance you cannot demonstrate. When public sources disagree, implement one, make it advisory, and document the disagreement.

Search for a free source before writing "blocked"

Every time this has been done it paid: the free PNRGOV guide fixed four PADIS bugs whose tests had encoded the same guess as the code; the free EMD guide fixed three coupon statuses; the free Type B whitepaper produced the 4 KB cap and PDM. Look for tables of contents, regulator guides, vendor and airport reproductions, and adjacent free standards. Then state precisely what remains unknown.

Testing discipline

  • A new test does not count until it has been watched to fail against the old behaviour.
  • Check against an external artefact where one exists; fuzz round trips for any codec; write fixtures by hand from the spec, not from your own builder.
  • Store changes pass the conformance suite against both backends (JETWAY_TEST_DSN).
  • Anything that crosses a link gets a scenario; unit tests do not catch what only shows up with a partner on the other end.
  • CI runs gofmt and the race detector. Watch the run after every push.

Conventions

  • Comments explain why. Go doc on every exported symbol, in prose. British spelling.
  • Commit messages have real bodies explaining the reasoning, wrapped at 72 columns.
  • wholesky finds a bug in jetway → fix it upstream, tag, pin. Sim-only behaviour stays in wholesky; jetway must remain usable as a real system.
  • Migrations are dense-numbered and never edited once applied anywhere. Never drop a partition by hand under a running process.
  • Open an issue or a pull request on either repo; both are MIT.

What is not here yet

IATA's IS-XML for the interline invoices (the proration, billing and agency memos are done), a revenue management forecast learned from history (the ladders, the booking-curve forecaster and the network programme's bid prices are done), Secure Flight-style vetting, WorldTracer's own formats (the AHL, OHD and FWD story of a short-shipped bag runs as a profile), crew, maintenance and rotation, weather and air-traffic flow, airport systems, cargo, and Type B over MQ as some carriers still receive it. The ordered list, with what each would add and exercise, is docs/missing-systems.md; what full load costs is docs/full-throttle.md.