1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright 2024-2026 Jonathan Shook
// SPDX-License-Identifier: Apache-2.0
//! Lifecycle event vocabulary — the kind-tag [`EventType`]
//! plus its [`SubjectKind`].
//!
//! [`EventType`] names the lifecycle slot that triggered a
//! render (`on_phase_end`, `on_update`, …); [`SubjectKind`]
//! names what kind of subject the firing context represents
//! (session / phase / iteration / scope). Together they are
//! the shared lifecycle vocabulary used by the readout binder
//! (to validate and dispatch readouts) and by the checkpoint
//! log (to tie a durable data record to its lifecycle kind-tag).
//!
//! See SRD-63 §4.1. Two kinds of events exist:
//!
//! - **Lifecycle events** — fire exactly once per
//! `(slot, subject)`. `_start` and `_end` are
//! delaminated; nothing fires twice for the same
//! subject under one slot.
//! - **Refresh events** — fire repeatedly while the
//! subject is in flight. Currently only [`EventType::Update`].
//!
//! Push 2 wires `Update` and `PhaseEnd` through the activity
//! pipeline; the remaining variants are reachable from Push 3
//! onward as the workload-side `readouts:` parser maps slot
//! names to events and Push 4 wires wildcard binding to
//! cover scope and session lifecycles.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum EventType {
/// `on_session_start` — the run is opening.
SessionStart,
/// `on_session_end` — the run is closing. Final render
/// for any session-scoped readout.
SessionEnd,
/// `on_phase_start` — a phase is opening. Equivalent to
/// today's deleted phase-starting log row when the
/// `phase_starting` readout is bound.
PhaseStart,
/// `on_phase_end` — a phase is closing. The ✓ DONE line
/// (`phase_outcome` readout) is bound here today.
PhaseEnd,
/// `on_each_start` — a `for_each` / `for_combinations`
/// iteration is opening. The current scope-ancestor
/// header (`· for_each profile=…`) becomes the
/// `scope_header` readout bound here in Push 3.
EachStart,
/// `on_each_end` — an iteration is closing.
EachEnd,
/// `on_scope_start` — a non-iteration scope group is
/// opening.
ScopeStart,
/// `on_scope_end` — a non-iteration scope group is
/// closing.
ScopeEnd,
/// `on_update` — periodic refresh tick. Today's inline
/// progress thread fires this at 0.5 s; the TUI fires
/// it per-frame. Drives the live status content.
Update,
}
impl EventType {
/// Lower-snake-case name matching the `readouts:` slot
/// keyword (`on_update`, `on_phase_end`, …). Used by
/// the `trace` diagnostic readout and by Push 3's
/// workload-block parser.
pub fn slot_name(self) -> &'static str {
match self {
EventType::SessionStart => "on_session_start",
EventType::SessionEnd => "on_session_end",
EventType::PhaseStart => "on_phase_start",
EventType::PhaseEnd => "on_phase_end",
EventType::EachStart => "on_each_start",
EventType::EachEnd => "on_each_end",
EventType::ScopeStart => "on_scope_start",
EventType::ScopeEnd => "on_scope_end",
EventType::Update => "on_update",
}
}
/// What kind of subject the context that fires this
/// event represents. Used by the binder to validate at
/// bind-time that bound readouts accept the slot's
/// subject kind. `Update` rides on the surrounding
/// phase, so it reports `Phase`.
pub fn subject_kind(self) -> SubjectKind {
match self {
EventType::SessionStart | EventType::SessionEnd => SubjectKind::Session,
EventType::PhaseStart | EventType::PhaseEnd | EventType::Update => SubjectKind::Phase,
EventType::EachStart | EventType::EachEnd => SubjectKind::Iteration,
EventType::ScopeStart | EventType::ScopeEnd => SubjectKind::Scope,
}
}
}
/// What kind of subject a context (and a render) is
/// scoped to. Determined by the firing event and the
/// surface that built the context. Builtins declare
/// which kinds they accept; the binder validates at
/// bake-time.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SubjectKind {
/// The whole run. Used for `on_session_start` /
/// `on_session_end`.
Session,
/// A single phase activity. Used for `on_phase_start` /
/// `on_phase_end` / `on_update`.
Phase,
/// One iteration of a `for_each` / `for_combinations`
/// scope. Used for `on_each_start` / `on_each_end`.
Iteration,
/// A non-iteration scope group (`do_while` /
/// `do_until`). Used for `on_scope_start` /
/// `on_scope_end`.
Scope,
}
impl SubjectKind {
/// Lower-snake-case name for the storage / replay
/// surface. Stored in the `readout_snapshots.subject_kind`
/// column so `nmbrs replay` can group rows by subject.
pub fn as_str(self) -> &'static str {
match self {
SubjectKind::Session => "session",
SubjectKind::Phase => "phase",
SubjectKind::Iteration => "iteration",
SubjectKind::Scope => "scope",
}
}
}