When embedded firmware crashes, the reboot usually erases the evidence that explains why. Latch preserves the device's last useful state across reset, then queues it for delivery when the device is back. The embedded runtime is heap-free C11 with bounded buffers: you keep control of storage, transport, and reset policy.
without Latch HardFault -> reboot -> "could not reproduce"
with Latch HardFault -> retained snapshot -> reboot -> persistent spool -> your transport
+-> CPU context, reset reason, build ID,
breadcrumbs, metrics, and health data
Live capture of a physical ESP32 executing the intentional-panic fixture: Latch persists the event, the board aborts and reboots, then a public collector validates both LEP envelopes and returns durable acknowledgements. Idle time is compressed. The complete procedure, decoded evidence, and current Xtensa limitation are documented in the ESP32 HIL fixture.
Once Latch is initialized, its storage and transport are registered, and ls_boot() has completed, useful evidence is three calls away:
ls_breadcrumb("sensor:read");
ls_metric_u32("battery_mv", 3264);
ls_capture_message("sensor timeout", LS_SEVERITY_ERROR);Architecture ports can capture fault state automatically. On the next boot, Latch promotes the retained snapshot into a transactional spool; normal runtime can then call ls_flush() to deliver a deterministic LEP v1 envelope through the best available transport.
You need CMake 3.20+, Ninja, and a C/C++ compiler. The host demo uses the same public in-memory storage, transport, capture, and decoding APIs as an embedded integration.
git clone https://github.com/laststate/latch.git
cd latch
cmake --preset host-debug
cmake --build --preset host-debug
ctest --preset host-debug
cd build/host-debug
./latch-host-example
./latch-dump latch-demo.lstOn Windows, use latch-host-example.exe and latch-dump.exe. The demo writes a binary envelope, and latch-dump validates its header and walks every length-checked TLV.
Captured 291-byte LEP envelope in latch-demo.lst
LEP v1 type=2 (error) arch=0 (unknown) flags=0x00 sequence=1 event=b63f7832 payload=263
tlv type=1 (identity) length=71 value_hex=...
...
See the complete host example for initialization, in-memory storage, and transport registration.
Have an ESP32? The copyable
ESP32 first crash tutorial starts with
only idf.py, triggers a real panic, recovers the flash-backed event after
reboot, and stores a durable ACK without requiring a hosted service.
latch-dump keeps its existing binary-file interface and also accepts bounded hexadecimal input with --hex. From the repository root:
build/host-debug/latch-dump --hex tests/vectors/lep-v1-basic.hexASCII whitespace is accepted between hexadecimal digits. Decoded input is limited to LS_MAX_EVENT_SIZE; odd-length, non-hexadecimal, and oversized inputs fail before envelope validation. Add --json (before or after --hex) for a dependency-free machine-readable document whose byte values are lowercase hexadecimal:
build/host-debug/latch-dump --json --hex tests/vectors/lep-v1-basic.hexThe documented top-level keys and TLV order are stable for the 0.x line;
consumers must ignore additional JSON keys. value_hex is always lowercase,
two characters per byte, and malformed input emits no partial JSON document.
For pipes and scripted collection, use - as the input path:
cat report.lep | build/host-debug/latch-dump --json -An encrypted envelope is represented as validated structural metadata only;
latch-dump never guesses a key or exposes unauthenticated plaintext.
Latch is built for failures where ordinary logging becomes least reliable:
- CPU context and fault status on supported Cortex-M, RV32, and Xtensa integrations;
- reset reason, boot counters, previous uptime, boot-loop and OTA state;
- fixed-capacity breadcrumbs, metrics, logs, power, health, and performance samples;
- selected stack or memory regions with explicit exclude, zero, or hash redaction;
- firmware identity and a reproducible build ID;
- retry and acknowledgement state in a CRC-protected persistent spool.
No allocator, scheduler, network stack, or hardware register map is hidden inside the core. Integrators provide the memory, timestamp, reset normalization, storage backend, and one or more transports.
fault -> retained minimal snapshot -> reboot -> LEP envelope --+
|
error -----------> bounded state snapshot -> LEP envelope -----+-> persistent spool
|
v
normal runtime -> transport -> ACK
The critical path stays deliberately small. Unknown LEP TLVs are skippable, interrupted spool records are ignored during recovery, and retained fault state is cleared only after it is promoted successfully. Read the architecture, concurrency contract, and wire format for the invariants.
- Link
laststate::latchor the smaller modules you need. - Provide identity and a monotonic timestamp to
ls_init(). - Register retained or flash-backed storage and at least one transport.
- Call
ls_boot()early, after the backend needed to recover the previous boot is ready. - Add breadcrumbs and metrics around the paths that are expensive to reproduce.
- Call
ls_flush()from normal runtime when delivery is allowed. - Install the architecture fault/trap integration and qualify it on the real board and toolchain.
For a vendored CMake checkout:
add_subdirectory(third_party/latch)
target_link_libraries(firmware PRIVATE laststate::latch)Or pin a release with CMake FetchContent:
include(FetchContent)
FetchContent_Declare(
latch
GIT_REPOSITORY https://github.com/laststate/latch.git
GIT_TAG v0.5.0
)
FetchContent_MakeAvailable(latch)
target_link_libraries(firmware PRIVATE laststate::latch)Both paths have standalone, CI-tested examples under
examples/add-subdirectory and
examples/fetch-content.
Start with the integration overview, then choose a port or a native integration. Rust firmware can use the #![no_std] SDK over the C runtime.
| Failure mode | Ordinary logging | Latch |
|---|---|---|
| Fault occurs while the scheduler or heap is damaged | May allocate or depend on a task | Bounded, heap-free capture path |
| Device resets before upload | Last lines are often lost | Retained snapshot and persistent spool |
| Link is unavailable during the incident | Delivery fails in the fault path | Retry after boot through another transport |
| Firmware changed before reproduction | Logs may lack exact identity | Build ID travels with the envelope |
| Captured memory contains secrets | Ad hoc filtering | Registered regions and explicit redaction policies |
Latch is not a hosted observability backend and does not own the product's networking. It produces a portable, verifiable evidence envelope and hands it to infrastructure you control.
The portable runtime is split into core, capture, envelope, spool, storage, transport, metrics, and security libraries. The complete target is laststate::latch.
Architecture and platform support includes Cortex-M, RV32/RV64, Xtensa/ESP-IDF, Linux signal capture (including AArch64 Linux), STM32/RP plus Nordic/NXP/Microchip/TI/Silicon Labs reset-port boundaries, FreeRTOS, Zephyr, generic acknowledged streams, mbedTLS, BLE GATT, CAN/CAN-FD, LoRaWAN, cellular socket offload, and a CryptoAuthLib secure-element adapter. Each architecture and vendor entry has an explicit evidence tier in platform support; source-level integration is not a claim of board qualification.
Before selecting hardware, consult the public hardware compatibility matrix. It records the exact MCU, board, toolchain, fault-handler evidence, flash evidence, status and last accepted physical HIL date without treating emulator or compile results as qualification.
Feature switches and buffer capacities live in include/laststate/config.h. Production profiles can remove stored strings, disable features, and shrink buffers that the firmware does not need.
Latch supports XChaCha20-Poly1305 envelopes, HKDF-SHA-256 domain separation, replay windows, authenticated at-rest storage, and hardware-backed key contracts. These mechanisms still require a hardware CSPRNG, per-device provisioning, verified TLS, and an independent review for the product threat model. Latch has not claimed an independent cryptographic audit or MISRA compliance; read security, audit and compliance status and the security policy before enabling encryption or dumps.
The portable runtime and wire format are extensively host-tested. Hardware fault entry, linker placement, flash geometry, reset registers, vendor networking, TrustZone boundaries, and secure elements must be qualified on each selected board and toolchain. Host tests are not hardware certification. The exact release gates are in production readiness and implementation status.
On 2026-07-29 the complete flash → panic → reboot → UART → durable ACK path
was verified on a physical ESP32-D0WD-V3 with ESP-IDF 5.5.0 against the
production LastState Relay collector. Relay validates and persists LEP
envelopes before acknowledging them; it is a private LastState component and
was not publicly released on that date. Latch does not depend on Relay—the
ESP32 HIL fixture documents the public framing
contract, the from-zero procedure, decoded evidence, fixes and the
version-pinned Xtensa panic-hook boundary.
For safety- or mission-critical integrations, start with Production readiness, Commercial AUV integration readiness, and the AUV observability runtime guide.
- v0.3.0 release notes
- AUV observability runtime
- v0.2.0 release notes
- Integration overview
- Architecture
- LEP v1 wire format
- Security and storage
- Threat model and key rotation
- Registry packages
- Ports, platform support, and native integrations
- Footprint measurement, Linux signal capture, and security/audit/compliance status
- Hardware-in-the-loop qualification
- Hardware compatibility matrix
- Production reference designs
- Production readiness
- LTS policy and independent audit invitation
- AI-assisted development
- Release procedure
- Roadmap and changelog
New contributors should start with a good first issue. Integration experience, documentation fixes, test vectors, board reports, and small tooling improvements are all valuable.
Read CONTRIBUTING.md for the fastest validation path and CODE_OF_CONDUCT.md for the community standard. AI-assisted contributions should also follow the tool-neutral agent guide. Use GitHub Discussions for integration questions and design ideas. Report vulnerabilities privately as described in SECURITY.md.
The maintainer target is to acknowledge new issues and pull requests within 72 hours. It is a target, not an SLA; one friendly ping after seven days is welcome.
cmake --preset host-debug
cmake --build --preset host-debug
ctest --preset host-debug
cargo check --manifest-path rust/latch/Cargo.tomlFor a separate compact source distribution, while keeping the reviewable checkout untouched:
python tools/minify_sources.py --output ../latch-production --verifyRelease automation validates the compact distribution and generates release archives, checksums, an SPDX SBOM and build provenance. Registry publication remains an explicit, separate maintainer action described in the release procedure. Repository automation is documented in docs/automation.md.
Apache-2.0. See LICENSE.
For production/AUV deployments, configure LS_COMMERCIAL_PROFILE=ON. The runtime then fails closed
unless an externally-audited crypto provider is installed and passes provider KATs. The portable
built-in backend is intentionally reported as builtin-unqualified. See
docs/security/crypto-assurance.md.
Mutation assurance now includes a broad critical-runtime campaign in addition to the fast smoke
suite. See docs/security/mutation-assurance.md.
