Skip to content

0.8.2

Choose a tag to compare

@github-actions github-actions released this 11 Sep 22:03
· 24 commits to master since this release
b7ed145

Added

  • Post-mortem collection — Mob.PostMortem +
    Mob.PostMortem.BeamCrashDump
    (MOB-158, phase 1). The framework
    now picks up the crash dumps the BEAM leaves behind on disk and turns
    each into a Mob.Defect.Capsule on Mob.Defect.Bus (kind:
    :beam_crash, owner: :mob).

    Mob.PostMortem.sweep/0 and Mob.PostMortem.sweep/1 do a one-shot
    scan of default and caller-supplied paths. Nothing runs
    automatically — an app opts in from its on_start; a developer or CI
    can call it from IEx. Same discipline the rest of Mob.Defect
    follows: mob owns the format and the bus, never becomes the
    collector.

    Mob.PostMortem.BeamCrashDump scans for erl_crash.dump files,
    reads a bounded 8 KB header (slogan / system version / taints /
    atoms / dump version), computes a sha256 for artifact identity, and
    never touches the dump body. Dumps stay on disk for offline
    inspection with crashdump_viewer. Mob.PostMortem.Registry
    records emitted ids in a public ETS table so a re-sweep is a no-op;
    registry state is intentionally not persisted across BEAM restarts,
    because a fresh BEAM sweeps back into a fresh bus.

    Fingerprint is the normalized slogan: binary literals (including
    ones truncated by the 8 KB header cut-off) and long numeric runs
    that are the printable form of the same are stripped, so two
    {badarg, ...} crashes on io:put_chars/2 with different embedded
    payloads share a triage row instead of each opening one.

    Severity is picked from the slogan: allocator panics
    (eheap_alloc, binary_alloc, ets_alloc, sl_alloc,
    driver_alloc, fix_alloc, std_alloc) and boot-time crashes
    (Kernel pid terminated, Runtime terminating during boot) are
    :fatal; a literal normal exit is :info; anything else is
    :critical.

  • iOS MetricKit ingest for Mob.PostMortem.IOS (MOB-179). Replaces
    the phase 1 scaffold. Mob.PostMortem.sweep/0 on iOS now attaches an
    MXMetricManagerSubscriber on first call (lazy — no work for apps
    that never opt in), buffers OS-delivered payloads in a bounded native
    queue (32 entries, oldest-dropped on overflow), and drains them into
    Mob.Defect.Capsules on each subsequent call. Ships in release —
    MetricKit is the whole reason MOB-158 exists.

    Payload → kind mapping: MXCrashDiagnostic → :native_crash /
    :fatal; MXHangDiagnostic → :anr / :critical;
    MXCPUExceptionDiagnostic and MXDiskWriteExceptionDiagnostic →
    :perf_regression / :warning. (These are the individual
    diagnostics; the MXDiagnosticPayload container that carries them
    is what MetricKit hands the delegate.)

    Fingerprint groups a crash by (kind + top-frame binary + top-frame
    offset) so the same crash across launches becomes one triage row.
    Redaction: MetricKit call-stack payloads carry only mangled symbols,
    binary UUIDs and image names — safe identifiers, no user data. The
    full MXDiagnosticPayload.JSONRepresentation rides on evidence,
    bounded by the capsule's existing truncation.

    iOS 14+ required (didReceiveDiagnosticPayloads: is iOS 14+). Older
    builds hit the fallback path and return []. Android's stub NIF also
    returns [] — ApplicationExitInfo is a separate ticket (MOB-180).

  • Android ApplicationExitInfo ingest for Mob.PostMortem.Android
    (MOB-180). Replaces the phase 1 scaffold. Mob.PostMortem.sweep/0
    on Android now pulls the OS-held history of process exits via
    ActivityManager.getHistoricalProcessExitReasons (API 30+), filters
    against a persistent marker at
    <filesDir>/mob_post_mortem_appexit_marker.txt so each exit emits
    exactly once across boots, and turns each new entry into a
    Mob.Defect.Capsule on Mob.Defect.Bus.

    Reason code → kind mapping: REASON_CRASH / REASON_CRASH_NATIVE
    / REASON_SIGNALED → :native_crash / :fatal; REASON_ANR →
    :anr / :critical; REASON_LOW_MEMORY /
    REASON_EXCESSIVE_RESOURCE_USAGE → :oom / :fatal;
    REASON_USER_STOPPED / REASON_USER_REQUESTED / REASON_EXIT_SELF
    / REASON_DEPENDENCY_DIED / REASON_OTHER / REASON_FREEZER →
    :user_kill / :info.

    Fingerprint groups by (kind + process_name + reason_code) — the
    same class of exit for the same process across boots becomes one
    triage row. The emitted capsule carries reason code, pid,
    timestamp, process name, and the OS-generated description string.
    Trace file contents (an ANR's stack text) are NOT included this
    phase — they can carry app strings and need the same discipline the
    receipt module documents before they can safely reach the bus.

    Implementation is pure Zig JNI in android/jni/mob_nif.zig rather
    than a MobBridge.kt.eex template addition. Every existing mob
    app gets the feature the moment they bump mob, with no template
    refresh dance. See
    decisions/2026-09-11-appexit-native-jni-not-bridge.md. iOS
    registers a stub nif_post_mortem_android_drain returning [] so
    the shared mob_nif.erl NIF list resolves on both platforms.