Skip to content

Commit 08b770c

Browse files
feat(run-journal): measure lifecycle journal ceiling for #635
Extend the measurement harness with volume mode (real journal scan, representative and worst-case synthetic runs) and record the raise-bounds design decision before any production ceiling change. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a3b2fba commit 08b770c

3 files changed

Lines changed: 494 additions & 10 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Issue #635: journal ceiling decision (measure, then design)
2+
3+
Status: design decision awaiting grading. No production implementation in this change.
4+
5+
## Measured numbers
6+
7+
Artifact: `.brigade/measurements/issue-635-journal-ceiling.json` (local, same shape family as `issue-568-slice6.json`: environment metadata plus structured aggregates; volume mode adds bounds and scenario blocks).
8+
9+
Current hard bounds: `MAX_JOURNAL_EVENTS = 512`, `MAX_JOURNAL_BYTES = 8 MiB`.
10+
11+
| Scenario | Events | Bytes | % event ceiling | % byte ceiling | Hit ceiling? |
12+
| --- | ---: | ---: | ---: | ---: | --- |
13+
| Scanned real runs (n=13, single-seat journal-hardening runs) | p50/max 17 | p50 14,279 / max 14,286 | 3.3% | 0.17% | no |
14+
| Synthetic representative (1 seat, 1 attempt, 0 pauses) | 11 | 7,780 | 2.1% | 0.09% | no |
15+
| Synthetic roster-sized (11 seats, 2 attempts, 3 pause/resume cycles) | 155 | 124,777 | 30.3% | 1.5% | no |
16+
| Synthetic worst case (32 seats, 3 attempts, 8 pause cycles) | 512 | 420,683 | 100% | 5.0% | yes, mid-dispatch |
17+
18+
Worst-case stop reason: `LifecycleJournalError: bound exceeded: journal event sequence above MAX_JOURNAL_EVENTS` at `seat-28` attempt 1. Bytes were still ~5% of the 8 MiB cap.
19+
20+
## Decision
21+
22+
Raise the event bound. Do not segment the journal yet.
23+
24+
Reasoning:
25+
26+
1. Event count is the binding constraint. At a full 512-event journal the file is only ~0.4 MiB.
27+
2. Realistic and roster-sized load stays far from 512 (3% real, 30% for 11 seats with retries and three approval cycles).
28+
3. The ceiling is reachable, but only under a synthetic fleet larger than today's 11-seat roster with three attempts per seat.
29+
4. Segmentation (cross-linked `lifecycle.NNNNN.jsonl`, reader/projector/doctor/recovery awareness) is a large surface change. The measured gap does not justify that cost before a cheaper bound raise plus a soft warning.
30+
5. Keep segmentation as the explicit escape hatch if #593 budget events or larger fleets push roster-sized runs past ~50% of the raised ceiling.
31+
32+
Recommended production change (separate implementation issue, after this design is graded):
33+
34+
- Raise `MAX_JOURNAL_EVENTS` from 512 to 2048 (4x).
35+
- Leave `MAX_JOURNAL_BYTES` at 8 MiB for now (still ~20x headroom at 2048 median-sized events).
36+
- Add a doctor soft threshold at 75% of the event bound (1536 of 2048): `WARN`, not `FAIL`.
37+
- Keep the hard refuse-to-append behavior only at the new ceiling, and make that failure visible (already true today via `LifecycleJournalError`).
38+
39+
## Soft-threshold doctor warning
40+
41+
When `brigade doctor` inspects a run journal and `event_count >= ceil(0.75 * MAX_JOURNAL_EVENTS)` while still under the hard ceiling:
42+
43+
- status: `WARN`
44+
- check name: `runs: journal event headroom`
45+
- detail shape: `lifecycle journal at {event_count}/{MAX_JOURNAL_EVENTS} events ({pct}%); raise or segment before the hard ceiling halts appends`
46+
47+
Below 75%: silent on this check. At or above the hard ceiling: keep today's `FAIL` / `bound exceeded` path for recovery verdicts that already treat oversize journals as failed.
48+
49+
## Recovery after upgrade for a run already at the old ceiling
50+
51+
A run that stopped at sequence 512 under the old bound has a complete, chain-valid journal and paired checkpoints. It does not need rewrite or segmentation to become readable again.
52+
53+
After the bound raise ships:
54+
55+
1. Readers (`read_journal` / `read_journal_bounded`) accept the existing 512-event file because it is under the new ceiling.
56+
2. Doctor recovery checks that previously returned `FAIL` / `bound exceeded` solely for the old event ceiling clear once the constant moves.
57+
3. If the run is still live or being resumed, the active lock owner may append sequence 513+ again; checkpoint pairing and projection continue from the existing chain tip.
58+
4. No migration tool is required for journals that stopped exactly at 512. Journals that somehow exceed the new byte bound remain fail-closed (unchanged).
59+
60+
## Explicit non-goals for the follow-up implementation issue
61+
62+
- No journal segmentation.
63+
- No change to worker/provider event streams (#592 stays separate).
64+
- No silent truncation or dropping of lifecycle facts when the hard ceiling is hit.
65+
66+
## Reproducing the measurement
67+
68+
```bash
69+
python scripts/measure_run_journal.py \
70+
--mode volume \
71+
--scan /path/to/.brigade/runs \
72+
--output .brigade/measurements/issue-635-journal-ceiling.json
73+
```

0 commit comments

Comments
 (0)