šµļø FGA 01: Itās Alive, but It Does Nothing š¶
Building the Academy walking skeleton before the real suspects arrive

Part IāāāThe Body on theĀ Table
Something Moved
Version Zero had secured the crime scene, identified the authorities, divided the building into twenty rooms and written down who was allowed to touch what.
It had produced something important, disciplined and entirely incapable of being played.
Yesterday there had been twenty labelled rooms and no furniture. Today a light was blinking somewhere downstairs.
Progress, presumably. Or an electrical fault. Either way, something had moved.
We could finally start building the interesting parts.
The graph. Entity resolution. Suspicious transactions. Case packages. Analytical relationships. Perhaps even a magnificent investigation board covered in nodes, edges and enough ominous red lines to make every accountant in Europe request legal representation.
That would come later. Iteration 01 had a much less impressive objective:
Make one small but real journey through the entire application before making any part of that journey clever.
Launch the game. Choose an investigation path. Choose a training case. Create and start an investigation. Read its opening comic. Enter the board. Refresh the browser. Make sure the same detective, path, case and investigation are still there.
That was it. Not five screens that happened to resemble one application. Not a polished frontend connected to hard-coded fixtures. Not an impressive graph demonstration waiting for somebody to attach a game to it.
A walking skeleton*.*
The idea is simple: implement the thinnest end-to-end path that crosses the important architectural boundaries and forces them to cooperate.
The skeleton does not need muscles yet. It does not need a sophisticated case, an evaluator or a particularly convincing coat. It only needs to walk.
Detective work can wait until it stops falling down the stairs.
The Attractive Ways to Build the WrongĀ Thing
There were several more exciting things we could have built first. That was precisely the problem.
We could have begun with the perfect domain model. Actors. Organisations. Transactions. Documents. Evidence. Claims. Hypotheses. Entity-resolution candidates. Graph relationships. Fraud mechanisms. Scoring rules.
There is nothing wrong with any of those concepts. Eventually, most of them will be essential.
It would simply have been possible to spend several weeks producing an immaculate collection of beautifully typed Python objects for a game nobody could launch.
Hercule examined the proposal.
The ontology was immaculate. The victim was still outside waiting for somebody to open the door.
Then there was the graph. This temptation was particularly dangerous because the project is called Fraud Graph Arena*. Surely the graph should come first.*
We could load nodes and edges, pan, zoom, select paths, calculate connected components, highlight suspicious relationships and make everything rearrange itself dramatically whenever Hercule narrowed his eyes.
GraphFrames can eventually handle distributed graph processing over Spark data. A browser graph library can provide interactive visualisation.
Both will be useful. Neither explains how a player reaches the graph, which investigation produced it, what the player is permitted to see or who owns the state that brought them there.
A magnificent graph attached to nothing is still nothing. Just more photogenic.
We could also have started with the real cases. Invoice diversion. Shell companies. Ponzi schemes. Procurement fraud. Synthetic identities. Suspicious transfers wandering through increasingly improbable corporate structures.
Writing cases feels wonderfully close to building the game because the cases eventually contain much of the gameās substance.
Without a stable path into an investigation, however, we would merely accumulate increasingly elaborate folders waiting for an application capable of opening them.
Worse, documenting that work publicly would reveal details about mysteries the player is supposed to investigate later.
The kennel would be full of crimes. The detective would still be locked outside. And the newspaper would already have printed the suspectsā names.
So Iteration 01 adopted a deliberately unfashionable principle:
Breadth before depthāāāonce.
Not forever.
The graph will become deeper. The domain model will become richer. The cases will become complicated enough to require notebooks, diagrams and possibly counselling.
But before investing deeply in any one part, we wanted proof that a complete architectural path existed.
The Smallest PossibleĀ Crime
The next question was almost insulting.
How little could Fraud Graph Arena do and still prove that Fraud Graph Arena existed?
The answer was microscopic:
Launch
⤷ Detective Academy
⤷ Select training case
⤷ Create and start round
⤷ Opening comic
⤷ Investigation board
⤷ Recover the same context
That was the entire investigation.
No credits. No evidence revelation. No Zingg entity resolution. No GraphFrames analysis. No Genie-assisted retrieval. No scoring. No accusations. No dramatic reconstruction of shell companies transferring suspicious amounts of money at 03:17.
The radio could wait too. Machine learning powered by late-night jazz remains, regrettably, outside the current research programme.
The objective was not feature completeness. It was architectural continuity*.*
- The frontend had to launch through its real composition.
- The backend had to answer through real contracts.
- The training path and case required stable identities.
- The application had to create an authoritative round.
- The opening sequence had to appear before the board.
- And every piece of context had to survive navigation and refresh instead of evaporating somewhere between two React components.
Hercule did not need a crime yet.
First he needed to prove he could walk from the front door to the investigation room without disappearing between floors.
Why the Academy OpensĀ First
The original plan used Puppy and the first ranked case as the example journey.
That would have worked technically. It would also have been the wrong public setting.
The real anthology should remain sealed. Its case identifiers, suspects, evidence structures, clue order, hidden relationships and endings are part of the playerās investigation. Using those cases as development fixtures would gradually turn the article series into a beautifully illustrated collection of spoilers.
Detective Academy solves that problem.
It provides a permanent public laboratory where we can introduce every mechanic using deliberately synthetic, obvious and spoiler-free material.
The Academy is not a mock-up. It uses the same contracts, round lifecycle, narrative system, workspace boundary and persistence model intended for the ranked game.
The difference is purpose. Ranked cases are mysteries. Academy cases teach the player how mysteries work.
We can explain graph interaction using Academy records. We can demonstrate entity-resolution candidates using Academy identities. We can test scoring with controlled qualification data.
None of that requires printing the answer to a ranked mystery before the player has opened it.
Hercule approved.
He had always believed education was preferable to contaminating the evidence.
Part IIāāāThe Building Learns toĀ Walk
The FrontĀ Door
The first capability of Fraud Graph Arena was not graph analytics. It was not entity resolution. It was not fraud detection.
It was this:
The application starts.
That sounds almost too trivial to count. It is not.
- The frontend must load.
- The backend must boot.
- Configuration must resolve.
- Dependencies must compose.
- Routes must register.
- The browser must know where the API lives.
- And the backend must answer when somebody asks whether it is alive.
More importantly, failures need somewhere intentional to occur.
Missing configuration should fail as missing configuration. Broken dependency wiring should fail while the application is assembled. An unavailable backend should produce an explicit connectivity problem.
It should not manifest as a mysterious white rectangle followed by three hours of archaeological debugging.
Startup therefore became part of the walking skeleton.
It exercised the composition root: the place where configuration, ports, adapters and concrete implementations finally meet.
No detective work happened. But the lights came on.
The front door opened. Somebody answered when Hercule knocked. The patient had its first heartbeat.
Choose Your TrenchĀ Coat
Once the door opened, Hercule needed somewhere to go.
The path-selection screen displayed four canonical routes returned by the backend:
- Detective Academy was available.
- Puppy was visible but marked COMING SOON*.*
- Adult Dog and Senior Dog remained locked.
That distinction came from the catalogue contract. Not from the interface teamās mood.
The identifiers were also canonical:
DETECTIVE_ACADEMY
PUPPY
ADULT_DOG
SENIOR_DOG
The interface could render cards, status labels, descriptions and suitably suspicious Spanish Water Dogs.
But those identities could not originate from the cards themselves.
That sounds pedantic until the alternative appears:
if button == "academy":
...
Now Detective Academy is not application data. It is a button somebody remembers to recognise.
The path screen therefore became the first meaningful meeting between presentation and game state.
Presentation tells Hercule what is available. The catalogue owns why it is available.
The application carries what he selected. They needed to shake hands. They did not need to move in together.
The Case of the Empty EvidenceĀ Board
After selecting Detective Academy, Hercule reached the training catalogue.
It contained one case:
ACADEMY_001
The Case of the Empty Evidence Board
Version 1.0.0-i01
Its description was unusually honest:
A spoiler-free training file whose only mystery is whether the application can carry it safely from catalogue to board.
There were no suspicious payments. No hidden identities. No fraudulent directors. No inexplicably wealthy Chihuahua operating through seven shell companies and a bakery.
The case existed to prove one thing:
If Hercule selected
ACADEMY_001, the rest of the application had to know he selectedACADEMY_001.
Not the first item in an array. Not a default case inserted by the board. Not a string independently copied into three components until one of them eventually became ACADEMY-01.
The selection had to travel through the system*.*
Opening the case therefore created a real round with a generated identity.
The round bound:
- the player
- the Academy path
- the case identifier
- the case version
- its lifecycle state
The browser did not simply navigate to a board carrying some convenient text. It asked the application to create an investigation.
That apparently tiny difference separated a game from a collection of pages.
A Story BeforeĀ Evidence
Every Fraud Graph Arena case begins with one or more comic-like pages.
Every case also concludes with one or more comic-like pages selected after evaluation.
That was too fundamental to leave until the interface became crowded.
If the walking skeleton moved directly from catalogue to board, it would teach the application the wrong journey and require the narrative layer to be inserted later between components that had already learned to bypass it.
So Iteration 01 added a dedicated narrative boundary.
Every published case must register:
- one versioned opening sequence
- one versioned closing sequence
- at least one page in each sequence
- contiguous page positions
- narration
- alternative text
- an image reference
- an explicit skip policy
The Academy opening contains two pages.
The first is titled:
The Academy Door
The second:
A Board with Nothing to Hide
The illustrations are intentionally modest placeholders. The narrative contract is not.
The browser receives a versioned comic sequence from the server and renders the current page. The page number lives in the URL:
/rounds/{round_id}/intro?page=2
Refresh the browser and page two remains page two. Not because React remembered it. Because the route describes it and the backend can reconstruct the sequence.
The closing sequence is also registered now. It is not exposed.
There is no submission, evaluation or ending selection in Iteration 01, so there is no legitimate route to a conclusion. Registering it validates that the case is structurally complete without sending future ending material to the browser before the player has earned it.
The opening belongs before the board. The closing belongs after the verdict.
The narrative module knows the sequences. It does not decide when Hercule is allowed to see them.
No respectable detective stores every possible ending in the waiting room.
The Round Learns ThreeĀ Words
Adding the opening sequence required the round to know more than whether it existed.
Its first lifecycle became:
CREATED ā INTRO_PENDING ā ACTIVE
Creating a round places it in CREATED. Starting it moves it to INTRO_PENDING. Completing or explicitly skipping the opening moves it to ACTIVE.
That may look like excessive ceremony for two comic pages. It is not.
Without an authoritative lifecycle, the browser could simply type the board URL and bypass whatever the interface happened to show before it.
So the board asks the backend whether the round is active. If the introduction is still pending, the server rejects the request with a deliberate problem:
INTRO_REQUIRED
The message explains that the Academy briefing must be completed or skipped before entering the board.
This is a small example of a larger rule:
Navigation is not authority.
A route can request a transition. It cannot grant itself permission.
Hercule may know where the evidence room is. That does not mean the guard must let him in before the briefing.
The Empty Investigation
And finally, Hercule entered the board. There was almost nothing there.
No graph. No documents. No suspicious transfers. No clues. No suspects. No button labelled Expose International Biscuit Laundering Network*.*
The board displayed:
- the selected path
- the selected training case
- the case version
- the round identity
- the active status
- zero evidence
- zero suspects
And one message:
The Academy workspace is intentionally empty. The real achievement is that the correct training file crossed every boundary and survived the journey.
That empty board was the first playable milestone. Not because it could investigate anything. Because it proved that the journey was coherent.
A path selected near the front door had become a server-owned round.
That round had loaded the correct narrative sequence.
The introduction had controlled entry into the workspace.
And the board had reconstructed the correct case from the round rather than guessing why Hercule was there.
Eventually this space will contain records, documents, list and graph views, relationship provenance, investigative actions and the case file.
For Iteration 01, none of that mattered yet.
Hercule had reached the right room carrying the right training file.
For a walking skeleton, that was enough.
The One Thing That Had toĀ Survive
Getting to the board was not sufficient.
Hercule had to remain in the same investigation after the browser behaved like a browser.
That raised a deceptively simple question:
Where does state actually live?
- A highlighted card is presentation state.
- The current route is navigation state.
- The current comic page can be represented by the URL.
- The selected path, case, case version and round lifecycle are authoritative game state.
Anything that must survive browser refresh or application restart needs either durable storage or enough authoritative information to reconstruct it.
This distinction matters because component state is wonderfully convincing until somebody presses Refresh*. Then the witness develops amnesia.*
Iteration 01 therefore keeps only one convenience value in browser storage:
fga.lastRoundId
That identifier allows the launch page to offer a route back to the previous investigation. It does not contain the truth of the investigation.
The intro and board routes use the round identifier to ask the backend for the current case and lifecycle.
With the SQLite adapter, the round and its introduction-completion state also survive application recomposition.
Destroy the application process. Build it again. Request the same round.
The backend still knows that Hercule selected ACADEMY_001 and completed the briefing.
The browser remembers where to ask. The server remembers what happened. That is a much healthier relationship.
The UI may remember convenience. It must not invent truth.
Part IIIāāāChalk Lines on theĀ Floor
One Repository, Several BadĀ Ideas
Once the journey existed, another question arrived.
Where should all these pieces live?
Hercule had four suspects.
1. Everything in OneĀ Pile
Frontend code. Backend code. Business rules. Configuration. Helpers. Constants.
All placed wherever convenient.
At first this feels remarkably productive. Imports are easy. Everything is nearby. Nobody wastes time asking whether something belongs somewhere else.
Then, six months later:
utils_final_really_shared.py
contains authentication helpers, graph colours, Academy identifiers and the path to an MP3.
The problem is not that frontend and backend share a repository. Fraud Graph Arena deliberately allows that.
The problem is having no enforceable boundary between them.
Easy childhood. Very expensive adolescence.
2. Two Completely Independent Applications
Separate repositories. Separate builds. Separate releases. Connected only through HTTP.
That can be exactly right when teams, operational boundaries or release cycles genuinely require independence.
It also creates coordination.
Contracts must remain compatible. Versions can drift. Changes may require coordinated releases. Shared assumptions become distributed assumptions.
For a game still celebrating the fact that an empty board opens correctly, we would be paying for independence we did not need.
The existence of HTTP is not, by itself, evidence of microservices.
3. The ModularĀ Monolith
One coherent product.
- React and TypeScript at the player-facing edge.
- FastAPI and Python behind the API.
- Logical capabilities separated by public contracts.
- Owned persistence.
- Independent tests.
- Explicit composition roots.
The modules live in the same building. That does not give them permission to rummage through one anotherās private drawers.
4. Microservices Before Breakfast
We could have created:
academy-path-service
training-case-service
comic-page-service
empty-board-service
dog-trenchcoat-service
Five independently deployable systems. Still no crime to solve.
Distributed systems buy useful properties when the problem requires them: independent scaling, deployment, ownership or isolation.
They also introduce network failure, service authentication, observability, version coordination, deployment complexity and distributed consistency.
Iteration 01 had nothing worth buying with those costs.
The property we needed was modularity*. Not distribution.*
Hercule closed the file.
Twenty rooms required walls. They did not require twenty postal codes.
Who Is Allowed to KnowĀ What?
The walking skeleton was tiny. That made it the cheapest possible moment to answer a dangerous question:
Who is allowed to know what?
Presentation knows how the game looks. It can render Detective Academy. It can disable Puppy. It can display a comic page. It can project an empty board.
It does not decide which paths are open, whether the introduction is complete or what case the round represents.
Presentation is a projection of game state. Not its source.
The catalogue knows which paths exist, their canonical identifiers, their availability and which safe case metadata belongs in each section.
It knows that Detective Academy contains ACADEMY_001.
It also knows that Puppy currently publishes no case details.
The catalogue does not own Herculeās selection.
The round capability owns one investigation attempt.
It knows the path, case, case version and lifecycle. It decides whether the investigation is CREATED, INTRO_PENDING or ACTIVE. It has absolutely no opinion about comic typography.
The narrative capability owns versioned opening and closing sequences.
It validates page order, narration and alternative text. It does not create rounds. It does not activate the workspace. And it certainly does not expose a conclusion because the frontend would like a dramatic preview.
Persistence stores state owned by the appropriate capability.
It does not decide what that state means. The database is a filing cabinet. Not the chief detective.
Then there was analytics. The glamorous witness.
Graphs. Entity resolution. Retrieval. Analytical relationships.
Eventually these will become some of the most interesting parts of the game.
That is exactly why analytics received a port before it received power.
No provider exists in Iteration 01. The boundary does.
When analytics eventually enters the room, it must do so through an agreed capability rather than becoming the centre of the game merely because it is technically impressive.
The graph may display evidence. It does not decide which evidence exists.
A retrieval provider may help locate records. It does not decide guilt.
An entity-resolution candidate may suggest that two records belong to the same underlying entity. It does not convict either of them.
Hercule had seen this pattern before.
The graph arrives as a witness. Three iterations later, it owns the building.
The chalk lines went down early.
Draw the Lines Before the Furniture Arrives
Architectural boundaries are cheapest when almost nothing depends on them.
If an interface has ten lines behind it, changing the contract is mildly irritating.
If ten thousand lines depend on an implementation detail that was never supposed to escape, changing it becomes a migration programme, a release plan and several meetings nobody wanted.
That is why the walking skeleton creates deliberate seams early.
The objective is not to predict every future abstraction perfectly. That would merely be another variety of premature design. The goal is to make future change local*.*
- A graph renderer should be replaceable without rewriting game state.
- A persistence adapter should change without teaching React about databases.
- A comic renderer should evolve without moving lifecycle authority into presentation.
- A case package should gain real evidence without every route learning its internal structure.
Empty rooms are easy to rearrange. It becomes considerably harder once somebody has bolted the sofa through three load-bearing walls.
The First Statements UnderĀ Oath
Once the boundaries existed, frontend and backend needed to agree on how they would speak.
Not sophisticated behaviour. Contracts.
The public conversation for Iteration 01 became:
GET /api/v1/health/live
GET /api/v1/health/ready
GET /api/v1/health/version
GET /api/v1/catalogue/sections
GET /api/v1/catalogue/{section}
POST /api/v1/rounds
POST /api/v1/rounds/{round_id}/start
GET /api/v1/rounds/{round_id}/opening
POST /api/v1/rounds/{round_id}/opening/complete
GET /api/v1/rounds/{round_id}/workspace
The health endpoints answer different questions.
- Liveness asks whether the process can respond.
- Readiness asks whether the application is sufficiently configured and its essential dependencies are available.
- Version exposes safe build and contract metadata.
One endpoint returning "ok" does not mean the entire fraud-investigation universe has received a clean bill of health.
The catalogue contract returns path availability and safe Academy metadata. It does not publish real ranked cases.
The round contract creates an authoritative investigation and starts its lifecycle.
The opening contract returns the registered comic sequence for that bound case version.
Completing the opening changes round state.
The workspace contract refuses premature entry and later reconstructs the active board context.
The interesting part was not the URLs. It was the agreement behind them.
Both sides had to agree on:
- stable identities
- request and response shapes
- optional fields
- lifecycle values
- case versions
- error semantics
- safe recovery instructions
Then the implementations behind those contracts could become complicated without forcing every caller to become complicated with them.
Hercule approved.
A witness may change their coat. Their statement under oath should be considerably harder to alter.
DTOs Are Not theĀ Domain
At first, many of the models looked suspiciously similar.
A training case had an ID, version, name and status. The backend had an object containing those fields. The API returned those fields. React needed those fields.
So why not use one structure everywhere?
Because same shape does not mean same responsibility*.*
- The domain model represents game concepts and rules.
- The API contract represents what is allowed to cross the frontend/backend boundary.
- The persistence representation describes how state is stored.
- The presentation model describes what a particular screen needs.
They may begin almost identical. That is fine. What matters is that they are not accidentally the same thing.
A SQLite column should be free to change without becoming an API-breaking change.
A server-only field should not appear in the browser simply because serialisation was convenient.
A React interface should not become the canonical definition of what a round means.
And later, evaluator truth must not leak because somebody returned every attribute on a case object.
So the rule was simple. Map deliberately. Expose deliberately. Persist deliberately.
A puppy, a wolf and a badly groomed mop can look surprisingly similar from twenty metres.
Hercule still preferred separate case files.
Failure Is Part of theĀ Contract
A contract that describes only success is not much of a contract.
Even this tiny skeleton needed deliberate failure behaviour.
What happens if the path identifier is malformed?
If the requested path is not open?
If the case does not belong to that path?
If a case lacks its required opening or closing narrative?
If the round has not started?
If the player tries to enter the board while the introduction is pending?
If the backend is unavailable?
Those outcomes should not become blank screens, raw exceptions or whatever the frameworks happen to improvise that afternoon.
Fraud Graph Arena returns failures using application/problem+json.
A problem contains a stable code, safe title, explanation, correlation identifier and recovery guidance where appropriate.
The browser can therefore distinguish:
INVALID_PATH_ID
PATH_NOT_OPEN
CASE_PATH_MISMATCH
ROUND_NOT_STARTED
INTRO_REQUIRED
ROUND_NOT_FOUND
BACKEND_UNAVAILABLE
Internal stack traces and storage details stay internal.
The player receives enough information to understand what happened without receiving a guided tour through the backend.
There is also an important anti-spoiler detail.
When a ranked path is unavailable, the application rejects the path before trying to resolve any requested case identifier. Otherwise an attacker could probe sealed paths and learn whether guessed case IDs exist by comparing different errors.
Even failure order can become evidence.
Hercule wrote in the margin:
A contract containing only happy paths is merely a witness statement taken before the questioning began.
Part IVāāāUgly onĀ Purpose
The Placeholder Must Be Fake; the Boundary Must BeĀ Real
A walking skeleton is allowed to be ugly.
The path cards can be basic. The Academy case can contain almost nothing. The comic illustrations can be placeholders. The board can be an empty rectangle.
Most behaviour can be temporary. What cannot be temporary is the way those pieces connect.
The route must be real. The API call must be real. The round must be real. The lifecycle transition must be real. The narrative contract must be real. The persistence boundary must be real. The test must be real.
That gives us an important distinction:
***Placeholder behaviour behind a real boundary is cheap to replace.***Impressive behaviour built across imaginary boundaries is expensive to untangle.
A temporary comic illustration can disappear in one commit. A narrative sequence secretly implemented inside one React page requires considerably more paperwork.
An empty board can later be replaced by the workspace. A graph engine that already owns navigation, case selection and persistence becomes an architectural hostage situation.
Hercule had no objection to cardboard furniture. He objected when somebody painted a door on the wall and called it architecture.
Why Not Build a Beautiful Prototype?
A polished prototype would have answered a useful question:
Can we make this interaction look convincing?
The walking skeleton answered another:
Can the real system carry this interaction from one side of the architecture to the other?
Both approaches have value.
A prototype reduces design uncertainty.
Can the layout work?
Does navigation feel natural?
Can the comic presentation communicate the story clearly?
Iteration 01 was removing a different uncertainty.
Integration uncertainty.
Could a choice leave one screen, cross the appropriate contract and become a server-owned round?
Could that round load its versioned opening?
Could comic progression survive refresh?
Could the board refuse entry until the lifecycle allowed it?
Could the same context be reconstructed after the application restarted?
A prototype could prove that the Academy looked excellent. The walking skeleton had to prove there was actually a building behind the faƧade.
Wiring the Laboratory
Sooner or later every software project discovers the phrase:
āJust hardcode it for now.ā
Iteration 01 tried not to leave too many bodies behind.
Things that genuinely vary by environment belong in configuration.
The runtime role. The API prefix. The build and contract versions. Logging level. Allowed origins. The frontend distribution path. The selected round repository. The SQLite location.
Those settings are typed and validated during startup.
Individual domain modules do not wander through environment variables looking for inspiration.
Configuration also has a boundary.
It describes where and under what operational conditions the application runs*. It does not redefine* what the game means*.*
Changing an environment variable cannot make a locked path open. It cannot remove the introduction from an active case. It cannot alter evidence rules or scoring.
Those belong to versioned game contracts, not deployment convenience.
Production settings also reject dangerous shortcuts.
A process-local in-memory round repository is not accepted as durable production state.
Wildcard CORS is not accepted as a production access policy.
Development may use cheaper furniture. It does not receive permission to remove the doors.
Hercule had investigated many suspicious documents. Few inspired more immediate distrust than a fourteen-month-old.env.example marked:
probably still correct
Dependency Injection Without a Religious War
Once modules depend on contracts, somebody must still provide the implementations.
That is the useful meaning of dependency injection here.
Application code asks for a capability through a port. The composition root decides which adapter satisfies it. The round service depends on a round repository contract.
In a focused test, that contract can be backed by a deterministic in-memory repository. In the durable journey, it can be backed by SQLite.
The catalogue and narrative modules currently use in-memory publication adapters containing the Academy material.
Later, their implementations can change without moving catalogue or narrative rules into the web layer.
Conceptually:
Domain/Application ā Port ā Adapter
ā
Composition root
The domain does not import FastAPI. It does not import SQLite. It does not inspect the environment and construct whatever infrastructure feels appropriate that morning.
Those decisions happen at the application edge.
None of this requires turning dependency injection into a theological dispute involving containers, decorators, service locators and seventeen levels of factories.
The purpose is considerably less exciting:
replaceability, isolation and testability.
Hercule did not care who delivered the evidence envelope. He cared that the envelope followed the agreed protocol and did not contain a database connection.
Development Is Not Production WearingĀ Slippers
Development should be easier than production. It should not be a different game.
Locally, Fraud Graph Arena may use deterministic fixtures, verbose diagnostics and lightweight adapters.
Tests use controlled implementations so the same input repeatedly produces the same result.
Production receives explicit configuration, durable state and stricter operational rules. But the important architecture stays the same.
The contracts remain. The lifecycle remains. The narrative requirements remain. The authority boundaries remain.
Where practical, environments change adapters and configuration. Not semantics.
A memory repository can replace SQLite during a unit test. The round still moves through the same states.
Placeholder comic art can replace final assets. The sequence still requires ordered pages, narration and alternative text.
Development gets brighter lights and cheaper furniture. It does not get a secret passage through the evidence room.
Part VāāāThe First Witnesses Lie
The Case of the Vanishing Investigation
The first bug was beautifully simple.
Hercule selected Detective Academy. He opened ACADEMY_001. He reached the briefing. Then he refreshed the page.
The investigation vanished.
A wonderfully small bug with a much larger confession behind it.
The real problem was not refresh.
Temporary browser state had been mistaken for durable journey state.
React knew what Hercule had selected because the previous component remembered it.
Reload the application and the witness developed amnesia.
The fix was not merely:
Make refresh work.
The real question was:
Who owns this state?
A highlighted card belongs to the interface. The current comic page belongs in navigation. The investigation identity and lifecycle belong to authoritative game state.
The browser may retain a round ID as a shortcut. The backend must reconstruct what that ID means.
Once ownership becomes explicit, recovery becomes an engineering problem instead of a magic trick.
Hercule added a note:
If nobody can explain who owns a piece of state, the browser will eventually steal it.
Page Two Disappears
The next witness was the comic itself.
Hercule advanced from The Academy Door to A Board with Nothing to Hide*.*
He refreshed. The story returned to page one.
The round had survived. The playerās position inside the sequence had not.
This was not authoritative game history yet; the server did not need to store every page turn. But it did need a stable navigation representation.
The solution was to place the page number in the URL:
?page=2
That choice gives comic position a clear owner.
The route describes where the player is. The server provides the ordered sequence. The presentation renders the corresponding page.
No component needs to preserve a secret counter in memory and hope the browser remains cooperative.
Page two became a place. Not a feeling.
Attack of the Duplicated Constants
The next suspect looked harmless.
A few strings.
The frontend needed path identifiers. The backend needed them. Tests needed expected values.
So the easiest thing was to write them down in each place.
That works perfectly until one says:
DETECTIVE_ACADEMY
another says:
detective-academy
and a third confidently tests:
detective_academy
Three witnesses. One school. Somebody is lying.
The canonical identifiers belong to the catalogue contract.
The frontend receives them. It does not define a second private anthology.
An architectural test even scans the production frontend to ensure the complete path list has not quietly been copied there.
Duplicated constants are not merely untidy. They create competing sources of truth.
The frontend may render the right card while sending an identifier the backend rejects. Tests may then repeat the same wrong string and confidently certify the mistake.
The cure is not clever normalisation. It is one canonical contract and tests that detect drift.
The Frontend Knows TooĀ Much
The next failure was more subtle because everything still appeared to work.
The frontend knew Detective Academy was open. It knew Puppy was unavailable. It knew which training case belonged to the Academy. It knew the round should enter the board after the comic.
Convenient. Also suspicious.
Those facts belong to different authorities.
The catalogue owns path and case availability. The round owns lifecycle. The narrative service owns the opening sequence.
The frontend may render what those capabilities report. It may disable a locked path. It may show a Next page button. It may display the board after successful activation.
What it cannot do is quietly become the second authority for the rules. Otherwise availability exists once in Python and once in TypeScript.
Eventually they disagree.
Then the browser confidently opens a door the backend knows is locked. The frontend may know how to show the truth*. It should be considerably more cautious about deciding what the truth is.*
The Secret Passage to theĀ Board
The comic introduced another tempting shortcut.
The browser knew the board route. Why not navigate there directly?
Because showing a comic and enforcing a narrative transition are not the same thing.
If the board trusted the route alone, the introduction would be optional regardless of the case contract. Any player could bypass it by editing the URL.
So the board asks the round service for its workspace.
A round in INTRO_PENDING receives:
INTRO_REQUIRED
Only an ACTIVE round receives the workspace.
This failure was useful because it exposed an authority boundary that a purely visual prototype would never reveal.
The interface can guide the player through the intended sequence. The backend must enforce the transitions that matter.
A painted guard is decorative. A locked door is architecture.
Every Tiny Bug Has an Architectural Alibi
Taken separately, these looked like ordinary implementation mistakes. They were not.
- The vanishing investigation exposed a state-ownership problem*.*
- The disappearing comic page exposed a navigation-state problem*.*
- The duplicated identifiers exposed a contract problem*.*
- The clever frontend exposed an authority problem*.*
- The direct board route exposed a lifecycle-enforcement problem*.*
That is exactly why the walking skeleton is useful.
A thin end-to-end slice forces boundaries, ownership and contracts to interact while the system is still small enough to change cheaply.
Later, the same mistakes would be buried under graph logic, persistence, analytics, scoring and real case content.
Now they were fingerprints on an otherwise empty room.
The skeleton was not merely learning to walk. It was identifying which floorboards were loose before the architecture had enough code to hire expensive lawyers.
Part VIāāāMaking the Corpse Prove It IsĀ Alive
āIt Works on My Laptopā Is Not anĀ Alibi
Hercule distrusted demonstrations.
Launching the application, clicking:
Detective Academy
⤷ ACADEMY_001
⤷ Next page
⤷ Enter the Academy
and announcing:
It works.
proves exactly one thing. That one particular journey worked once, on one machine, while somebody was watching.
Iteration 01 needed repeatable evidence.
Version Zero had already established the qualification model.
- Modules should be testable through their public contracts.
- Collaborating modules should be tested together.
- Capability groups should be tested as groups.
- The complete application should be exercised end to end.
The walking skeleton therefore needed tests exactly where its claims could fail. Not hundreds of speculative tests for features that did not exist.
Enough evidence to prove the route was real.
Does the HeartĀ Start?
The first test was gloriously undramatic.
Can the application boot under a controlled configuration?
Do settings validate?
Can the composition root assemble the catalogue, narrative and round services?
Can it select the configured repository adapter?
Are the expected routes registered?
Are there missing imports, circular dependencies or absent adapters waiting for the first player to discover them?
This is not yet a health test.
We are not asking whether a running process responds. We are proving that the application can become a running process in the first place.
Cheap. Fast. Boring. Exactly what we want from a heartbeat.
The patient had not solved a crime. But it had successfully become a patient.
Is It Breathing?
Once the process existed, the next test asked whether it could answer.
The liveness endpoint reports a narrow fact:
The process is alive and capable of responding.
Readiness answers a broader operational question. Version reports safe build and contract information. None of them claims that the graph works, the Academy contains evidence or every external provider is healthy.
That modesty matters.
A health contract should report what it actually knows. Not optimism.
Hercule checked the result.
The patient had a pulse. He wisely postponed declaring it fit for marathon fraud investigation.
Has the Academy Kept Its FilesĀ Sealed?
The catalogue tests establish the public policy.
The backend returns exactly four canonical path identifiers.
- Detective Academy is open.
- Puppy is coming soon.
- Adult Dog and Senior Dog are locked.
The Academy catalogue contains exactly one spoiler-free training case. The ranked catalogue returns no real case details.
This test is not merely checking a menu. It proves that the public development route does not accidentally become an information leak.
The Academy door is open. The evidence lockers upstairs remain closed.
Does Every Case Have a Beginning and anĀ End?
The narrative tests ask a different question.
For every published case, does an opening sequence exist?
Does a closing sequence exist?
Does each sequence contain pages?
Are positions contiguous and one-based?
Does every page contain narration and alternative text?
Iteration 01 exposes only the Academy opening.
The test still requires the closing sequence to exist. That prevents case publication from becoming structurally incomplete merely because the conclusion is not yet reachable.
The story begins now. The ending is registered for later. The player does not receive it early.
That is narrative design behaving like a contract rather than a folder full of images.
Can the Dog Find theĀ Door?
Then came the browser-level smoke test.
The automated journey follows the real path:
Launch
⤷ Choose your trench coat
⤷ Confirm Puppy is unavailable
⤷ Select Detective Academy
⤷ Open ACADEMY_001
⤷ Read comic page one
⤷ Move to page two
⤷ Refresh
⤷ Remain on page two
⤷ Enter the Academy board
⤷ Refresh
⤷ Remain in ACADEMY_001
The assertions are intentionally uncomplicated.
Can Hercule activate the controls?
Is the ranked path disabled?
Does the correct training case open?
Does the second comic page survive refresh?
Does the board display ACADEMY_001?
Does the same board return after another refresh?
This is where browser automation earns its keep.
Unit tests can prove that every door opens independently. Only Playwright discovers that somebody installed the corridor against a brick wall.
Did We Bring the RightĀ Case?
The navigation test proves that Hercule can enter a board. It does not automatically prove that he brought the correct file.
So the most meaningful assertion remains tiny:
- Select
ACADEMY_001.
2. Enter the board.
3. Verify that the board displays ACADEMY_001.
Not the first catalogue entry. Not a default case. Not a convenient fixture inserted by the frontend.
That single assertion follows information through the whole slice:
catalogue selection
⤷ round creation
⤷ case/version binding
⤷ opening sequence
⤷ lifecycle transition
⤷ workspace projection
Every stage must preserve the identity instead of reconstructing or guessing it.
The dog entered one side carrying ACADEMY_001. ACADEMY_001 came out the other.
That was the skeleton walking.
Does It Remember After the Lights GoĀ Out?
A browser refresh is useful. An application restart is a more convincing witness.
The persistence test creates an Academy round using SQLite, starts it and completes its introduction. Then it destroys the application.
A second application instance opens the same database and requests the workspace.
The expected result is still:
case_id: ACADEMY_001
status: ACTIVE
intro_completed_at: present
This proves that the selected training file and lifecycle are not merely surviving inside one Python object or one browser tab.
The lights can go out. The filing cabinet remembers.
Are the WallsĀ Real?
The architecture tests inspect the source itself.
Domain and port modules must not import FastAPI, Pydantic settings, SQLite or other infrastructure frameworks.
The production frontend must not define the canonical path catalogue a second time.
These tests are intentionally unfashionable. They do not prove player behaviour.
They prove that the implementation has not quietly violated the boundaries the player behaviour depends upon.
A module can pass every functional test while reaching through another moduleās private drawers.
Architecture tests catch the fingerprints.
No Pass, NoĀ Progress
There was one final rule.
Iteration 01 did not finish because the screens existed. It finished only when the complete slice passed its gate.
- If startup failed, we fixed startup.
- If the Academy exposed ranked case details, we fixed the catalogue.
- If refresh forgot page two, we fixed navigation.
- If the board opened during
INTRO_PENDING, we fixed lifecycle enforcement. - If
ACADEMY_001became a frontend constant, we fixed the contract boundary.
A failed test did not become a note for the next iteration. It created corrective work inside the current one.
The gate includes:
- Python boot and domain tests
- API and problem-contract tests
- narrative validation
- persistence and recovery
- architecture boundaries
- TypeScript type checking
- React component tests
- Playwright browser automation
Only when the entire gate is green should the iteration be closed and tagged.
That discipline matters because red tests carried forward become assumptions. Assumptions acquire dependencies. Dependencies eventually become architecture.
Hercule closed the report.
The build had the right to remain silent. The test suite had other ideas.
Part VIIāāāItĀ Lives
The First CompleteĀ Journey
And then, finally, Hercule played Fraud Graph Arena.
He launched the application. It answered.
He opened the path catalogue. Detective Academy was waiting. The ranked careers remained sealed.
He selected the Academy. The application loaded The Case of the Empty Evidence Board*.*
He opened the training file. The backend created a round. The round started. Its status became INTRO_PENDING. The Academy projected its first comic page.
Hercule moved to the second. He refreshed the browser. The second page was still there.
He entered the Academy. The round became ACTIVE. The board opened.
It displayed the correct path. The correct case. The correct version. The correct round.
Thenā¦
Nothing happened. No clues. No graph. No documents. No suspects. No suspicious transfers. No credits to spend. No accusation to make. No jazz-assisted analytical revelation.
Just an almost empty board carrying the correct training file.
It was, by conventional game-development standards, spectacularly unimpressive.
It was also exactly what Iteration 01 was supposed to prove.
For the first time, Fraud Graph Arena did almost nothing. But it did almost nothing end to end*.*
What Iteration 01 ActuallyĀ Proves
The empty board looked like very little.
Architecturally, it removed a surprising number of unknowns.
- We now know that the React frontend and Python backend can coexist inside one coherent product without becoming one undifferentiated application.
- We know the modular monolith can compose into a runnable system.
- We know the first versioned API contracts can survive real use.
- We know the catalogue can publish Academy content without revealing ranked cases.
- We know a case can require both an opening and a closing narrative sequence.
- We know a real round can carry path, case, version and lifecycle state.
- We know the opening comic can control access to the board without presentation becoming the authority.
- We know comic position can survive refresh through navigation state.
- We know the investigation itself can survive application reconstruction through durable persistence.
- We know infrastructure can remain behind replaceable adapters.
- And we know the complete route can be exercised automatically from launch to board.
None of those things is an exciting player feature. They are something more useful at this stage.
Eliminated uncertainties.
Yesterday we knew how the building was supposed to be divided. Today we know the doors between the rooms actually open.
What Iteration 01 Very Definitely Does NotĀ Prove
Version Zero refused to award itself points for capabilities that existed only in specifications.
Iteration 01 has to maintain the same discipline.
There is a route into an investigation. That does not mean there is much investigation waiting at the end.
The board contains no evidence. The graph does not work. There are no real records or documents. Zingg entity resolution has not entered the room. GraphFrames analytics have not entered the room. Genie-assisted retrieval has not entered the room. Credits, commands and evidence revelation remain future work.
There is no structured case file. There is no submission. There is no evaluator. The registered closing sequence cannot yet be selected or displayed. The ranked anthology remains unpublished.
And most importantly:
Iteration 01 proves absolutely nothing about whether the game is fun*.*
Hercule can finally reach the training room. There is simply very little training waiting there.
Why āIt Does Nothingā IsĀ Progress
Software projects become dangerous when they accumulate impressive parts before proving those parts can actually live together.
A beautiful graph engine here. A sophisticated persistence layer there. A polished interface somewhere else. A folder full of excellent comic art on another drive.
Each individually respectable. Together, perhaps, a collection of expensive strangers.
The walking skeleton reverses the order.
First the system learns to stand. Then it learns to walk. Then it learns to tell the player why they are entering the room.
Later it can carry evidence. Reveal relationships. Spend credits. Build a case. Evaluate an accusation. Select the correct ending. And eventually convict somebody who, with luck, deserves it.
The sophistication is postponed until there is somewhere safe to put it.
Build the boring path before the interesting destination.
A graph renderer can change. The visual design can change. A persistence adapter can change. A comic illustration can change. A training case can change.
Fundamental boundaries are more expensive.
Once presentation, narrative, navigation, game state, persistence and analytics become entangled, every replacement drags half the application into the evidence room with it.
That was the final luxury of the Academyās empty board.
There was almost nothing in the system yet. Which meant almost everything was still cheap to fix.
Hercule had rarely been so pleased to find an empty crime scene.
The Patient Has aĀ Pulse
Hercule stood in the Academy investigation room.
The evidence board was empty.
No photographs. No documents. No bank statements. No shell companies. No red string connecting nervous accountants to offshore biscuit manufacturers. Not even one suspiciously affluent Chihuahua.
Behind him, the application had done something far less dramatic and far more important.
The frontend had loaded. The backend had answered. The catalogue had kept the ranked files sealed. The Academy had returned a real training case. The round had been created and started. The opening comic had appeared in the correct order. Page two had survived a refresh.
The briefing had moved the investigation from INTRO_PENDING to ACTIVE. The board knew exactly why Hercule was standing there.
By every reasonable measure, it was one of the least impressive detective games ever created.
Hercule looked at the empty evidence board.
He smiled. The thing was alive. Now we could start giving it problems.
The skeleton could walk. The Academy would teach it what to carry.
Next in the Series: FGA 02āāāEvidence, NotĀ Answers
Putting the first clue on the AcademyĀ board
The Academy route exists. The training file opens. The narrative reaches the board. Now the empty workspace needs something worth investigating.
The next iteration introduces the first real Academy evidence contract: records, documents, events, direct relationships and carefully separated analytical relationships.
It also confronts the first rule every graph investigation must learn:
- A record is not necessarily an entity.
- An entity is not necessarily a suspect.
- A relationship is still not a verdict.
The examples will remain inside Detective Academy. The ranked cases will remain behind locked doors.
The building existed. The detective could reach his desk. The briefing was complete. Now somebody had to put evidence on the board.

Built like a detective epic, behaves like an empty roomāāāand thatās exactly theĀ point.
