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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! `ops.jsonl`: the durable action-outcome log (DESIGN §4.2, §15 Y15).
//!
//! yog is a pure renderer of disk except for two owned files; this is one of
//! them. Every **attempted** yog-initiated CLI action appends one JSON line to
//! `<yog_state_root>/ops.jsonl` — `{ts, argv, cwd, exit, stdout, stderr}` — so
//! both instances tail one shared history instead of holding gate/close output
//! RAM-only (§4.2 as amended: "closes the one durability leak"). A *completed*
//! run logs its real outcome; a spawn or non-spawn **step failure** logs a
//! synthetic line ([`OpEntry::synthetic_failure`] / [`OpEntry::step_failure`])
//! so no error class is un-logged — the §7.3 failed-action row depends on it.
//!
//! **Atomicity by size cap.** The line *including its newline* is hard-capped
//! at [`CAP`] = 4096 bytes (PIPE_BUF): a write that size or under is a single
//! atomic `O_APPEND` on Linux, so two instances never interleave.
//! [`build_line`] is the pure capper — it truncates `stdout`, then `stderr`,
//! keeping heads and stamping `"truncated":true`; the fixed fields
//! (`ts`/`argv`/`cwd`/`exit`) never truncate, so a pathological argv is the one
//! case a line may exceed the cap (structurally unavoidable; §4.2 is silent
//! there — we stay strict-otherwise).
//!
//! **Append-only, with exactly one operator-initiated exception.** No line is
//! ever rewritten — a row is a projection of what was true when it was written,
//! and the read-time folds below are how it gains detail. The exception is
//! [`clear`] ([`operator`], bl-c417): the operator asking for a fresh trail
//! truncates the file and logs *that* as the new trail's first row. Durability
//! (§4.2) is the promise that yog never loses an outcome **silently**; a
//! discard the operator asked for, which leaves its own record behind, loses
//! nothing silently.
//!
//! **No clock here.** `ts` is a data field, stamped upstream from the caller's
//! clock; nothing in this module reads wall-clock time, keeping [`build_line`]
//! and the tail parser pure and deterministic.
//!
//! **One field is not stored here.** A detached spawn's `stderr` is captured to
//! a per-spawn sink file and folded into the row at read time
//! ([`detached`]) — the log records the launch, the sink records what the
//! launched process said.
use fs;
use ;
use Path;
/// The hard cap on a serialized line *including* its trailing newline, in
/// bytes. 4096 = PIPE_BUF: at or under it an `O_APPEND` write is atomic.
pub const CAP: usize = 4096;
/// The log's leaf name under the yog state root (§4.2).
const FILENAME: &str = "ops.jsonl";
/// How many `ops.jsonl` lines the trail carries (§4.2, §11 accessory) — **one
/// bound, named once**. The derivation tails this many into the snapshot, and
/// the §11 activity accessory asks [`Query::Ops`](crate::boundary::Query::Ops)
/// for this many, so the pane and the fold behind it cannot disagree about how
/// much trail there is. It lives here rather than beside either reader because
/// it is a fact about the log.
pub const OPS_TAIL: usize = 256;
/// `exit` sentinel for a piped verb whose status was unobservable
/// (`ExitInfo::Unknown`, [`crate::cli_outbound::ExitInfo::shell_code`]): the
/// process **ran** — not a rendered failure ([`rows::OpRow::failed`]).
pub const PIPED_UNOBSERVED: i32 = -1;
/// `exit` sentinel for the detached `litany prompt` (§8.1, §13.3): the row is
/// written the moment the child launches, while its status arrives arbitrarily
/// later (the reaper thread takes it and discards it — bl-3016 — since
/// `ops.jsonl` is append-only and never rewritten), so `-2` records **the
/// handoff itself**: "launched detached; exit deliberately unobserved". It says
/// that and only that (bl-afa9) — a spawn that never launched is a
/// [`SYNTHETIC_EXIT`] line like every other never-launched spawn, so this
/// sentinel can no longer stand for two opposite facts. The one thing that can
/// still make a `-2` row [`rows::OpRow::failed`] is text the *launched* child
/// wrote on stderr, folded in from its sink at read time ([`detached`]).
pub const DETACHED_EXIT: i32 = -2;
/// `exit` sentinel for a **synthetic failure line** (§4.2 as amended): an
/// attempted action that produced no process status — a spawn that never
/// launched (piped or detached), or a non-spawn yog-step failure
/// ([`OpEntry::synthetic_failure`] / [`OpEntry::step_failure`]). The failure
/// text always rides in `stderr`; which of the two it is reads off `argv[0]`
/// ([`exit::ExitKind`]), the [`YOG_STEP`] pseudo-binary naming the stepwise one.
pub const SYNTHETIC_EXIT: i32 = -3;
/// `exit` sentinel for a **drift line** (§7.2 instrumentation): not an attempted
/// action at all, but an observation yog made about *its own* event stream — a
/// sweep or the watch backend finding a change nobody announced. It rides
/// `ops.jsonl` because that is where yog's durable, two-instance-shared trail
/// already lives, and is deliberately **not** a [`rows::OpRow::failed`] row: a
/// drift is an alarm about the watcher, not a failed operator action, so it must
/// not hijack the §7.3 failure banner. It carries its own count on the §11
/// activity chip instead ([`live::Activity`]) — a query over the tail, not a
/// stored counter.
pub const DRIFT_EXIT: i32 = -4;
/// `argv[0]` of a drift line (§7.2). The drift *kind* rides as `argv[1]` — e.g.
/// `["yog-drift","unannounced"]` — and the roots it names ride in `stderr`, one
/// per line, so the §11 accessory's existing expand-a-row affordance shows the
/// attribution with no new surface.
pub const YOG_DRIFT: &str = "yog-drift";
/// `argv[0]` of a non-spawn step-failure line (§4.2): the logical step name
/// rides as `argv[1]`, e.g. `["yog-step","mint"]` — an error class with no real
/// binary still gets a rendered ops row (the §7.3 failed-action row depends on
/// every error class having one).
pub const YOG_STEP: &str = "yog-step";
/// `argv[0]` of a **capability-answer** line (§8.6): the operator's answer to a
/// held tool invocation, or a per-conversation floor raised or lowered. These
/// rows are at once the audit and the memory the capability control folds on
/// read (`["yog-control","answer",<tool-use-id>,<verdict>]`,
/// `["yog-control","floor",<conversation-id>,"raise"|"lower"]`) — the alignment
/// monitor's own pattern, so answering needs no fourth durable artifact and I2
/// holds at three. Written by the boundary's answer actions (bl-765d, bl-94b4);
/// read by [`crate::control::judge`], which is the whole reason the grammar has
/// one home rather than two.
pub const YOG_CONTROL: &str = "yog-control";
/// **The record itself** — [`OpEntry`] and its synthetic constructors; its own
/// file per §12's budget.
pub use OpEntry;
/// The pure ≤[`CAP`] line serializer and the caller-side argv clip (§4.2). Split
/// out of this file per §12's line-budget discipline.
use parse_line;
pub use ;
/// Append `entry`'s capped line to `<state_root>/ops.jsonl` via `O_APPEND`,
/// creating the state dir if absent. Atomic against a concurrent instance by
/// the [`CAP`] size bound.
/// The last `max` parseable entries, oldest-first (newest-last). A missing file
/// or unreadable bytes yield an empty view; each line parses forgivingly — a
/// corrupt or mid-write-torn line is skipped, never an error.
/// View-models over the log (§4.2, §7.3, §11), split out per §12's line budget:
/// `rows` = the expandable [`OpRow`];
/// `live` = §6's retirement projection over a tail of rows ([`OpOutcome`]: which
/// failures are still live — the log keeps every failure, prominence is derived)
/// + [`Activity`].
pub use ;
pub use OpRow;
/// **What a row stands at**, the §7.3 carrier (bl-4d81): [`live`]'s retirement
/// projection folded with [`operator`]'s ack watermark into one total
/// [`Standing`] per row, so the failure banner crosses the §8.5 boundary as a
/// fact a seat renders rather than five derivations it re-implements. Its own
/// file rather than a third of [`live`], which is already at §12's pre-split
/// band.
pub use ;
/// The one reading of the `exit` field (§4.2's sentinels) and the one home of
/// its wording: [`exit::ExitKind`] plus the `OpRow` half that asks it
/// everything — `failed`, `drift`, `exit_label`.
/// The §7.3 attribution — which surface an attempted action came from, and the
/// one thing that lets a banner tell its own failures from someone else's.
pub use Origin;
/// The detached driver's captured stderr (§8.1, §13.3): the per-spawn sink file
/// and the read-time fold that projects its tail into the row.
/// **What a detached launch produced** (§8.1, §13.3, bl-b95e) — the state a
/// `-2` row's failure is derived from, which decides whether the sink above is
/// read at all. It replaced the marker table (`opslog::notice`, bl-1296) that
/// tried to tell a dying line from a benign one by its words.
/// The operator's own two lines (§4.2 as amended, bl-c417): the **ack** — a
/// global seen-watermark that quiets every failure-derived alarm without
/// hiding a row — and the **clear**, the one gesture that ends a trail, which
/// logs itself as the next trail's first row.
pub use since_ack;
pub use ;