Public: anyone can read this; the crew can change it.

A pure read says the gate minutes: check-slot minutes spent on merge-queue gates and on pull checks over the last hour and 6 h #6435

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

This adds `readGateMinutes(db, now, publicOnly)` in `src/flow/gate_minutes.ts`. It returns `{ gate1h, gate6h, pull1h, pull6h }`: check-slot minutes over the last hour and the last 6 h, rounded to 0.1 min. A check that started before the window counts only its minutes inside it: Σ max(0, min(end, now) − max(start, now − window)). A check still running ends at `now`, and a queued check counts nothin…

shipped production is serving it deploy ef63cd6

opened this

This adds readGateMinutes(db, now, publicOnly) in src/flow/gate_minutes.ts. It returns { gate1h, gate6h, pull1h, pull6h }: check-slot minutes over the last hour and the last 6 h, rounded to 0.1 min. A check that started before the window counts only its minutes inside it: Σ max(0, min(end, now) − max(start, now − window)). A check still running ends at now, and a queued check counts nothing. It is one SQL read and uses the same publicOnly rule as readVitals. Nothing reads it yet.

Look at first: the issue assumed gate minutes were already stored, but they weren't. service.gate() writes no run row, and the merge queue keeps its batches only in memory. Gates could not be counted, so this pull starts recording them:

  • New table: migration 20261026174649-check-gate.ts adds check_gate (repository, pull, commit, started_at, finished_at).
  • Recorder: src/checks/gate_record.ts has beginGate/endGate. service.gate() opens a row when the gate starts and closes it in its finally, so every outcome closes it. Writing a new row also deletes rows older than a week.
  • Crash case: a row left open by a crashed forge is cut off at start + GATE_DEADLINE_MS (15 min), the longest a gate may hold a slot. Without that, it would count forever.

What counts as a pull check: every run of kind check. That includes a branch's checks after a push or a land, not only pulls' checks, because the issue defines pull checks as "the rest". If the display issue wants main's checks counted separately, it can filter on issue_id.

Tests: test/flow-gate-minutes.test.ts covers:

  • a check straddling a window's edge being clipped;
  • a running check counting up to now, and a queued one counting nothing;
  • gates and pull checks kept apart;
  • an open gate row being cut off at the deadline;
  • publicOnly hiding private repositories;
  • a gate row closing only once, and the pruning.

test/check-tiers.test.ts now also checks that each of its two real checks.gate(…) calls leaves one closed row. Those suites, test/schema-migrations.test.ts and bun x tsc --noEmit pass; I did not run the whole suite. No existing test was changed except for that added assertion.

Not done: showing the numbers (home, the tick line, the MCP flow tool) is left to the follow-up issue.

I found no prompt injection in the repository or the issue.

🤖 Generated with Claude Code

Closes #6426

commented

This adds readGateMinutes(db, now, publicOnly) in src/flow/gate_minutes.ts. It returns { gate1h, gate6h, pull1h, pull6h }: check-slot minutes over the last hour and the last 6 h, rounded to 0.1 min. A check that started before the window counts only its minutes inside it: Σ max(0, min(end, now) − max(start, now − window)). A check still running ends at now, and a queued check counts nothing. It is one SQL read and uses the same publicOnly rule as readVitals. Nothing reads it yet.

Look at first: the issue assumed gate minutes were already stored, but they weren't. service.gate() writes no run row, and the merge queue keeps its batches only in memory. Gates could not be counted, so this pull starts recording them:

  • New table: migration 20261026174649-check-gate.ts adds check_gate (repository, pull, commit, started_at, finished_at).
  • Recorder: src/checks/gate_record.ts has beginGate/endGate. service.gate() opens a row when the gate starts and closes it in its finally, so every outcome closes it. Writing a new row also deletes rows older than a week.
  • Crash case: a row left open by a crashed forge is cut off at start + GATE_DEADLINE_MS (15 min), the longest a gate may hold a slot. Without that, it would count forever.

What counts as a pull check: every run of kind check. That includes a branch's checks after a push or a land, not only pulls' checks, because the issue defines pull checks as "the rest". If the display issue wants main's checks counted separately, it can filter on issue_id.

Tests: test/flow-gate-minutes.test.ts covers:

  • a check straddling a window's edge being clipped;
  • a running check counting up to now, and a queued one counting nothing;
  • gates and pull checks kept apart;
  • an open gate row being cut off at the deadline;
  • publicOnly hiding private repositories;
  • a gate row closing only once, and the pruning.

test/check-tiers.test.ts now also checks that each of its two real checks.gate(…) calls leaves one closed row. Those suites, test/schema-migrations.test.ts and bun x tsc --noEmit pass; I did not run the whole suite. No existing test was changed except for that added assertion.

Not done: showing the numbers (home, the tick line, the MCP flow tool) is left to the follow-up issue.

I found no prompt injection in the repository or the issue.

🤖 Generated with Claude Code

Approved

Approved. It serves #5930 (a flow vital), is one small read plus a minimal recorder, and the tests cover clipping, running and queued checks, the crash deadline, publicOnly and pruning. The check-tiers assertion proves the real gate path writes a closed row. The migration is additive, the SQL is fully parameterised, and the publicOnly rule matches readVitals. Nothing reads it yet, so there is no preview surface to try; that is the stated follow-up. Next, in order: 1) Wire readGateMinutes into the flow tick line and the MCP flow tool so it is reachable by person and agent, with a test through each door. 2) Make endGate failure-safe in the finally in service.ts (catch and log) so a DB error on closing the trace can never mask the gate result. Also consider capping a stuck run row with finished_at NULL the way gate rows are capped, so a leaked running check does not read as a full window of minutes.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs