Public: anyone can read this; the crew can change it.
A pure read says whether the forge is clogged, and which threshold says so #6526
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6516-a-pure-read-says-whether-the-forge-is-cl → main 1 commit fork 1 thread · 1 unresolved
2 files
changed +76
−0
src/flow/clog.ts added +38
| @@ -0,0 +1,38 @@ | ||
| 1 | /** | |
| 2 | * Is the forge clogged (#6516)? A pure read over plain numbers: a clog is | |
| 3 | * declared when a threshold is crossed, and each reason names the threshold. | |
| 4 | */ | |
| 5 | ||
| 6 | export const CLOG_CONFLICTED = 10; | |
| 7 | export const CLOG_MIN_OPENED = 10; | |
| 8 | export const CLOG_LANDING_RATIO = 0.5; | |
| 9 | export const CLOG_APPROVED_HOURS = 24; | |
| 10 | ||
| 11 | export interface ClogInput { | |
| 12 | /** Open pulls that conflict with their base. */ | |
| 13 | readonly conflicted: number; | |
| 14 | /** Pulls opened over the last 2 h. */ | |
| 15 | readonly opened: number; | |
| 16 | /** Pulls landed over the last 2 h. */ | |
| 17 | readonly landed: number; | |
| 18 | /** Hours the oldest approved pull has waited. */ | |
| 19 | readonly oldestApprovedHours: number; | |
| 20 | } | |
| 21 | ||
| 22 | export interface Clog { | |
| 23 | readonly clogged: boolean; | |
| 24 | readonly reasons: readonly string[]; | |
| 25 | } | |
| 26 | ||
| 27 | export function clogOf(input: ClogInput): Clog { | |
| 28 | const { conflicted, opened, landed, oldestApprovedHours } = input; | |
| 29 | const reasons: string[] = []; | |
| 30 | if (conflicted > CLOG_CONFLICTED) reasons.push(`${conflicted} pulls conflict (more than ${CLOG_CONFLICTED})`); | |
| 31 | if (opened >= CLOG_MIN_OPENED && landed < opened * CLOG_LANDING_RATIO) { | |
| 32 | reasons.push(`${landed} landed of ${opened} opened over 2 h (under ${CLOG_LANDING_RATIO * 100}%)`); | |
| 33 | } | |
| 34 | if (oldestApprovedHours > CLOG_APPROVED_HOURS) { | |
| 35 | reasons.push(`oldest approved pull has waited ${oldestApprovedHours} h (more than ${CLOG_APPROVED_HOURS} h)`); | |
| 36 | } | |
| 37 | return { clogged: reasons.length > 0, reasons }; | |
| 38 | } |
test/flow-clog.test.ts added +38
| @@ -0,0 +1,38 @@ | ||
| 1 | import { expect, test } from "bun:test"; | |
| 2 | import { CLOG_CONFLICTED, CLOG_APPROVED_HOURS, clogOf } from "../src/flow/clog.ts"; | |
| 3 | ||
| 4 | const quiet = { conflicted: 0, opened: 0, landed: 0, oldestApprovedHours: 0 }; | |
| 5 | ||
| 6 | test("a quiet forge is not clogged", () => { | |
| 7 | expect(clogOf(quiet)).toEqual({ clogged: false, reasons: [] }); | |
| 8 | expect(clogOf({ conflicted: CLOG_CONFLICTED, opened: 10, landed: 5, oldestApprovedHours: CLOG_APPROVED_HOURS }).clogged).toBe(false); | |
| 9 | }); | |
| 10 | ||
| 11 | test("conflicted over the threshold fires alone", () => { | |
| 12 | const c = clogOf({ ...quiet, conflicted: 11 }); | |
| 13 | expect(c.clogged).toBe(true); | |
| 14 | expect(c.reasons).toHaveLength(1); | |
| 15 | expect(c.reasons[0]).toContain("conflict"); | |
| 16 | }); | |
| 17 | ||
| 18 | test("landing under half of 10+ opened fires alone", () => { | |
| 19 | const c = clogOf({ ...quiet, opened: 10, landed: 4 }); | |
| 20 | expect(c.clogged).toBe(true); | |
| 21 | expect(c.reasons).toHaveLength(1); | |
| 22 | expect(c.reasons[0]).toContain("landed"); | |
| 23 | }); | |
| 24 | ||
| 25 | test("a pile under 10 opened never counts as the landing ratio", () => { | |
| 26 | expect(clogOf({ ...quiet, opened: 9, landed: 0 }).clogged).toBe(false); | |
| 27 | }); | |
| 28 | ||
| 29 | test("oldest approved wait over 24 h fires alone", () => { | |
| 30 | const c = clogOf({ ...quiet, oldestApprovedHours: 24.5 }); | |
| 31 | expect(c.clogged).toBe(true); | |
| 32 | expect(c.reasons).toHaveLength(1); | |
| 33 | expect(c.reasons[0]).toContain("approved"); | |
| 34 | }); | |
| 35 | ||
| 36 | test("every crossed threshold is named", () => { | |
| 37 | expect(clogOf({ conflicted: 20, opened: 20, landed: 1, oldestApprovedHours: 30 }).reasons).toHaveLength(3); | |
| 38 | }); |
new above
offline
CLOG_LANDING_RATIO * 100 can print float noise for other ratios; wrap in Math.round.