🕵️ FGA 03: Screen Me Up Before You Go-Go 🐶
A detective story about screen contracts and browser history

Part I: The Corridor Crime Scene
Another Screen Walks into the Office
The rain had returned. It struck the windows of the Academy with the persistence of a frontend defect that had survived three refactors and acquired seniority.
Inside, the investigation board from FGA 02 was finally alive. The paper could hold content. The graph panel had room to breathe. The typewriter keys behaved like controls instead of painted suggestions. Full and compact layouts could disagree about furniture without disagreeing about meaning.
The room was ready. Then three screens arrived, each with its own route, event listener, loading flag, fade timer, focus behaviour and theory about where the player should go.
Soon the application had not one navigation system, but several local customs connected by browser history and optimism.
The symptoms were familiar:
- One component called
navigate()directly. - Another waited 500 milliseconds before doing the same thing.
- A third changed state first and route second.
- A fourth mounted, fetched data and quietly performed a game command because the information was already nearby.
- Every screen knew where it came from, where it went and how long darkness ought to last.
Nothing looked catastrophic in isolation. That was the problem.
Navigation spaghetti rarely arrives in one dramatic bowl. It appears strand by strand, each one entirely reasonable, until the application needs a fork and an incident report.
A Route Is an Address, Not Permission
The first correction was conceptual. A URL can identify a place:
/rounds/abc123/board
It cannot prove that:
- The round exists.
- It belongs to the current player.
- Its opening has been completed.
- Its status permits investigation.
- It has not already closed.
The browser may request the board. The backend still decides whether the round is allowed to produce an active workspace.
This distinction already existed in the Academy walking skeleton. An INTRO_PENDING round could not obtain the board; the application returned an INTRO_REQUIRED problem instead. Completing the opening moved the round to ACTIVE, after which the empty workspace became available.
The screen system therefore needed to coordinate presentation without becoming a second source of game truth.
Navigation is not authority.
Animation is not state.
Mounting is not a game event.
Hercule wrote the three lines in his notebook and underlined the third.
React components have a habit of entering rooms more than once during development.
A lifecycle hook that creates rounds, spends credits or completes briefings is not convenient. It is evidence tampering with a hook name.
The Original Suspect Python Screen Classes
The first architectural idea looked respectable:
class CaseIntroductionScreen(Screen):
async def on_load(self): ...
async def on_enter(self): ...
async def on_exit(self): ...
Python already controlled the backend. It knew the round. It knew the case. It knew which actions were valid. Why not let it represent the screens too?
Because knowing whether the player may enter the room is not the same as knowing where the browser should place the lamp.
Backend code should own authoritative facts:
CREATED
⤷ INTRO_PENDING
⤷ ACTIVE
Later domain iterations may extend that lifecycle with submission, evaluation, closure and recovery states.
Frontend navigation owns presentation states:
LAUNCH
⤷ PATH_SELECTION
⤷ CASE_SELECTION
⤷ CASE_INTRODUCTION
⤷ INVESTIGATION_BOARD
A future result flow can add case resolution without making the browser responsible for deciding the verdict.
Python classes could still expose safe lifecycle facts and commands. They should not reach into browser focus, animation timing, responsive layout or component mounting.
The backend knew the law. It did not need to arrange the furniture.
Part II: Five Suspects in One Corridor
Suspect One: Backend Controlled Screens
Strength: authoritative state remains central.
Weakness: presentation details cross the network boundary and become coupled to server code.
The backend can say:
The round is INTRO_PENDING.
The opening is available.
The board is forbidden.
It should not say:
Mount this React component, focus its second heading and fade
for 430 milliseconds.
Useful witness. Wrong jurisdiction.
Suspect Two: Frontend Component Hierarchy
React components naturally render browser interfaces. Shared parents can provide loading, errors and layout. Reusable components can handle cards, comics, boards and results. But component hierarchy alone does not define a journey.
Without another model, each screen begins to own:
- Route construction
- Transition timing
- Recovery logic
- Browser-history interpretation
- Assumptions about domain state
The components render well and navigate by folklore.
Suspect Three: The State Machine
A state machine makes movement explicit:
CASE_INTRODUCTION
+
INTRODUCTION_COMPLETED
[round is ACTIVE]
⬇
INVESTIGATION_BOARD
Now legal and illegal transitions can be tested without clicking through the whole application.
But a state machine does not render a comic, draw a board or load a catalogue. It is traffic law, not the city.
Suspect Four: JSON Driven Screens
A small excerpt shows version metadata and registered screen references:
{
"schema_version": "1.0",
"document_version": "1.0.0",
"minimum_runtime_version": "1.0.0",
"id": "CASE_INTRODUCTION",
"component": "CASE_INTRODUCTION",
"data_source": "ROUND_OPENING"
}
Then somebody asks for one condition.
Then a calculated target.
Then a network call.
Then a retry.
By coffee number four, the configuration has variables, expressions, inheritance and the emotional confidence of a language runtime with no debugger.
JSON is excellent evidence. It is a terrible place to hide an interpreter designed during a caffeine incident.
Suspect Five: The Hybrid
The hybrid gave every participant one narrow job:
Backend ➜ validates game authority
State machine ➜ validates navigation
Configuration ➜ describes screens and transitions
Components ➜ render reusable screen families
Effects ➜ animate presentation changes
Schemas ➜ reject invalid definitions
This architecture did not eliminate complexity. It stopped mailing copies of the complexity to every screen.
The hybrid was arrested, questioned and offered supervised employment.
Part III: The Screen Contract
Every Screen Gives a Statement Under Oath
Reusable did not mean identical. Path selection, case selection, a comic introduction and an investigation board do not share the same anatomy. Forcing them into one universal component would produce forty-seven optional properties and a switch statement large enough to require its own postcode.
They could still share one contract.
A compact excerpt from the production introduction definition looks like this:
{
"schema_version": "1.0",
"document_version": "1.0.0",
"minimum_runtime_version": "1.0.0",
"id": "CASE_INTRODUCTION",
"component": "CASE_INTRODUCTION",
"data_source": "ROUND_OPENING",
"actions": [
{
"id": "COMPLETE_INTRO",
"handler": "COMPLETE_INTRO"
},
{
"id": "SKIP_INTRO",
"handler": "SKIP_INTRO"
}
],
"transitions": [
{
"event": "INTRODUCTION_COMPLETED",
"target": "INVESTIGATION_BOARD",
"effect": "FADE_TO_BLACK",
"history": "REPLACE",
"guard": "ROUND_ACTIVE"
},
{
"event": "INTRODUCTION_SKIPPED",
"target": "INVESTIGATION_BOARD",
"effect": "FADE_TO_BLACK",
"history": "REPLACE",
"guard": "ROUND_ACTIVE"
}
]
}
The definition describes the participants. Typed code performs the work.
onLoad: Prepare the Room
A loader may retrieve:
- Catalogue sections.
- Cases for the selected path.
- The current opening sequence.
- The active workspace.
- A future safe result projection.
A loader may not:
- Create a round.
- Complete an introduction.
- Reveal evidence.
- Submit a case.
- Decide a verdict.
Loading is a read concern. Commands remain explicit.
This boundary matters because screen setup may repeat. Development checks, remounts, retries, refreshes and interrupted navigation can all cause another load. Safe reads should tolerate repetition; authoritative mutations should never depend on a component appearing exactly once.
The Academy evidence already demonstrates the right separation: round creation, start and opening completion are explicit operations; opening and workspace are reads. Start and completion are designed to be idempotent where retry safety requires it.
onEnter: Presentation, Not History
Entering a screen may:
- Move focus to a semantic heading or landmark.
- Announce the new view.
- Activate keyboard handling.
- Restore safe local presentation state.
- Start an approved decorative effect.
It may not infer that the player has completed anything merely because the component mounted.
The final comic page being visible does not activate the round. The player action does.
The backend accepts it. Then navigation responds.
onExit: Clean Up Without Rewinding
Exiting may:
- Cancel obsolete reads.
- Remove screen listeners.
- Stop decorative effects.
- Clear transient messages.
- Preserve an allowed local draft.
It may not undo authoritative history.
Leaving the board does not return the round to INTRO_PENDING.
Leaving a future result screen will not make the case mutable again.
Browser navigation moves through addresses; it does not negotiate with time.
Actions: Labels Are Not Identities
A visible control might say:
Enter the Academy
Its semantic identity is:
COMPLETE_INTRO
The label may be translated, shortened or moved to a typewriter key. The action remains the same.
The sequence is:
player activates control
⤷ registered handler sends request
⤷ backend accepts or rejects
⤷ handler emits semantic outcome
⤷ state machine resolves navigation
A click is a request. Not a verdict.
Transitions: Outcomes Not Route Commands
Prefer:
INTRODUCTION_COMPLETED
over:
GO_TO_BOARD
The first describes what happened. The state machine decides where that outcome leads from the current state.
This leaves room for a future briefing summary or accessibility step without changing the meaning of the action.
A transition contains four distinct ideas:
event ➜ what happened
guard ➜ whether movement is valid
target ➜ where the journey goes
effect ➜ how the change appears
Mixing them produces components that know too much and timeouts that know even more.
Validation: Valid JSON Can Still Lie
Parsing proves that the commas survived.
Qualification must also check:
- Supported schema version.
- Stable screen ID.
- Registered component.
- Registered data source, action handler, guard and effect.
- Compatible references.
- Unique action IDs.
- Existing transition targets.
- Reachable required screens.
- Deliberate terminal states.
- No forbidden unknown properties.
One screen can be individually valid and still point into the void.
Polite documents also lie when nobody compares their statements.
Part IV: Reusable Screen Families
Reuse Meaning Without Forcing Coincidence
The common renderer should not become every screen.
Its job is smaller:
load definition
⤷ validate
⤷ resolve the registered component
⤷ provide common lifecycle services
⤷ mount the screen component
⤷ coordinate actions and transitions
The family component understands its room.
Path Selection
Path selection renders the canonical investigation paths returned by the catalogue:
DETECTIVE_ACADEMY
PUPPY
ADULT_DOG
SENIOR_DOG
Availability comes from the server-owned catalogue. The component does not keep a private list that eventually disagrees with it.
Selecting a path changes navigation context. It does not create a round.
Case Selection
Case selection displays safe metadata for the chosen path.
In the verified Academy baseline, the public case is:
ACADEMY_001
The Case of the Empty Evidence Board
Opening it performs an authoritative operation:
create round
⤷ bind path, case and case version
⤷ start round
⤷ receive INTRO_PENDING
⤷ enter case introduction
The component does not optimistically build a board route and hope the backend catches up.
Case Introduction
The introduction family renders a versioned opening sequence with accessible page content, progress and safe image fallbacks.
Page position is presentation state and may live in the URL:
?page=2
Completing or skipping the sequence is an explicit command. The backend moves the round to ACTIVE; only then may the board load.
Game Board
The board family mounts the office created in FGA 02:
- Case paper
- Graph viewport
- Typewriter capability controls
- Responsive full and compact compositions
The Screen Engine owns entry, loading, failure and exit.
The board owns interaction inside the room.
Selecting a graph node is not a navigation transition. Responsive recomposition is not a game event. Opening a paper section does not need to appear on the application state diagram.
The board remains intentionally empty in the verified baseline. That is not an omission disguised as suspense. It is the scope.
Case Resolution
Case resolution belongs in the contract because the journey will eventually close.
It needs a reusable family, terminal semantics and safe result data. It must never calculate the verdict in the browser or expose protected truth.
But the evidence reviewed for this article does not establish a public, fully qualified resolution route in the current playable baseline.
So the architecture prepares the door. It does not claim somebody has already walked through it.
This is less dramatic than inventing a green check mark. It is also how evidence works.
Part V: JSON as Evidence Not Sorcery
Data References and Executable Behaviour
Every value inside a screen definition belongs to one of three categories.
Data
Static, inspectable values:
- IDs
- Message references
- Design viewport
- Safe asset references
- Bounded visual parameters
- Version metadata
Registered References
Names resolved by approved registries:
- Component
- Data source
- Action handler
- Guard
- Effect
Behaviour
Executable work:
- API calls
- State mutation
- Validation logic
- Focus movement
- Cancellation
- Animation implementation
Behaviour belongs in typed code.
The rule is simple:
Configuration may describe data and select registered capabilities. It may not smuggle arbitrary behaviour inside strings.
Conditions Without an Expression Language
This is dangerous:
{
"visibleWhen": "round.status === 'ACTIVE' && credits > 0"
}
It looks compact because the parser, type system, debugger and security model are all missing from the screenshot.
Prefer a registered policy:
{
"availability": "round-action-available"
}
The implementation receives a safe projection and returns a structured result:
AVAILABLE
UNAVAILABLE_PENDING
UNAVAILABLE_LOCKED
UNAVAILABLE_COMPLETED
A grey button without a reason is merely a locked door whose architect has stopped returning calls.
Bounded Sequences Without a Workflow Language
Some declarative screen runtimes support a bounded sequence of registered actions. That can be useful for controlled presentation work such as playing optional audio and running a reveal effect.
The boundary is not “arrays are forbidden.”
The boundary is:
- the action catalogue is closed;
- payloads are validated;
- sequences cannot call arbitrary code;
- domain mutations still go through explicit authoritative handlers;
- configuration cannot invent loops, branches, retries or deep object access.
A short, typed sequence is configuration.
A general workflow engine hiding inside onEnter is a suspect with forged papers.
Schemas and Versions
Each definition should identify:
schema_version
document_version
minimum_runtime_version
These answer different questions:
- Which contract language does the document use?
- Which revision of this screen is it?
- Which runtime can interpret it safely?
Unknown fields should fail where strictness is intended. References should be checked against registries. The complete catalogue should be checked as a graph.
The build rejects definitions that violate the screen schema.
Startup validation checks the loaded screen set and its registries.
One minimal recovery boundary must remain outside the declarative catalogue, because an invalid catalogue cannot be trusted to render its own obituary.
Migrate Then Render One Current Model
When a schema evolves, use an explicit versioned migration where compatibility is possible.
Do not scatter this archaeology through every render:
if oldField
else if olderField
else maybe newField
Migrate.
Validate.
Render one current internal model.
Old evidence deserves preservation, not permanent authority over the present.
Part VI: One Shared Fade
Several Screens Had Invented Darkness
The old pattern looked harmless:
setIsFading(true);
setTimeout(() => navigate(nextRoute), 500);
Another component used 400 milliseconds.
A third changed route before the fade.
A fourth waited for transitionend and assumed the browser would always deliver the invitation.
Every implementation produced black pixels. None agreed on what the application was doing while the pixels changed.
The Correct Sequence
For the Academy introduction, the transition should be:
player requests completion
⤷ action becomes pending; concurrent actions are blocked
⤷ backend confirms the round is ACTIVE
⤷ handler returns INTRODUCTION_COMPLETED
⤷ state machine resolves INVESTIGATION_BOARD
⤷ coordinator accepts and locks the transition
⤷ registered fade effect completes
(immediately when reduced motion is preferred)
⤷ route and history commit
⤷ board model loads
⤷ board heading receives focus and readiness
is announced
Do not fade before the backend answers. Otherwise a rejected command leaves the player staring into darkness while the application decides how to reverse its confidence.
Noir permits tragedy. It does not require lying to the player.
One Transition Lock
Once a screen instance commits an outgoing transition, later requests from that instance are stale or duplicate.
This protects against:
- Double clicks.
- Repeated success callbacks.
- Retries completing late.
- Two effects competing for the same source screen.
The frontend lock protects presentation.
Backend idempotency protects authoritative state.
Both are necessary. A button being disabled is not a security boundary, and a database constraint is not a pleasant user experience.
The Old Screen Stops Accepting Input
During the exit effect, the old screen may still be visible. It should no longer behave as active.
Keyboard commands, pointer actions and duplicate semantic events must be suppressed or rejected deliberately.
The screen is present to finish leaving. It is not taking new cases.
Cancellation and Cleanup
Every effect needs deterministic disposal:
- Cancel timers.
- Remove effect-owned listeners.
- Abort obsolete reads where possible.
- Ignore stale completions.
- Remove temporary overlays.
- Release focus work owned by the old transition.
A transition event may never arrive if the CSS transition is cancelled or removed, so decorative completion must have a bounded fallback. The fallback does not decide navigation; it prevents decoration from holding the corridor hostage.
The lamp may fail. The door must still open.
Reduced Motion
Reduced motion changes the theatre, not the destination.
The semantic transition remains:
CASE_INTRODUCTION
+
INTRODUCTION_COMPLETED
⬇
INVESTIGATION_BOARD
The effect may become immediate or nearly immediate. The player should not wait through an invisible 500-millisecond ceremony merely because the animated version had one.
Focus Crosses Deliberately
After a single-page application changes views, focus must not fall onto the document body like a detective losing the address of the crime scene.
The target family defines a semantic focus destination:
SCREEN_HEADING
BOARD_LANDMARK
RESULT_SUMMARY
The component resolves the actual element. Configuration does not contain brittle CSS selectors describing the third heading inside the second div behind the sofa.
Part VII: Test the Whole Journey
One Successful Navigation Is Not an Alibi
A browser test can prove that one fortunate journey worked:
click Start
click Academy
click ACADEMY_001
complete opening
expect board
Useful.
Not sufficient.
The state model must also answer:
- Can the board be reached before the opening completes?
- Can an unknown event move the application?
- Can the player become trapped in a nonterminal screen?
- Can browser Back restore an obsolete lifecycle?
- Can a duplicate event run the transition twice?
- Does every target exist?
- Can an old round be reopened after future closure?
Test Legal and Illegal Transitions
Legal:
CASE_INTRODUCTION
+
INTRODUCTION_COMPLETED
[round ACTIVE]
⬇
INVESTIGATION_BOARD
Illegal:
CASE_INTRODUCTION
+
INTRODUCTION_COMPLETED
[round INTRO_PENDING]
⬇
rejected
Also illegal:
LAUNCH
+
INTRODUCTION_COMPLETED
⬇
rejected
A semantic event is not a global command. It is meaningful only from the correct state and context.
Test the Catalogue as a Graph
The navigation catalogue should prove:
- One deliberate entry state.
- Every required screen reachable.
- Every nonterminal screen has a way forward.
- No missing target.
- No unexplained cycle.
- No path bypasses a mandatory lifecycle step.
For the verified Academy flow, every route from case selection to the board must pass through the opening requirement.
The backend already blocks the bypass.
The frontend should not keep attempting the crime merely because another layer will stop it.
Route Reconstruction
A refresh starts with an address and authoritative context, not the memory of the previous component.
Given:
route = /rounds/abc123/board
round = ACTIVE
render:
INVESTIGATION_BOARD
Given the same address with:
round = INTRO_PENDING
recover to:
CASE_INTRODUCTION via the safe INTRO_REQUIRED recovery
The route is evidence. The round is authority.
Browser Back Is Not Rollback
Browser Back moves through session history. It may request an old introduction address after the round has become active.
The loader checks the round before displaying obsolete screen content.
The board remains the valid presentation.
Later, when the domain gains terminal closure, Back must not reopen a mutable investigation.
History navigation is not game-history mutation. Hercule wrote it down again in case the browser had merely overlooked the first copy.
What the Available Evidence Actually Covers
The supplied Academy contracts and tests cover the important baseline claims:
- four canonical path identifiers.
- Academy open while ranked paths are unavailable.
- ACADEMY_001 bound to the Academy path.
- CREATED ➜ INTRO_PENDING ➜ ACTIVE.
- board access blocked before introduction completion.
- start and completion designed as idempotent operations.
- opening page state and board reconstruction requirements.
- active board reporting zero evidence and zero suspects.
The declarative Screen Engine materials also show versioned screen documents, registered actions/effects and schema validation work.
They do not justify claiming that every future screen family, complete result flow and full browser qualification have already shipped.
A test plan is not a test result.
A schema is not a playable journey.
A convincing architecture diagram is not an alibi.
Part VIII: Bodies Found in the Corridor
The Double Transition
The player clicks once.
The request succeeds.
A second callback arrives before the first screen fully exits.
Both see the same valid source state.
Without a transition lock, both proceed.
The final screenshot still shows the correct board.
Behind it lie two route writes, two focus moves and a timeout wondering whether it still works here.
A correct destination does not acquit the journey that produced it.
The Screen That Loaded Twice
A component remounts and performs its loader again.
Safe read: acceptable.
Hidden round creation in onLoad: two rounds and one ghost file.
The fix is not a clever “mounted once” flag.
The fix is restoring the boundary:
Loaders prepare screens. Explicit actions change the game.
The Stale Callback
The player leaves.
An old request or effect completes.
Its response belongs to a previous path, round or screen instance.
Results must be context-bound. Transition tokens must expire. Obsolete callbacks may finish privately; they may not change the current application.
A witness who missed the interview cannot enter the verdict by post.
The Universal Component
The common renderer gradually acquires branches for every family.
Then family-specific loading.
Then comic paging.
Then board overlays.
Then result rendering.
Soon DeclarativeScreen is the entire application wearing a reusable name badge.
The renderer should resolve a family, not impersonate all of them.
Reuse stable responsibilities:
- shell
- focus
- loading orchestration
- action dispatch
- transition coordination
- error boundary
Keep family meaning inside family components.
The Route and State Disagree
The screen changes but the route write fails.
The player sees the board under an introduction address.
Refresh reconstructs something else.
Route and navigation state must be part of one committed transition outcome. Preparation may be temporary; public state may not maintain two versions of where the player is.
Part IX: Evidence Handling Rules
The autopsy produced a shorter constitution.
Rule 1: Routes Are Requests Not Permissions
Resolve them against current authoritative context.
Rule 2: Mounting Is Not a Domain Event
No round creation, credit spending, reveal, submission or lifecycle completion inside presentation hooks.
Rule 3: Loaders Read and Actions Command
Loaders must tolerate repetition. Commands must be explicit and idempotent where retries matter.
Rule 4: Components Emit Outcomes
Use INTRODUCTION_COMPLETED, not GO_TO_BOARD.
Rule 5: The State Machine Coordinates Navigation Only
It does not evaluate fraud, calculate credits or become the backend in a trench coat.
Rule 6: Configuration Describes and Selects
No arbitrary code, deep domain-property tunnels or surprise network clients inside JSON.
Rule 7: One Screen Instance Commits One Exit
Later events are stale, duplicate or rejected.
Rule 8: Effects Decorate Valid Movement
They do not grant it, reverse it or hold it hostage.
Rule 9: Recovery Preserves Semantic State
A failed board load remains a board failure for an active round. It does not resurrect the introduction.
Rule 10: Adding a Screen Does Not Add Traffic Laws
A new screen may require a definition, family, handler and tests.
It should not require a private router, fade, loading philosophy and storage strategy.
The project should remember these rules through schemas, architecture tests and state-model tests, because developers have other responsibilities.
Some of them are trying to draw graphs.
Part X: Playable Evidence
The Corridors Behave
Hercule launches Fraud Graph Arena.
The current entry screen loads.
He enters the path catalogue.
The server returns:
DETECTIVE_ACADEMY
PUPPY
ADULT_DOG
SENIOR_DOG
He chooses Detective Academy.
The Academy catalogue exposes:
ACADEMY_001
The Case of the Empty Evidence Board
He opens it.
A real round is created and bound to the path, case and case version.
The round starts:
CREATED ➜ INTRO_PENDING
The opening sequence loads.
Hercule reaches the final page.
The board remains forbidden.
He completes the briefing.
The backend moves the round to:
ACTIVE
Only then may the application enter the board.
The correct case appears.
The evidence count is zero.
The suspect count is zero.
The graph has nothing to confess.
This is the verified playable baseline:
Launch
⤷ path selection
⤷ Detective Academy
⤷ ACADEMY_001
⤷ opening comic
⤷ explicit opening completion
⤷ active empty board
The declarative Screen Engine gives that journey a common vocabulary: versioned definitions, registered actions, reusable screen families, shared effects and validation.
The evidence reviewed here does not extend the verified playable route through submission and case resolution. Those remain architectural contract and future gameplay work, not decorative green ticks.
What This Article Proves
The production Academy journey now uses a versioned screen set, registered handlers and one transition coordinator. Unit and browser tests cover route reconstruction, history, transition locking, focus and recovery.
Adding another screen no longer needs another private theory of navigation.
What It Does Not Prove
Be explicit.
- There is still no real evidence on the verified empty board.
- The graph remains empty.
- The typewriter capabilities remain unavailable in that baseline.
- No real case submission is evaluated here.
- No public case-resolution journey is claimed as qualified by the reviewed evidence.
- Declarative screens do not automatically create good UX.
- Schemas cannot detect confusing copy.
- A state machine cannot determine whether the game is fun.
- Future authentication, recovery and long-running gameplay may require richer state structures.
- Case packages still need a common data contract.
The corridors are organised.
The rooms are still waiting for clues.
Iteration labels may change. Architecture should not need to lie about what is already playable.
The Case Remains Open
The verdict was procedural. The backend retained game authority; one screen runtime validated the contracts and coordinated presentation. The Academy journey now follows a single route and transition model. The next file brought a different kind of trouble.
Every case still arrives carrying its own names, identifiers, files, relationships, evidence and assumptions.
One calls a company organisation.
Another calls it business_entity.
A third calls it dog_with_tax_number and appears proud of the decision.
All three insist they are perfectly normal.
Hercule returns to the board and opens a fresh notebook.
Next in the Series
FGA 04 The Dog Ate My Data Contract
Creating one common model for every case
The next investigation asks whether every case can speak the same language when it arrives:
Entities
Relationships
Events
Evidence
Provenance
Ground truth
Because “we will normalise it later” is not a plan. It is the opening sentence of a data-platform horror story.
Source Code
Browse the source code for FGA 03 on GitHub ↗

The journey is tested, the board is still empty, and the next data contract is waiting.
