First public release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 14:56:08 -04:00
co-authored by Claude Opus 5.5
commit d2a07d5988
16 changed files with 2272 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
+8
View File
@@ -0,0 +1,8 @@
node_modules/
dist/
*.log
.DS_Store
.env*
!.env.example
.npmrc
dist-cjs/
+6
View File
@@ -0,0 +1,6 @@
stages: [test]
test:
stage: test
image: node:22-alpine
script: [npm ci, npm run build, npm test]
+15
View File
@@ -0,0 +1,15 @@
# Changelog
## 0.3.0 - 2026-09-28
First public release under MIT.
- `slaReport()` computes availability over a half-open window, charges
downtime per failing sample capped at one probe interval, and excludes
scheduled maintenance.
- Zero samples gives `uptime_pct: null`, never 100. No target gives
`met: null`, never a pass or a failure.
- `toReport()` and `formatSla()` produce an `@extant2000/evidence-record`
report. Zero samples with a target is not assessed, not a missed SLA.
- `describeAvailability()` and `describeTarget()` put the null cases into
plain sentences.
+22
View File
@@ -0,0 +1,22 @@
# Contributing
Issues and merge requests are welcome at
https://gitlab.com/extant2000/proof-of-up.
## Ground rules
- **A test that cannot fail proves nothing.** If you fix a bug, add a test
that fails without your fix, and check that it does fail before you submit.
- Measure, do not assume. Two modules with the same line count can be
different programs.
- Keep dependencies minimal. Every new dependency needs a reason.
- Explain why in comments, not what.
## Before you open a merge request
Run both of these and make sure they pass:
```
npm run build
npm test
```
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Extant 2000 LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+97
View File
@@ -0,0 +1,97 @@
# ProofOfUp
Computes availability for uptime and SLA statements from probe samples, and
refuses to report a number the samples do not support. It is used in Hitt
Hosting products.
## What it does and why
An uptime figure is only as good as the samples behind it, and the common
failures all flatter the service. ProofOfUp computes availability over
samples you have already loaded and handles the edge cases in the direction
of saying less.
**Zero samples is not 100%.** With no samples, `uptime_pct` is `null`, and
the report says availability cannot be computed. A statement that answers
"100%" when nothing was measured is worse than one that abstains. It also
does not say the SLA was missed: when a target applies but nothing was
measured, the report is not assessed and states that the target was neither
met nor missed. (The raw `met` field stays `false` in that case.)
"No SLA target" is not "SLA met". With no target, `met` is `null` and the
report says no SLA target applies to this plan, so a free plan never gets a
statement that reads as a failure.
The rest of the arithmetic:
- the window is half-open, `[from, to)`, so adjacent monthly statements never
count a boundary sample twice;
- samples outside the window or with bad timestamps are dropped, not counted;
- each failing sample is charged the gap to the next sample, capped at one
probe interval (300 seconds by default), so a long reporting gap after one
failure cannot inflate downtime; the last sample is charged one interval;
- samples inside scheduled maintenance windows are removed from the math as if
the probe never ran; cancelled windows are ignored, overlapping windows are
merged, and windows are clipped to the period;
- uptime is rounded to one decimal, and the result does not depend on the
order of the input.
The functions are pure, with no I/O and no clock, so the same samples always
give the same numbers. That is what makes an availability figure evidence and
not a reading taken at a moment. Reports use the `@extant2000/evidence-record`
format. The availability citation carries its sample count, so 100% over zero
samples cannot be mistaken for 100% over thousands, and excluded maintenance
is reported as not applicable with its own measurement so planned downtime
stays visible.
## Usage
```ts
import { slaReport, formatSla, describeAvailability, describeTarget, type Check } from '@extant2000/proof-of-up'
// One probe result every five minutes, loaded from wherever you store them.
const checks: Check[] = [
{ ts: '2026-08-01T00:00:00Z', ok: true },
{ ts: '2026-08-01T00:05:00Z', ok: false },
// ...
]
const report = slaReport(checks, {
from: '2026-08-01T00:00:00Z',
to: '2026-09-01T00:00:00Z',
target: 99.9, // omit or null for no SLA
intervalSeconds: 300,
excludeWindows: [{ starts_at: '2026-08-10T02:00:00Z', ends_at: '2026-08-10T03:00:00Z' }],
})
describeAvailability(report) // e.g. 'Availability 99.8% over 8916 samples, 100 minutes down.'
describeTarget(report) // e.g. 'Target 99.9% was NOT met.'
console.log(formatSla(report, { subject: 'app.example.com' }))
```
For a month of five-minute samples with 20 failures and one hour of
maintenance, `formatSla()` printed this (header line omitted):
```
NOT MET · 8916 items examined · 2 findings
Availability 99.8% over 8916 samples, 100 minutes down.
Target 99.9% was NOT met.
✗ CRITICAL Availability 99.8% over 8916 samples, 100 minutes down. [NOT MET]
Target 99.9% was NOT met.
evidence: Availability = 99.8% (8916 samples, 2026-08-01 → 2026-09-01)
◦ info 60 minutes of scheduled maintenance excluded (12 samples) [N/A]
```
With no samples, the same call reports NOT ASSESSED and "No samples in this
period; availability cannot be computed."
## Install
```
npm install @extant2000/proof-of-up
```
## License
MIT. See [LICENSE](LICENSE).
+6
View File
@@ -0,0 +1,6 @@
# Security
Please report vulnerabilities privately by email to security@extant2000.com.
Do not open a public issue for a security problem.
We aim to acknowledge every report within 5 business days.
+1504
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@extant2000/proof-of-up",
"version": "0.3.0",
"private": false,
"description": "Backs uptime claims with measured data.",
"license": "MIT",
"type": "module",
"main": "./dist-cjs/index.js",
"files": [
"dist",
"dist-cjs",
"src",
"README.md",
"LICENSE"
],
"repository": {
"type": "git",
"url": "https://gitlab.com/extant2000/proof-of-up.git"
},
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist-cjs/index.js"
}
},
"scripts": {
"build": "tsc && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist-cjs/package.json', JSON.stringify({type:'commonjs'}))\"",
"test": "vitest run",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"typescript": "^5.6.0",
"vitest": "^2.1.0"
},
"module": "./dist/index.js",
"dependencies": {
"@extant2000/evidence-record": "^0.1.2"
},
"author": "Extant 2000 LLC",
"homepage": "https://gitlab.com/extant2000/proof-of-up",
"bugs": {
"url": "https://gitlab.com/extant2000/proof-of-up/-/issues"
}
}
+3
View File
@@ -0,0 +1,3 @@
export * from './sla.js'
export { toReport, formatSla, describeAvailability, describeTarget } from './report.js'
export type { SlaReportOptions } from './report.js'
+108
View File
@@ -0,0 +1,108 @@
import {
type CapabilityReport,
type Finding,
type FormatOptions,
evidence,
formatReport as renderReport,
} from '@extant2000/evidence-record'
import type { SlaReport } from './sla.js'
export interface SlaReportOptions {
/** What the statement is about, e.g. "app.example.com". */
subject?: string
}
/**
* Say the null cases out loud.
*
* `uptime null, met=false` was correct and unreadable. Nobody should have to
* know that zero samples is not 100% in order to read the line, and nobody
* should have to know that a null target is not a missed target. The rigour
* was already in the arithmetic; this is the phrasing catching up.
*/
export function describeAvailability(r: SlaReport): string {
if (r.uptime_pct === null) {
return 'No samples in this period; availability cannot be computed.'
}
return `Availability ${r.uptime_pct}% over ${r.total_checks} samples, ${r.downtime_minutes} minutes down.`
}
export function describeTarget(r: SlaReport): string {
if (r.target_pct === null) return 'No SLA target applies to this plan.'
if (r.uptime_pct === null) {
return `A ${r.target_pct}% target applies, but nothing was measured — the target was neither met nor missed.`
}
return r.met
? `Target ${r.target_pct}% was met.`
: `Target ${r.target_pct}% was NOT met.`
}
const window = (r: SlaReport) =>
r.from && r.to ? `${r.from.slice(0, 10)} → ${r.to.slice(0, 10)}` : 'unbounded period'
/**
* Build the portfolio-standard report for one availability statement.
*
* Zero samples with a target set produces `not-assessed`, not `fail`. The
* underlying `met: false` is preserved because the ported arithmetic is
* unchanged, but reporting a MISSED SLA when nothing was measured is an
* overclaim in the opposite direction from reporting 100% — and this library
* exists to avoid exactly that kind of unearned certainty.
*/
export function toReport(r: SlaReport, opts: SlaReportOptions = {}): CapabilityReport {
const subject = opts.subject ?? 'service'
const findings: Finding[] = []
const measured = evidence.measurement('Availability', r.uptime_pct, r.total_checks, {
unit: '%',
window: window(r),
})
findings.push({
id: 'availability',
summary: describeAvailability(r),
determination: r.uptime_pct === null
? 'not-assessed'
: r.target_pct === null
? 'not-applicable'
: r.met ? 'pass' : 'fail',
severity: r.uptime_pct === null ? 'high' : r.met === false ? 'critical' : 'info',
detail: describeTarget(r),
evidence: r.uptime_pct === null ? [] : [measured],
})
if (r.excluded_checks > 0) {
findings.push({
id: 'maintenance',
summary: `${r.excluded_minutes} minutes of scheduled maintenance excluded (${r.excluded_checks} samples)`,
determination: 'not-applicable',
severity: 'info',
detail: 'Planned downtime inside a declared window is removed from the availability math, as if the probe never ran. A cancelled window does not excuse anything.',
evidence: [evidence.measurement('Excluded maintenance', r.excluded_minutes, r.excluded_checks, {
unit: ' min',
window: window(r),
})],
})
}
return {
capability: 'ProofOfUp',
scope: `${subject}, ${window(r)}`,
// The sample count IS the examined count. Zero samples therefore forces
// not-assessed at the report level too, without a special case.
examined: r.total_checks,
findings,
// Both sentences go in the notes, not only on the finding: with zero
// samples the renderer leads with "nothing was assessed" and stops before
// the findings, and the reader still has to be told why.
notes: [describeAvailability(r), describeTarget(r)],
}
}
/** Render an availability statement for a terminal, CI log or customer PDF. */
export function formatSla(
r: SlaReport,
opts: SlaReportOptions & FormatOptions = {},
): string {
return renderReport(toReport(r, opts), opts)
}
+164
View File
@@ -0,0 +1,164 @@
/**
* Availability math for uptime / SLA statements.
*
* Only the arithmetic lives here. Queries, auth, CSV and printable rendering
* stay in the product that calls it.
*/
export interface Check {
/** Sample timestamp. */
ts: Date | string | number
/** Whether the probe succeeded. */
ok: boolean
}
export interface MaintenanceWindow {
starts_at?: Date | string | number
ends_at?: Date | string | number
/** Aliases accepted for convenience. */
start?: Date | string | number
end?: Date | string | number
/** A `cancelled` window is ignored — it did not happen. */
status?: string
}
export interface SlaOptions {
from: Date | string | number
to: Date | string | number
/** Marketed availability target, e.g. 99.9. `null`/absent = no SLA. */
target?: number | null
/** Probe cadence in seconds. Bounds each failing sample's charged downtime. */
intervalSeconds?: number
excludeWindows?: MaintenanceWindow[]
}
export interface SlaReport {
from: string | null
to: string | null
total_checks: number
up_checks: number
down_checks: number
/** `null` when there were no samples — NOT 100. */
uptime_pct: number | null
downtime_minutes: number
excluded_checks: number
excluded_minutes: number
target_pct: number | null
/** `null` when no target is set — "no SLA" is not "SLA met". */
met: boolean | null
}
function ms(v: unknown): number {
if (v instanceof Date) return v.getTime()
return new Date(v as string).getTime()
}
function round1(n: number): number {
return Math.round(n * 10) / 10
}
/**
* Compute an availability report over already-loaded probe samples.
*
* Pure: no I/O, no clock. Given the same samples it returns the same numbers,
* which is what makes an availability figure auditable evidence rather than a
* reading taken at a moment.
*
* The window is half-open `[from, to)` — includes `from`, excludes `to` — so
* adjacent monthly statements never double-count a boundary sample.
*/
export function slaReport(checks: Check[], opts: SlaOptions): SlaReport {
const fromMs = ms(opts.from)
const toMs = ms(opts.to)
const target = opts.target != null && Number.isFinite(Number(opts.target))
? Number(opts.target)
: null
const intervalSec = Number.isFinite(Number(opts.intervalSeconds)) && Number(opts.intervalSeconds) > 0
? Number(opts.intervalSeconds)
: 300
const intervalMs = intervalSec * 1000
const fromIso = Number.isFinite(fromMs) ? new Date(fromMs).toISOString() : null
const toIso = Number.isFinite(toMs) ? new Date(toMs).toISOString() : null
// ── Scheduled-maintenance exclusion ──────────────────────────────────────
// Normalize windows into half-open [s, e) intervals clipped to the report
// period, dropping cancelled ones. A sample inside such an interval is
// removed from the availability math entirely — as if the probe never ran —
// so planned downtime never counts against uptime_pct.
const raw: Array<[number, number]> = []
if (Array.isArray(opts.excludeWindows) && Number.isFinite(fromMs) && Number.isFinite(toMs)) {
for (const w of opts.excludeWindows) {
if (!w || w.status === 'cancelled') continue
const s = ms(w.starts_at ?? w.start)
const e = ms(w.ends_at ?? w.end)
if (!Number.isFinite(s) || !Number.isFinite(e) || e <= s) continue
const cs = Math.max(s, fromMs)
const ce = Math.min(e, toMs)
if (ce > cs) raw.push([cs, ce])
}
}
// Merge overlapping/adjacent intervals so excluded_minutes never
// double-counts two windows that overlap.
raw.sort((a, b) => a[0] - b[0])
const merged: Array<[number, number]> = []
for (const iv of raw) {
const last = merged[merged.length - 1]
if (last && iv[0] <= last[1]) last[1] = Math.max(last[1], iv[1])
else merged.push([iv[0], iv[1]])
}
const isExcluded = (t: number) => merged.some(([s, e]) => t >= s && t < e)
const excludedMs = merged.reduce((acc, [s, e]) => acc + (e - s), 0)
// ── Collect in-window samples ────────────────────────────────────────────
const kept: Array<{ t: number, ok: boolean }> = []
let excludedChecks = 0
if (Array.isArray(checks) && Number.isFinite(fromMs) && Number.isFinite(toMs)) {
for (const c of checks) {
if (!c || c.ts == null) continue
const t = ms(c.ts)
if (!Number.isFinite(t) || t < fromMs || t >= toMs) continue
if (isExcluded(t)) { excludedChecks++; continue }
kept.push({ t, ok: !!c.ok })
}
}
kept.sort((a, b) => a.t - b.t)
// ── Availability ─────────────────────────────────────────────────────────
let up = 0
let downtimeMs = 0
for (let i = 0; i < kept.length; i++) {
const cur = kept[i]!
if (cur.ok) { up++; continue }
// Charge the gap to the next sample, capped at one probe interval, so a
// long reporting gap after a single failure cannot inflate downtime. The
// last sample has no successor and is charged exactly one interval.
const next = kept[i + 1]
const gap = next ? next.t - cur.t : intervalMs
downtimeMs += Math.min(Math.max(gap, 0), intervalMs)
}
const total = kept.length
// null, not 100: zero samples is no evidence, and an availability report that
// answers "100%" when nothing was measured is worse than one that abstains.
const pct = total === 0 ? null : round1((up / total) * 100)
return {
from: fromIso,
to: toIso,
total_checks: total,
up_checks: up,
down_checks: total - up,
uptime_pct: pct,
downtime_minutes: round1(downtimeMs / 60000),
excluded_checks: excludedChecks,
excluded_minutes: round1(excludedMs / 60000),
target_pct: target,
// null, not false: "no SLA target" and "SLA missed" are different claims,
// and a free plan must not produce a statement that reads as a failure.
met: target == null ? null : (pct != null && pct >= target),
}
}
+243
View File
@@ -0,0 +1,243 @@
import { describe, expect, it } from 'vitest'
import { slaReport, toReport, formatSla, type Check } from '../src/index.js'
import { overall, validateReport } from '@extant2000/evidence-record'
const FROM = '2026-07-01T00:00:00.000Z'
const TO = '2026-08-01T00:00:00.000Z'
/** n samples 5 minutes apart from `start`, `okAt` decides pass/fail. */
function series(start: string, n: number, okAt: (i: number) => boolean): Check[] {
const t0 = new Date(start).getTime()
return Array.from({ length: n }, (_, i) => ({
ts: new Date(t0 + i * 300_000).toISOString(),
ok: okAt(i),
}))
}
describe('slaReport — refusing flattering answers', () => {
it('reports uptime_pct as NULL with no samples, not 100', () => {
// An availability report that answers "100%" when nothing was measured is
// worse than one that abstains.
const r = slaReport([], { from: FROM, to: TO, target: 99.9 })
expect(r.uptime_pct).toBeNull()
expect(r.total_checks).toBe(0)
})
it('reports met as NULL with no target, not false', () => {
// "no SLA target" and "SLA missed" are different claims. A free plan must
// not produce a statement that reads as a failure.
expect(slaReport(series(FROM, 10, () => true), { from: FROM, to: TO }).met).toBeNull()
})
it('cannot report met=true from zero evidence', () => {
const r = slaReport([], { from: FROM, to: TO, target: 99.9 })
expect(r.met).toBe(false)
})
})
describe('slaReport — window semantics', () => {
it('is half-open [from, to) so adjacent months never double-count', () => {
const boundary: Check[] = [
{ ts: FROM, ok: true }, // included
{ ts: TO, ok: true }, // excluded — belongs to the NEXT statement
]
expect(slaReport(boundary, { from: FROM, to: TO }).total_checks).toBe(1)
})
it('drops samples outside the window entirely', () => {
const r = slaReport([
{ ts: '2026-06-30T23:59:59.000Z', ok: false },
{ ts: '2026-07-15T00:00:00.000Z', ok: true },
], { from: FROM, to: TO })
expect(r.total_checks).toBe(1)
expect(r.uptime_pct).toBe(100)
})
it('ignores malformed samples rather than counting them', () => {
const r = slaReport(
[{ ts: 'not-a-date', ok: false }, { ts: FROM, ok: true }] as Check[],
{ from: FROM, to: TO },
)
expect(r.total_checks).toBe(1)
})
})
describe('slaReport — availability and downtime', () => {
it('computes uptime to one decimal', () => {
// 999 up, 1 down out of 1000 → 99.9
const r = slaReport(series(FROM, 1000, i => i !== 500), { from: FROM, to: TO, target: 99.9 })
expect(r.uptime_pct).toBe(99.9)
expect(r.up_checks).toBe(999)
expect(r.down_checks).toBe(1)
expect(r.met).toBe(true)
})
it('misses the target just below it', () => {
// 998/1000 = 99.8 < 99.9
const r = slaReport(series(FROM, 1000, i => i !== 500 && i !== 600), {
from: FROM, to: TO, target: 99.9,
})
expect(r.uptime_pct).toBe(99.8)
expect(r.met).toBe(false)
})
it('charges a failing sample the gap to its successor, capped at one interval', () => {
// Two failures 5 min apart at a 300s cadence → 5 min each = 10 min.
const r = slaReport(series(FROM, 3, i => i === 2), {
from: FROM, to: TO, intervalSeconds: 300,
})
expect(r.downtime_minutes).toBe(10)
})
it('caps a long reporting gap at one interval', () => {
// A single failure followed by a sample a DAY later must not charge a day.
const r = slaReport([
{ ts: '2026-07-01T00:00:00.000Z', ok: false },
{ ts: '2026-07-02T00:00:00.000Z', ok: true },
], { from: FROM, to: TO, intervalSeconds: 300 })
expect(r.downtime_minutes).toBe(5)
})
it('charges the final sample exactly one interval when it has no successor', () => {
const r = slaReport([{ ts: FROM, ok: false }], {
from: FROM, to: TO, intervalSeconds: 600,
})
expect(r.downtime_minutes).toBe(10)
})
})
describe('slaReport — scheduled maintenance', () => {
const win = [{ starts_at: '2026-07-10T00:00:00.000Z', ends_at: '2026-07-10T01:00:00.000Z' }]
it('removes samples inside a window from the math entirely', () => {
const r = slaReport([
{ ts: '2026-07-10T00:30:00.000Z', ok: false }, // planned downtime
{ ts: '2026-07-11T00:00:00.000Z', ok: true },
], { from: FROM, to: TO, excludeWindows: win, target: 99.9 })
expect(r.excluded_checks).toBe(1)
expect(r.total_checks).toBe(1)
expect(r.uptime_pct).toBe(100) // planned downtime never counts against it
expect(r.excluded_minutes).toBe(60)
})
it('ignores a cancelled window — it did not happen', () => {
const r = slaReport([{ ts: '2026-07-10T00:30:00.000Z', ok: false }], {
from: FROM, to: TO,
excludeWindows: [{ ...win[0]!, status: 'cancelled' }],
})
expect(r.excluded_checks).toBe(0)
expect(r.uptime_pct).toBe(0)
})
it('does NOT double-count overlapping windows', () => {
const r = slaReport([], {
from: FROM, to: TO,
excludeWindows: [
{ starts_at: '2026-07-10T00:00:00.000Z', ends_at: '2026-07-10T02:00:00.000Z' },
{ starts_at: '2026-07-10T01:00:00.000Z', ends_at: '2026-07-10T03:00:00.000Z' },
],
})
expect(r.excluded_minutes).toBe(180) // union, not 120 + 120
})
it('clips a window to the report period', () => {
const r = slaReport([], {
from: FROM, to: TO,
excludeWindows: [{ starts_at: '2026-06-01T00:00:00.000Z', ends_at: '2026-07-01T01:00:00.000Z' }],
})
expect(r.excluded_minutes).toBe(60) // only the hour inside July
})
it('drops a zero-length or inverted window', () => {
const r = slaReport([], {
from: FROM, to: TO,
excludeWindows: [
{ starts_at: '2026-07-10T01:00:00.000Z', ends_at: '2026-07-10T01:00:00.000Z' },
{ starts_at: '2026-07-10T05:00:00.000Z', ends_at: '2026-07-10T04:00:00.000Z' },
],
})
expect(r.excluded_minutes).toBe(0)
})
it('accepts start/end aliases', () => {
const r = slaReport([], {
from: FROM, to: TO,
excludeWindows: [{ start: '2026-07-10T00:00:00.000Z', end: '2026-07-10T00:30:00.000Z' }],
})
expect(r.excluded_minutes).toBe(30)
})
})
describe('slaReport — reproducibility', () => {
it('same input, identical output', () => {
const checks = series(FROM, 200, i => i % 17 !== 0)
const a = slaReport(checks, { from: FROM, to: TO, target: 99 })
const b = slaReport(checks, { from: FROM, to: TO, target: 99 })
expect(a).toEqual(b)
})
it('is order-independent — unsorted input gives the same answer', () => {
const checks = series(FROM, 50, i => i !== 10)
const shuffled = [...checks].reverse()
expect(slaReport(shuffled, { from: FROM, to: TO, intervalSeconds: 300 }))
.toEqual(slaReport(checks, { from: FROM, to: TO, intervalSeconds: 300 }))
})
})
describe('report — the null cases said out loud', () => {
const period = { from: '2026-07-01T00:00:00Z', to: '2026-08-01T00:00:00Z' }
it('reads zero samples as uncomputable, not as a missed SLA', () => {
// `uptime null, met=false` is correct arithmetic and an overclaim to
// print: nothing was measured, so nothing was missed.
const r = slaReport([], { ...period, target: 99.9 })
expect(r.uptime_pct).toBeNull()
expect(r.met).toBe(false) // ported behaviour, unchanged
expect(overall(toReport(r))).toBe('not-assessed') // what a reader is told
const text = formatSla(r)
expect(text).toContain('No samples in this period; availability cannot be computed.')
expect(text).toContain('neither met nor missed')
expect(text).not.toContain('100%')
})
it('says plainly when no SLA target applies', () => {
const r = slaReport([{ ts: '2026-07-02T00:00:00Z', ok: true }], period)
expect(r.met).toBeNull()
expect(formatSla(r)).toContain('No SLA target applies to this plan.')
})
it('carries the sample count into the citation', () => {
const checks = Array.from({ length: 100 }, (_, i) => ({
ts: new Date(Date.parse(period.from) + i * 300_000).toISOString(),
ok: i !== 3,
}))
const r = slaReport(checks, { ...period, target: 99.9 })
const text = formatSla(r, { subject: 'app.example.com', evidence: 'inline' })
expect(text).toContain('100 samples')
expect(text).toContain('app.example.com')
expect(overall(toReport(r))).toBe('fail')
expect(text).toContain('was NOT met')
})
it('reports excluded maintenance as not-applicable, with its own measurement', () => {
const checks = Array.from({ length: 20 }, (_, i) => ({
ts: new Date(Date.parse(period.from) + i * 300_000).toISOString(),
ok: true,
}))
const r = slaReport(checks, {
...period,
target: 99.9,
excludeWindows: [{ start: period.from, end: new Date(Date.parse(period.from) + 3600_000).toISOString() }],
})
expect(r.excluded_checks).toBeGreaterThan(0)
const f = toReport(r).findings.find(x => x.id === 'maintenance')!
expect(f.determination).toBe('not-applicable')
expect(formatSla(r)).toContain('scheduled maintenance excluded')
})
it('every conclusion carries a citation', () => {
const r = slaReport([{ ts: '2026-07-02T00:00:00Z', ok: true }], { ...period, target: 99.9 })
expect(validateReport(toReport(r))).toEqual([])
})
})
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node",
"outDir": "dist-cjs",
"declaration": false
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "ES2022", "module": "ES2022", "moduleResolution": "bundler",
"declaration": true, "outDir": "dist", "rootDir": "src",
"strict": true, "skipLibCheck": true
},
"include": ["src/**/*.ts"]
}