core/docs/decisions/ADR-0158-reboot-event-audit.md
Shay 5045700484
feat(W-024): reboot_event audit trail entry (L10b.3, ADR-0158) (#284)
* feat(W-024): reboot_event audit trail entry (L10b.3, ADR-0158)

L10 scope §Sub-question 3: a reboot_event analog of TurnEvent, written
to the telemetry JSONL, lets future audit reconstruct when this engine
instance lost and regained its lifetime.

- serialize_reboot_event / format_reboot_event_jsonl in chat/telemetry.py
  emit type="reboot" with restored_turn_count, stored/current revisions,
  revision_matched, recognizers_count, candidates_count
- ChatRuntime._load_engine_state() buffers the JSONL line in
  _pending_reboot_payload (str|None); ChatRuntime.attach_telemetry_sink()
  flushes it exactly once when a sink is first attached
- Reboot event precedes all turn events in the session audit stream
- Pinned by 11 tests: serializer structure, determinism, revision_matched
  logic, runtime integration (emit-once, no-checkpoint, no-load-state,
  revision match, ordering)

Closes L10b: W-022 (atomic writes) + W-023 (revision warning) + W-024
together satisfy ADR-0146's atomic/observable/auditable checkpoint triad.

* fix(W-024): expose cached public git revision helper
2026-05-25 20:37:00 -07:00

3.8 KiB

ADR-0158 — reboot_event audit trail entry (W-024 / L10b.3)

Status: accepted Date: 2026-05-26

Context

L10 scope §Sub-question 3 asked:

What does reboot record? A reboot_event analog of TurnEvent, written to the audit trail, that lets future audit reconstruct the fact that this engine instance lost and regained its lifetime here.

ADR-0156 §"Out of scope" deferred this to L10b.3 / W-024. After W-022 (atomic writes) and W-023 (revision-mismatch warning), the checkpoint path is now correct and observable. The missing piece is the audit record itself: without it, the telemetry JSONL cannot distinguish a fresh start from a reboot recovery, making the engine's lifetime history uninterpretable.

Decision

Serializer (chat/telemetry.py)

Add serialize_reboot_event(...) and format_reboot_event_jsonl(...), following the same pattern as serialize_correction_event / ADR-0059:

{
  "type": "reboot",                  // discriminator
  "restored_turn_count": 5,          // manifest["turn_count"]
  "stored_revision": "abc123",       // manifest["written_at_revision"]
  "current_revision": "def456",      // _git_revision() at load time
  "revision_matched": false,         // true iff both known and equal
  "recognizers_count": 2,            // DerivedRecognizers restored
  "candidates_count": 1,             // DiscoveryCandidates restored
  "timestamp": "..."                 // optional, caller-provided
}

revision_matched is False when either side is "unknown" or "" — the same guard as W-023's warning suppression, keeping the two features consistent.

Runtime wiring (chat/runtime.py)

The telemetry sink is None at __init__ time — attached later via attach_telemetry_sink(). Two-step buffered emission:

  1. _load_engine_state() — builds and stores the JSONL string in self._pending_reboot_payload: str | None. The string is the fully serialized line; no further computation required at flush time.

  2. attach_telemetry_sink(sink) — if _pending_reboot_payload is set and sink is not None, emits the line and clears the field. Subsequent calls to attach_telemetry_sink (e.g. replacing the sink) do NOT re-emit; the payload is consumed exactly once.

This placement guarantees the reboot event appears before any turn events in the audit stream for the session, matching the semantic intent of "lifetime boundary record".

Trust boundary

Metadata only — no surface text, no tokens, no versor coordinates. _git_revision() returns a short SHA derived from the local git repo; already used for the manifest and W-023 warning.

Invariants pinned by tests

tests/test_adr_0158_reboot_event.py (11 tests):

Serializer:

  • Structure, fields, and revision_matched logic (match / mismatch / unknown)
  • Optional timestamp field
  • Byte-identical output for identical inputs (determinism)

Runtime integration:

  • Reboot event emitted to sink on attach_telemetry_sink after checkpoint load
  • Emitted exactly once — second attach_telemetry_sink call does not re-emit
  • No event when no checkpoint directory exists (fresh start)
  • No event when no_load_state=True
  • revision_matched: true when revisions agree
  • Reboot event precedes synthetic turn events in the stream

L10b closure

With W-024, the L10b sequence is complete:

Chunk Work item ADR What it does
L10b.1 W-022 ADR-0156 Atomic checkpoint writes (write-temp + fsync + rename)
L10b.2 W-023 ADR-0157 Revision-mismatch RuntimeWarning on load
L10b.3 W-024 ADR-0158 reboot_event audit trail entry

The checkpoint path now satisfies all three of ADR-0146's runtime-model commitments: atomic, observable (warning on staleness), and auditable (reboot recorded in the telemetry stream).