journal + determinism tests + Creusot verification scaffolding

Phase 1 — semantics doc
  docs/002. hsmc-semantics-formal.md

Phase 2 — deterministic execution journal
  hsmc/src/journal.rs (TraceEvent, JournalSink, ActionKind)
  hsmc-macros: __chart_journal! emission at every observable site
    (entry/exit/action/transition/timer arm-cancel-fire/during/emit/
    event delivery/terminate). Per-state during count helpers, stable
    chart-hash via FNV-1a. Action observation moved out of run_action
    and into call sites so ActionKind is accurate.
  Public API on the generated machine: journal(), take_journal(),
    clear_journal(), CHART_HASH.

Phase 3 — verification crate (Creusot scaffolding)
  verification/{Cargo.toml,mise.toml,requirements.txt,INVARIANTS.md}
  verification/src/{event_queue.rs,timer_table.rs} — Pearlite
    contracts on simplified mirrors of the runtime types. cargo
    creusot generates Coma IR cleanly; VCs not yet discharged.
  Correspondence tests drive the verified mirror and the runtime
    type with the same op sequences and assert observable parity.

Tests — 113 deterministic-flow tests across 22 files. Each compares a
  hand-built expected journal byte-for-byte against actual, anchored
  to a spec section: initial entry, lateral/self/up transitions,
  bubbling, timers, emit, ordering, durings, step/dispatch
  equivalence, root-targeting, current_state, termination.

Tooling — mise.toml (root): stable Rust + cargo subcommands.
  verification/mise.toml: pinned nightly-2025-11-13 + opam + python
  venv + uv. just verify is fully self-installing.

Bug fix — root state targetable by the chart's own name (e.g.
  `on(Trig) => ChartName;`) per spec §"How transitions work".

CHART_HASH — stable FNV-1a fingerprint over the chart's structural
  definition; mismatch on replay = different chart.
This commit is contained in:
swaits committed 2026-04-27 20:15:05 -06:00
1 parent 4d7baf7236
commit 081bd28cca
55 files changed
+6476 -151

No files matched your search

+25
View File
@@ -28,6 +28,13 @@ pub struct StateBody {
/// split borrow on the generated `select` call site; the macro emits a
/// clearer error when it can detect overlap at expansion time.
pub durings: Vec<During>,
/// `trace;` — root-only flag. When set, codegen injects calls to
/// `::hsmc::__chart_trace!(...)` at every state entry, state exit,
/// action dispatch, and transition. The runtime backend (defmt /
/// log / tracing / no-op) is selected by which `hsmc/trace-*` cargo
/// feature the user enables in their `Cargo.toml`. Off by default,
/// fully zero-cost when off.
pub trace_enabled: bool,
}
impl StateBody {
@@ -45,6 +52,7 @@ impl StateBody {
handlers: Vec::new(),
children: Vec::new(),
durings: Vec::new(),
trace_enabled: false,
}
}
}
@@ -196,6 +204,23 @@ fn parse_body(input: ParseStream, is_root: bool) -> syn::Result<StateBody> {
}
body.event_ty = Some(ty);
}
"trace" => {
let _: Ident = input.parse()?;
input.parse::<Token![;]>()?;
if !is_root {
return Err(syn::Error::new(
ident.span(),
"`trace;` is only valid at root",
));
}
if body.trace_enabled {
return Err(syn::Error::new(
ident.span(),
"duplicate `trace;` declaration",
));
}
body.trace_enabled = true;
}
"entry" => {
let _: Ident = input.parse()?;
input.parse::<Token![:]>()?;