Mob.Invariant.Builtins (mob v0.8.3)

Copy Markdown View Source

The checks the framework ships with, and an honest list of the ones it does not yet.

MOB-156 names ten. Two are implemented here, because those two are the only ones observable from the BEAM today without hooks that do not exist. The rest are listed in unimplemented/0 with what each needs, rather than being half-written against state nobody records — a registry advertising ten checks and running two would be exactly the kind of claim this epic keeps having to retract.

The component-ownership one leads because three of the four agents in MOB-149 named that class independently.

Summary

Functions

A dead screen still present in a router's navigation.

Register the built-in checks. Idempotent.

A live component whose owning screen process is dead.

The eight checks MOB-156 names that are not implemented, and what each needs.

Functions

dead_screen_in_nav(arg1)

@spec dead_screen_in_nav(map()) :: Mob.Invariant.result()

A dead screen still present in a router's navigation.

Expects %{router: pid} in the context. A dead pid in the stack means a later pop returns to a corpse: the router restarts it and the user's state is gone, or the entry is skipped and the back stack lies about where it goes.

install()

@spec install() :: :ok

Register the built-in checks. Idempotent.

orphaned_component(context)

@spec orphaned_component(map()) :: Mob.Invariant.result()

A live component whose owning screen process is dead.

The registry keys entries by {screen_pid, id, module}, and reaping happens on reconcile or when the owner goes down. A component that is still alive under a dead owner is a leaked process holding a native handle — the class MOB-100 and its follow-ups kept re-fixing.

Sampled at :on_screen_stop, which runs inside the screen that is stopping — so this never sees that screen's own components: it is still alive, running its own terminate/2. What it sees is what an earlier screen left behind, one teardown later. A check written expecting otherwise would silently never fire.

Confirmation matters here more than anywhere: the router stops screens in a tight loop, so during a multi-screen reset this samples while the previous screen's components are mid-reap. Measured with the confirmation rule in place: 0 violations across 60 healthy teardowns.

Reaping is defence in depth — a monitor :DOWN, an :EXIT from a linked screen, and ComponentRegistry.reconcile/2 on the next paint — so no single-fault injection produces a real leak through the ordinary path. This check is verified against the orphan state itself rather than against a reproduction of the fault that would cause it. See the decision record.

unimplemented()

@spec unimplemented() :: [{atom(), String.t()}]

The eight checks MOB-156 names that are not implemented, and what each needs.

Kept as data rather than prose so it can be asserted on: a test pins that the registry never silently claims to run one of these.