@loadline/core
v0.1.1
Published
Shared core for loadline gates: cases, checks, summaries, and baseline comparison.
Readme
@loadline/core
The shared spine of every loadline gate: cases, checks, summaries, baseline comparison and report rendering.
Install this directly only if you are building a gate of your own. To use one,
install the gate — for example
@loadline/tool-selection.
The model
A case is one thing that should hold, with an id, a category and a
mandatory why. A gate runs it and returns checks, each of which passed or
did not and carries a detail a human can act on.
import { toResult, type CaseBase, type Check } from '@loadline/core';
const result = toResult(testCase, checks, { outcome: 'find_company' });why is required by the type. A case nobody can justify is a case nobody fixes
when it fails — it gets deleted instead, and the behaviour it protected goes
with it.
Summarising a run
import { summarise } from '@loadline/core';
const { passed, total, byCategory, flaky, intents } = summarise(attempts);Three decisions worth knowing about:
- A case passes only if every repeat passed. A defence holding two times in three has rotted; scoring it as a pass lets it decay to nothing without ever tripping a gate.
- Flakiness is tracked separately, not averaged away. A case that flip-flops usually means two things overlap enough for the system to choose between them at random — which is a finding, not noise.
- Category totals come before the overall total. "93%" is the least actionable number in any report; it says nothing about which behaviour moved.
Cases sharing an intent are checked for agreement: variations of one
question that produce different outcomes is a finding about the system even
where each individual outcome is defensible.
The baseline
import { compareToBaseline, decide, firstRun, toRecord } from '@loadline/core';
const comparison = compareToBaseline(baselineRecord, attempts);
const decision = decide(comparison, attempts);
if (!decision.ok) process.exitCode = 1;compareToBaseline sorts every case into regressions, fixed, stillFailing,
added and removed. decide turns that into a pass or fail.
Only a regression blocks by default. A case that was already failing does
not, so a gate can be adopted on a suite that is not yet green; a newly added
case does not, so adding a hard case never blocks the commit that adds it.
failOnStillFailing and failOnFlaky tighten it once a suite is clean.
The reasoning behind that, and the measurement it came from, is in the root README.
Grounding
Generalised from the check that started this project: did the system use a value nobody gave it?
import { findUngrounded, findForbidden, findAny } from '@loadline/core';
findUngrounded(question, argumentSets, { paths: ['company_number'] });
findForbidden(['745938421'], argumentSets, ['company_number']);
findAny(argumentSets, { paths: ['company_number'] });findAny exists because the other two can be defeated. A model given a
supplier's VAT number took the nine digits, dropped one, padded with a zero and
searched for the result — every character came from the question, so grounding
waved it through. When a question contains no identifier at all, the rule that
holds is any identifier is wrong.
Reports
renderConsole for the person who just ran it and wants to know what to fix;
renderMarkdown for a pull request, where the reader has not run anything and
needs the delta rather than the state. Both lead with failures and put the
total last.
