A release gate in CI¶
A notebook whose exit code is the result. Checks evaluate every rule and report, a scoreboard shows every result, and raise_if_failed() at the end turns failures into exit code 1 — which any CI system turns red. On demo data the gate fails by design (the generator's weak cell trips the undervoltage floor), and one --param override flips it green.
You'll use: check.that() on series · CheckResults · raise_if_failed() · --param waivers · exit codes
Run it¶
Copy the notebook below into release-gate.md, then:
The notebook¶
---
description: A parameterized pass/fail runbook with exit codes CI understands.
requires-python: ">=3.10"
dependencies:
- zelos-sdk[notebook]
params:
window: "-3m"
cell_floor: 3.0
current_max: 40.0
---
Checks report, they don't raise — so every rule below runs and the page shows
the whole board, not just the first failure. The last cell is the gate: it
raises `AssertionError`, the run's outcome is `failed`, and the process exits
`1`.
This gate holds the battery pack to its undervoltage floor. On demo data it
**fails by design** — the generator plants one weak cell — and passes when
you relax the floor:
```python {.norun}
# In CI, or a terminal:
# zelos notebook run release-gate.md -o release-gate.html # exit 1: one cell sags
# zelos notebook run release-gate.md --param cell_floor=2.5 # exit 0: floor relaxed
```
```python
from zelos_sdk import CheckResults, connect
agent = connect()
cells = agent.query("bus0/BMS_message/cells.*", start=params.window)
cells
```
## Every cell holds above the undervoltage floor
One rule per cell, all eight scored. `CheckResults` is a list that knows its
own pass/fail count, so it renders as a board once every rule is in it:
```python
results = CheckResults(
agent.check.that(series, ">", params.cell_floor, name=path)
for path, series in cells.short_names().items()
)
```
## Pack current stays inside its envelope
A check takes the series you already queried, so this rule is scored over the
same window as the eight above:
```python
current = agent.query("bus0/BMS_message/status.pack_current", start=params.window)
results.append(
agent.check.that(
current["bus0/BMS_message/status.pack_current"],
"<",
params.current_max,
name="pack current",
)
)
results
```
## The gate
One cell turns the scoreboard into an exit code. If anything failed or
errored, this raises `AssertionError` naming each one — the run prints the
traceback, its outcome is `failed`, and CI goes red with the rendered
notebook as the evidence:
```python
results.raise_if_failed()
```
Parameterize the thresholds, keep the notebook in the repo next to the code
it guards, and the gate evolves in review like everything else. The
`--param` override is the escape hatch for waivers — explicit, logged in the
run command, and visible in the rendered notebook.
What to notice¶
- Every rule always runs. Because checks report instead of raising, the board shows all results — the gate cell at the end is the only place failure becomes an exception. See Notebooks as tests.
raise_if_failed()raises a plainAssertionError, which is what makes the run's outcomefailedrather thanerrored. A single rule needs no scoreboard:assert agent.check.that(...).- Exit codes are the contract:
0succeeded,1failed or errored,2infrastructure failure,130interrupted. A CI step needs nothing else. --paramis the explicit waiver. Relaxing a threshold happens on the command line, visible in the run and in the rendered notebook — never by editing the rulebook.-o release-gate.htmlwrites the evidence: a self-contained page with every board, ready to attach to the release.- Each rule's window is the query's window, so the gate's coverage is whatever
params.windowsays. See find the weak cell.