snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
All checks were successful
Release / Create Version PR or Publish Stable Release (push) Has been skipped
Release / Publish Prerelease to NPM (push) Has been skipped
Monitor Registries / Monitor registry health (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 22:44:43 +05:00
commit 1416db152c
5727 changed files with 630238 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
import { describe, expect, it, vi } from "vitest"
import {
cleanRegistryHealthHistory,
loadRegistryHealthSnapshot,
loadRegistryMonitorState,
publishRegistryHealth,
type BlobOperations,
} from "./blob"
import type {
RegistryHealthSnapshot,
RegistryMonitorRun,
RegistryMonitorState,
} from "./schema"
function streamJson(value: unknown) {
const bytes = new TextEncoder().encode(JSON.stringify(value))
return new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(bytes)
controller.close()
},
})
}
function createOperations(overrides: Partial<BlobOperations> = {}) {
return {
get: vi.fn(async () => null),
put: vi.fn(async (pathname) => ({
url: `https://blob.example.com/${pathname}`,
})),
list: vi.fn(async () => ({ blobs: [], hasMore: false })),
del: vi.fn(async () => {}),
...overrides,
} satisfies BlobOperations
}
const STATE: RegistryMonitorState = {
schemaVersion: 1,
scoreVersion: 1,
updatedAt: "2026-08-24T12:00:00.000Z",
registries: {},
}
const SNAPSHOT: RegistryHealthSnapshot = {
schemaVersion: 1,
scoreVersion: 1,
generatedAt: "2026-08-24T12:00:00.000Z",
globalMeans: {
availability7d: 0.85,
availability30d: 0.85,
indexSchema: 0.9,
itemValidity: 0.9,
dryRun: 0.9,
},
registries: {},
}
const RUN: RegistryMonitorRun = {
schemaVersion: 1,
startedAt: "2026-08-24T12:00:00.000Z",
completedAt: "2026-08-24T12:01:00.000Z",
mode: "hourly",
totals: {
registries: 0,
reachable: 0,
unavailable: 0,
challenges: 0,
itemChecks: 0,
dryRuns: 0,
},
results: {},
diagnostics: [],
}
describe("loadRegistryMonitorState", () => {
it("returns null on the first run", async () => {
const state = await loadRegistryMonitorState({
token: "test-token",
operations: createOperations(),
})
expect(state).toBeNull()
})
it("validates stored state before using it", async () => {
const operations = createOperations({
get: vi.fn(async () => ({ stream: streamJson(STATE) })),
})
await expect(
loadRegistryMonitorState({ token: "test-token", operations })
).resolves.toEqual(STATE)
expect(operations.get).toHaveBeenCalledWith(
"registry-health/v1/state.json",
{
access: "private",
token: "test-token",
useCache: false,
}
)
})
})
describe("loadRegistryHealthSnapshot", () => {
it("reads and validates the private latest snapshot", async () => {
const abortController = new AbortController()
const operations = createOperations({
get: vi.fn(async () => ({ stream: streamJson(SNAPSHOT) })),
})
await expect(
loadRegistryHealthSnapshot({
token: "test-token",
storeId: "test-store-id",
abortSignal: abortController.signal,
operations,
})
).resolves.toEqual(SNAPSHOT)
expect(operations.get).toHaveBeenCalledWith(
"registry-health/v1/latest.json",
{
access: "private",
token: "test-token",
storeId: "test-store-id",
useCache: false,
abortSignal: abortController.signal,
}
)
})
it("rejects an invalid latest snapshot", async () => {
const operations = createOperations({
get: vi.fn(async () => ({
stream: streamJson({ ...SNAPSHOT, schemaVersion: 2 }),
})),
})
await expect(
loadRegistryHealthSnapshot({ token: "test-token", operations })
).rejects.toThrow()
})
})
describe("publishRegistryHealth", () => {
it("publishes immutable history before overwriting state and latest", async () => {
const operations = createOperations()
const result = await publishRegistryHealth({
state: STATE,
snapshot: SNAPSHOT,
run: RUN,
token: "test-token",
operations,
})
const calls = vi.mocked(operations.put).mock.calls
expect(calls.map(([pathname]) => pathname)).toEqual([
"registry-health/v1/runs/2026-08-24T12-00-00-000Z.json",
"registry-health/v1/daily/2026-08-24.json",
"registry-health/v1/state.json",
"registry-health/v1/latest.json",
])
expect(calls[0][2].allowOverwrite).toBe(false)
expect(calls[2][2].allowOverwrite).toBe(true)
expect(calls[3][2].allowOverwrite).toBe(true)
expect(calls.every(([, , options]) => options.access === "private")).toBe(
true
)
expect(result.latestPath).toBe("registry-health/v1/latest.json")
})
})
describe("cleanRegistryHealthHistory", () => {
it("paginates listings and deletes expired history", async () => {
const list = vi
.fn()
.mockResolvedValueOnce({
blobs: [
{
pathname: "registry-health/v1/runs/old.json",
uploadedAt: new Date("2026-08-01T00:00:00.000Z"),
},
],
cursor: "next",
hasMore: true,
})
.mockResolvedValueOnce({ blobs: [], hasMore: false })
.mockResolvedValueOnce({ blobs: [], hasMore: false })
const operations = createOperations({ list })
const result = await cleanRegistryHealthHistory({
token: "test-token",
now: new Date("2026-08-24T12:00:00.000Z"),
operations,
})
expect(list).toHaveBeenCalledTimes(3)
expect(operations.del).toHaveBeenCalledWith(
["registry-health/v1/runs/old.json"],
{ token: "test-token" }
)
expect(result.deleted).toBe(1)
})
})

View File

@@ -0,0 +1,294 @@
import {
del as deleteBlob,
get as getBlob,
list as listBlobs,
put as putBlob,
} from "@vercel/blob"
import {
registryHealthSnapshotSchema,
registryMonitorStateSchema,
type RegistryHealthSnapshot,
type RegistryMonitorRun,
type RegistryMonitorState,
} from "./schema"
const BLOB_PREFIX = "registry-health/v1"
const STATE_PATH = `${BLOB_PREFIX}/state.json`
const LATEST_PATH = `${BLOB_PREFIX}/latest.json`
type BlobListItem = {
pathname: string
uploadedAt: Date
}
type BlobOperations = {
get: (
pathname: string,
options: {
access: "private"
token?: string
storeId?: string
useCache: false
abortSignal?: AbortSignal
}
) => Promise<{ stream: ReadableStream<Uint8Array> | null } | null>
put: (
pathname: string,
body: string,
options: {
access: "private"
token?: string
addRandomSuffix: false
allowOverwrite?: boolean
cacheControlMaxAge: number
contentType: "application/json"
}
) => Promise<{ url: string }>
list: (options: {
prefix: string
cursor?: string
limit: number
token?: string
}) => Promise<{
blobs: BlobListItem[]
cursor?: string
hasMore: boolean
}>
del: (pathnames: string[], options: { token?: string }) => Promise<void>
}
const defaultOperations: BlobOperations = {
get: getBlob,
put: putBlob,
list: listBlobs,
del: deleteBlob,
}
async function readJsonStream(stream: ReadableStream<Uint8Array>) {
const reader = stream.getReader()
const chunks: Uint8Array[] = []
let size = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
chunks.push(value)
size += value.byteLength
}
const body = new Uint8Array(size)
let offset = 0
for (const chunk of chunks) {
body.set(chunk, offset)
offset += chunk.byteLength
}
return JSON.parse(new TextDecoder().decode(body)) as unknown
}
export async function loadRegistryMonitorState({
token,
operations = defaultOperations,
}: {
token?: string
operations?: BlobOperations
}) {
const result = await operations.get(STATE_PATH, {
access: "private",
token,
useCache: false,
})
if (!result?.stream) return null
return registryMonitorStateSchema.parse(await readJsonStream(result.stream))
}
export async function loadRegistryHealthSnapshot({
token,
storeId,
abortSignal,
operations = defaultOperations,
}: {
token?: string
storeId?: string
abortSignal?: AbortSignal
operations?: BlobOperations
}) {
const result = await operations.get(LATEST_PATH, {
access: "private",
token,
storeId,
useCache: false,
abortSignal,
})
if (!result?.stream) return null
return registryHealthSnapshotSchema.parse(await readJsonStream(result.stream))
}
function getRunPath(run: RegistryMonitorRun) {
const timestamp = run.startedAt.replaceAll(":", "-").replaceAll(".", "-")
return `${BLOB_PREFIX}/runs/${timestamp}.json`
}
function getDailyPath(snapshot: RegistryHealthSnapshot) {
return `${BLOB_PREFIX}/daily/${snapshot.generatedAt.slice(0, 10)}.json`
}
function getDailyDocument(
state: RegistryMonitorState,
snapshot: RegistryHealthSnapshot
) {
const date = snapshot.generatedAt.slice(0, 10)
return {
schemaVersion: snapshot.schemaVersion,
scoreVersion: snapshot.scoreVersion,
date,
generatedAt: snapshot.generatedAt,
globalMeans: snapshot.globalMeans,
registries: Object.fromEntries(
Object.entries(state.registries).map(([name, entry]) => [
name,
{
aggregate: entry.daily.find((bucket) => bucket.date === date) ?? null,
health: snapshot.registries[name],
},
])
),
}
}
async function putJson({
pathname,
value,
token,
allowOverwrite = false,
operations,
}: {
pathname: string
value: unknown
token?: string
allowOverwrite?: boolean
operations: BlobOperations
}) {
return operations.put(pathname, JSON.stringify(value), {
access: "private",
token,
addRandomSuffix: false,
allowOverwrite,
cacheControlMaxAge: 60,
contentType: "application/json",
})
}
export async function publishRegistryHealth({
state,
snapshot,
run,
token,
operations = defaultOperations,
}: {
state: RegistryMonitorState
snapshot: RegistryHealthSnapshot
run: RegistryMonitorRun
token?: string
operations?: BlobOperations
}) {
await putJson({
pathname: getRunPath(run),
value: run,
token,
operations,
})
await putJson({
pathname: getDailyPath(snapshot),
value: getDailyDocument(state, snapshot),
token,
allowOverwrite: true,
operations,
})
await putJson({
pathname: STATE_PATH,
value: state,
token,
allowOverwrite: true,
operations,
})
await putJson({
pathname: LATEST_PATH,
value: snapshot,
token,
allowOverwrite: true,
operations,
})
return { latestPath: LATEST_PATH }
}
async function listAll({
prefix,
token,
operations,
}: {
prefix: string
token?: string
operations: BlobOperations
}) {
const blobs: BlobListItem[] = []
let cursor: string | undefined
do {
const page = await operations.list({
prefix,
cursor,
limit: 1000,
token,
})
blobs.push(...page.blobs)
cursor = page.hasMore ? page.cursor : undefined
} while (cursor)
return blobs
}
export async function cleanRegistryHealthHistory({
token,
now = new Date(),
operations = defaultOperations,
}: {
token?: string
now?: Date
operations?: BlobOperations
}) {
const policies = [
{ prefix: `${BLOB_PREFIX}/runs/`, retentionDays: 14 },
{ prefix: `${BLOB_PREFIX}/daily/`, retentionDays: 90 },
]
const stale: string[] = []
for (const policy of policies) {
const blobs = await listAll({
prefix: policy.prefix,
token,
operations,
})
const cutoff = now.getTime() - policy.retentionDays * 24 * 60 * 60 * 1000
stale.push(
...blobs
.filter((blob) => blob.uploadedAt.getTime() < cutoff)
.map((blob) => blob.pathname)
)
}
for (let index = 0; index < stale.length; index += 100) {
await operations.del(stale.slice(index, index + 100), { token })
}
return { deleted: stale.length }
}
export { BLOB_PREFIX, LATEST_PATH, STATE_PATH }
export type { BlobOperations }

View File

@@ -0,0 +1,117 @@
import { promises as fs } from "node:fs"
import os from "node:os"
import path from "node:path"
import { describe, expect, it } from "vitest"
import { getDryRunEnvironment, runLocalCliDryRun } from "./dry-run"
import { REGISTRY_DRY_RUN_FAILURE_MESSAGE_MAX_LENGTH } from "./schema"
describe("getDryRunEnvironment", () => {
it("allowlists process variables and excludes credentials", () => {
const environment = getDryRunEnvironment("/tmp/registry-health-test")
expect(environment).toMatchObject({
CI: "1",
NO_COLOR: "1",
XDG_CACHE_HOME: "/tmp/registry-health-test",
XDG_CONFIG_HOME: "/tmp/registry-health-test",
})
expect(environment).not.toHaveProperty("BLOB_READ_WRITE_TOKEN")
expect(environment).not.toHaveProperty("GITHUB_TOKEN")
expect(environment).not.toHaveProperty("GH_TOKEN")
})
})
describe("runLocalCliDryRun", () => {
it("creates a TypeScript project the CLI can load", async () => {
const directory = await fs.mkdtemp(
path.join(os.tmpdir(), "registry-health-cli-test-")
)
const cliPath = path.join(directory, "cli.mjs")
await fs.writeFile(
cliPath,
`
import { readFileSync } from "node:fs"
const tsconfig = JSON.parse(readFileSync("tsconfig.json", "utf8"))
if (tsconfig.compilerOptions.paths["@/*"][0] !== "./*") {
process.exit(1)
}
`
)
try {
await expect(
runLocalCliDryRun({
namespace: "@acme",
item: "button",
registryUrl: "https://acme.example.com/r/{name}.json",
cliPath,
})
).resolves.toMatchObject({ success: true })
} finally {
await fs.rm(directory, { recursive: true, force: true })
}
})
it("captures bounded sanitized CLI errors", async () => {
const directory = await fs.mkdtemp(
path.join(os.tmpdir(), "registry-health-cli-test-")
)
const cliPath = path.join(directory, "cli.mjs")
await fs.writeFile(
cliPath,
'process.stderr.write("\\u001b[31m" + "x".repeat(1500) + " useful failure\\u001b[0m"); process.exit(1)'
)
try {
const result = await runLocalCliDryRun({
namespace: "@acme",
item: "button",
registryUrl: "https://acme.example.com/r/{name}.json",
cliPath,
})
expect(result).toMatchObject({
success: false,
failureCode: "exit_1",
})
expect(result.failureMessage).toContain("useful failure")
expect(result.failureMessage).not.toContain("\u001b")
expect(result.failureMessage?.length).toBeLessThanOrEqual(
REGISTRY_DRY_RUN_FAILURE_MESSAGE_MAX_LENGTH
)
} finally {
await fs.rm(directory, { recursive: true, force: true })
}
})
it("force kills a CLI process that ignores SIGTERM", async () => {
const directory = await fs.mkdtemp(
path.join(os.tmpdir(), "registry-health-cli-test-")
)
const cliPath = path.join(directory, "cli.mjs")
await fs.writeFile(
cliPath,
'process.on("SIGTERM", () => {}); setInterval(() => {}, 1000)'
)
try {
const result = await runLocalCliDryRun({
namespace: "@acme",
item: "button",
registryUrl: "https://acme.example.com/r/{name}.json",
cliPath,
timeoutMs: 25,
killGraceMs: 25,
})
expect(result).toMatchObject({
success: false,
failureCode: "timeout",
})
} finally {
await fs.rm(directory, { recursive: true, force: true })
}
})
})

View File

@@ -0,0 +1,186 @@
import { spawn, type ChildProcess } from "node:child_process"
import { promises as fs } from "node:fs"
import os from "node:os"
import path from "node:path"
import { stripVTControlCharacters } from "node:util"
import { DEFAULT_STYLE, type RegistryDryRunResult } from "./monitor"
import { REGISTRY_DRY_RUN_FAILURE_MESSAGE_MAX_LENGTH } from "./schema"
const MAX_CAPTURED_OUTPUT_LENGTH = 4000
function getFailureMessage(output: string) {
const message = stripVTControlCharacters(output).replace(/\s+/g, " ").trim()
return message
? message.slice(-REGISTRY_DRY_RUN_FAILURE_MESSAGE_MAX_LENGTH)
: undefined
}
async function runLocalCliDryRun({
namespace,
item,
registryUrl,
cliPath,
timeoutMs = 60_000,
killGraceMs = 5_000,
}: {
namespace: string
item: string
registryUrl: string
cliPath: string
timeoutMs?: number
killGraceMs?: number
}): Promise<RegistryDryRunResult> {
const startedAt = Date.now()
const temporaryDirectory = await fs.mkdtemp(
path.join(os.tmpdir(), "shadcn-registry-health-")
)
try {
await fs.mkdir(path.join(temporaryDirectory, "app"), { recursive: true })
await Promise.all([
fs.writeFile(
path.join(temporaryDirectory, "components.json"),
JSON.stringify(
{
$schema: "https://ui.shadcn.com/schema.json",
style: DEFAULT_STYLE,
rsc: true,
tsx: true,
tailwind: {
config: "",
css: "app/globals.css",
baseColor: "neutral",
cssVariables: true,
prefix: "",
},
iconLibrary: "lucide",
aliases: {
components: "@/components",
utils: "@/lib/utils",
ui: "@/components/ui",
lib: "@/lib",
hooks: "@/hooks",
},
registries: {
[namespace]: registryUrl,
},
},
null,
2
)
),
fs.writeFile(path.join(temporaryDirectory, "app/globals.css"), ""),
fs.writeFile(
path.join(temporaryDirectory, "package.json"),
JSON.stringify({ private: true })
),
fs.writeFile(
path.join(temporaryDirectory, "tsconfig.json"),
JSON.stringify(
{
compilerOptions: {
baseUrl: ".",
paths: {
"@/*": ["./*"],
},
jsx: "preserve",
},
},
null,
2
)
),
])
return await new Promise<RegistryDryRunResult>((resolve) => {
const child: ChildProcess = spawn(
process.execPath,
[
cliPath,
"add",
`${namespace}/${item}`,
"--dry-run",
"--yes",
"--cwd",
temporaryDirectory,
],
{
cwd: temporaryDirectory,
env: getDryRunEnvironment(temporaryDirectory),
stdio: ["ignore", "pipe", "pipe"],
}
)
let completed = false
let timedOut = false
let output = ""
let forceKillTimeout: NodeJS.Timeout | undefined
const captureOutput = (chunk: Buffer | string) => {
const value = typeof chunk === "string" ? chunk : chunk.toString("utf8")
output = `${output}${value}`.slice(-MAX_CAPTURED_OUTPUT_LENGTH)
}
const finish = (result: RegistryDryRunResult) => {
if (completed) return
completed = true
clearTimeout(timeout)
if (forceKillTimeout) clearTimeout(forceKillTimeout)
resolve(result)
}
const timeout = setTimeout(() => {
timedOut = true
child.kill("SIGTERM")
forceKillTimeout = setTimeout(() => {
if (!completed) child.kill("SIGKILL")
}, killGraceMs)
}, timeoutMs)
child.stdout?.on("data", captureOutput)
child.stderr?.on("data", captureOutput)
child.on("error", (error) => {
captureOutput(error.message)
const failureMessage = getFailureMessage(output)
finish({
success: false,
failureCode: timedOut ? "timeout" : "spawn_error",
...(failureMessage ? { failureMessage } : {}),
durationMs: Date.now() - startedAt,
})
})
child.on("close", (code, signal) => {
const failureCode = timedOut
? "timeout"
: code === 0
? undefined
: signal
? "terminated"
: `exit_${code}`
const failureMessage =
code === 0 ? undefined : getFailureMessage(output)
finish({
success: !timedOut && code === 0,
failureCode,
...(failureMessage ? { failureMessage } : {}),
durationMs: Date.now() - startedAt,
})
})
})
} finally {
await fs.rm(temporaryDirectory, { recursive: true, force: true })
}
}
function getDryRunEnvironment(temporaryDirectory: string) {
return {
PATH: process.env.PATH,
TMPDIR: os.tmpdir(),
XDG_CACHE_HOME: temporaryDirectory,
XDG_CONFIG_HOME: temporaryDirectory,
npm_config_userconfig: path.join(temporaryDirectory, ".npmrc"),
NODE_ENV: "production",
CI: "1",
NO_COLOR: "1",
} satisfies NodeJS.ProcessEnv
}
export { getDryRunEnvironment, runLocalCliDryRun }

View File

@@ -0,0 +1,746 @@
import { describe, expect, it, vi } from "vitest"
import type { RegistryDirectoryEntry } from "../registry-directory"
import { runRegistryMonitor } from "./monitor"
import {
REGISTRY_DRY_RUN_VERSION,
type RegistryMonitorEntryState,
type RegistryMonitorState,
} from "./schema"
const NOW = new Date("2026-08-24T12:00:00.000Z")
const DIRECTORY: RegistryDirectoryEntry[] = [
{
name: "@acme",
homepage: "https://acme.example.com",
url: "https://acme.example.com/r/{name}.json",
description: "Acme components",
logo: "https://acme.example.com/logo.svg",
},
]
function createEntryState(overrides: Partial<RegistryMonitorEntryState> = {}) {
return {
firstObservedAt: "2026-08-01T00:00:00.000Z",
lastSuccessfulCheck: "2026-08-24T10:00:00.000Z",
status: "healthy",
consecutiveSuccessfulIndexes: 0,
consecutiveIndexFailures: 0,
availabilityRecoveryRequired: false,
itemCursor: 0,
itemNames: [],
recentIndex: [],
recentDryRuns: [],
daily: [],
latestHygiene: {
contentTypeJson: null,
noDuplicateNames: null,
nameMatches: null,
},
...overrides,
} satisfies RegistryMonitorEntryState
}
function createState(entry = createEntryState()) {
return {
schemaVersion: 1,
scoreVersion: 1,
dryRunVersion: REGISTRY_DRY_RUN_VERSION,
updatedAt: "2026-08-24T10:00:00.000Z",
registries: { "@acme": entry },
} satisfies RegistryMonitorState
}
function createSuccessfulFetch(itemCount = 1) {
return {
ok: true as const,
json: {
name: "acme",
homepage: "https://acme.example.com",
items: Array.from({ length: itemCount }, (_, index) => ({
name: `item-${index}`,
type: "registry:ui",
files: [],
})),
},
status: 200,
durationMs: 120,
responseSize: 500,
contentType: "application/json; charset=utf-8",
redirectCount: 0,
finalUrl: "https://acme.example.com/r/registry.json",
}
}
describe("runRegistryMonitor", () => {
it("records completion separately from the observation time", async () => {
vi.useFakeTimers()
vi.setSystemTime(new Date("2026-08-24T12:05:00.000Z"))
try {
const result = await runRegistryMonitor({
directory: DIRECTORY,
mode: "hourly",
now: NOW,
fetchJson: vi.fn(async () => createSuccessfulFetch()),
runDryRun: vi.fn(),
})
expect(result.run.startedAt).toBe(NOW.toISOString())
expect(result.run.completedAt).toBe("2026-08-24T12:05:00.000Z")
} finally {
vi.useRealTimers()
}
})
it("records a successful hourly index observation", async () => {
const result = await runRegistryMonitor({
directory: DIRECTORY,
mode: "hourly",
now: NOW,
fetchJson: vi.fn(async () => createSuccessfulFetch(2)),
runDryRun: vi.fn(),
})
expect(result.run.totals).toMatchObject({
registries: 1,
reachable: 1,
unavailable: 0,
challenges: 0,
})
expect(result.state.registries["@acme"].itemNames).toEqual([
"item-0",
"item-1",
])
expect(result.snapshot.registries["@acme"]).toMatchObject({
status: "observing",
monitoringLimited: false,
checkedAt: NOW.toISOString(),
})
})
it("starts an outage from the last successful index check", async () => {
const lastSuccessfulCheck = "2026-08-23T11:00:00.000Z"
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState: createState(
createEntryState({
firstObservedAt: "2026-08-01T00:00:00.000Z",
lastSuccessfulCheck,
consecutiveSuccessfulIndexes: 24,
})
),
mode: "hourly",
now: NOW,
fetchJson: vi.fn(async () => ({
ok: false as const,
failureCode: "timeout",
reachable: false,
botChallenge: false,
durationMs: 100,
redirectCount: 0,
finalUrl: "https://acme.example.com/r/registry.json",
})),
runDryRun: vi.fn(),
})
expect(result.state.registries["@acme"]).toMatchObject({
consecutiveSuccessfulIndexes: 0,
consecutiveIndexFailures: 1,
availabilityOutageSince: lastSuccessfulCheck,
availabilityRecoveryRequired: true,
})
expect(result.snapshot.registries["@acme"].status).toBe("unavailable")
})
it("clears availability recovery after two valid index checks", async () => {
const outageSince = "2026-08-16T00:00:00.000Z"
const recentIndex = Array.from({ length: 24 }, (_, index) => ({
checkedAt: new Date(
NOW.getTime() - (24 - index) * 60 * 60 * 1000
).toISOString(),
outcome: "unreachable" as const,
durationMs: 100,
redirectCount: 0,
}))
const previousState = createState(
createEntryState({
firstObservedAt: "2026-08-01T00:00:00.000Z",
lastSuccessfulCheck: "2026-08-15T23:00:00.000Z",
status: "unavailable",
consecutiveSuccessfulIndexes: 0,
consecutiveIndexFailures: 24,
availabilityOutageSince: outageSince,
availabilityRecoveryRequired: true,
recentIndex,
})
)
const first = await runRegistryMonitor({
directory: DIRECTORY,
previousState,
mode: "hourly",
now: NOW,
fetchJson: vi.fn(async () => createSuccessfulFetch()),
runDryRun: vi.fn(),
})
const recovering = first.state.registries["@acme"]
expect(recovering).toMatchObject({
consecutiveSuccessfulIndexes: 1,
consecutiveIndexFailures: 0,
availabilityOutageSince: outageSince,
availabilityRecoveryRequired: true,
})
expect(first.snapshot.registries["@acme"]).toMatchObject({
status: "unavailable",
hidden: true,
})
const second = await runRegistryMonitor({
directory: DIRECTORY,
previousState: first.state,
mode: "hourly",
now: new Date("2026-08-24T13:00:00.000Z"),
fetchJson: vi.fn(async () => createSuccessfulFetch()),
runDryRun: vi.fn(),
})
const recovered = second.state.registries["@acme"]
expect(recovered).toMatchObject({
consecutiveSuccessfulIndexes: 2,
consecutiveIndexFailures: 0,
availabilityRecoveryRequired: false,
})
expect(recovered).not.toHaveProperty("availabilityOutageSince")
expect(second.snapshot.registries["@acme"].status).toBe("healthy")
})
it("records a challenge without lowering availability", async () => {
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState: createState(),
mode: "hourly",
now: NOW,
fetchJson: vi.fn(async () => ({
ok: false as const,
failureCode: "bot_challenge",
reachable: false,
botChallenge: true,
status: 403,
durationMs: 80,
responseSize: 200,
contentType: "text/html",
redirectCount: 0,
finalUrl: "https://acme.example.com/r/registry.json",
})),
runDryRun: vi.fn(),
})
const bucket = result.state.registries["@acme"].daily[0]
const state = result.state.registries["@acme"]
expect(bucket.challengeObservations).toBe(1)
expect(bucket.availabilityObservations).toBe(0)
expect(state).toMatchObject({
consecutiveSuccessfulIndexes: 0,
consecutiveIndexFailures: 0,
availabilityRecoveryRequired: false,
})
expect(state).not.toHaveProperty("availabilityOutageSince")
expect(result.snapshot.registries["@acme"].monitoringLimited).toBe(true)
})
it("allows an existing outage to become unavailable during a challenge", async () => {
const outageSince = "2026-08-23T11:00:00.000Z"
const previousState = createState(
createEntryState({
firstObservedAt: "2026-08-01T00:00:00.000Z",
lastSuccessfulCheck: outageSince,
consecutiveIndexFailures: 1,
availabilityOutageSince: outageSince,
recentIndex: [
{
checkedAt: outageSince,
outcome: "unreachable",
durationMs: 100,
redirectCount: 0,
},
],
})
)
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState,
mode: "hourly",
now: NOW,
fetchJson: vi.fn(async () => ({
ok: false as const,
failureCode: "bot_challenge",
reachable: false,
botChallenge: true,
status: 403,
durationMs: 80,
responseSize: 200,
contentType: "text/html",
redirectCount: 0,
finalUrl: "https://acme.example.com/r/registry.json",
})),
runDryRun: vi.fn(),
})
expect(result.state.registries["@acme"]).toMatchObject({
consecutiveIndexFailures: 1,
availabilityOutageSince: outageSince,
availabilityRecoveryRequired: false,
})
expect(result.snapshot.registries["@acme"]).toMatchObject({
status: "unavailable",
monitoringLimited: true,
})
})
it("refreshes hygiene after a reachable invalid index", async () => {
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState: createState(
createEntryState({
latestHygiene: {
contentTypeJson: true,
noDuplicateNames: true,
nameMatches: true,
},
})
),
mode: "hourly",
now: NOW,
fetchJson: vi.fn(async () => ({
...createSuccessfulFetch(),
json: { invalid: true },
contentType: "text/plain",
})),
runDryRun: vi.fn(),
})
expect(result.state.registries["@acme"].latestHygiene).toEqual({
contentTypeJson: false,
noDuplicateNames: null,
nameMatches: null,
})
})
it("rotates enough daily items to cover a catalog in thirty days", async () => {
const itemNames = Array.from({ length: 61 }, (_, index) => `item-${index}`)
const fetchJson = vi.fn(async (url: string) => {
const item = url.match(/item-\d+/)?.[0] ?? "item-0"
return {
...createSuccessfulFetch(),
json: { name: item, type: "registry:ui", files: [] },
finalUrl: url,
}
})
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState: createState(createEntryState({ itemNames })),
mode: "daily",
now: NOW,
fetchJson,
runDryRun: vi.fn(),
})
expect(fetchJson).toHaveBeenCalledTimes(3)
expect(result.state.registries["@acme"].itemCursor).toBe(3)
expect(result.run.totals.itemChecks).toBe(3)
expect(result.run.results["@acme"].items).toHaveLength(3)
expect(result.state.lastDailyRunAt).toBe(NOW.toISOString())
})
it("excludes item challenges from validation rates", async () => {
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState: createState(
createEntryState({
itemNames: ["button"],
daily: [
{
date: "2026-08-24",
availabilitySuccesses: 0,
availabilityObservations: 0,
challengeObservations: 0,
schemaSuccesses: 0,
schemaObservations: 0,
itemSuccesses: 8,
itemObservations: 9,
dryRunSuccesses: 0,
dryRunObservations: 0,
},
],
})
),
mode: "daily",
now: NOW,
fetchJson: vi.fn(async () => ({
ok: false as const,
failureCode: "bot_challenge",
reachable: false,
botChallenge: true,
status: 403,
durationMs: 80,
responseSize: 200,
contentType: "text/html",
redirectCount: 0,
finalUrl: "https://acme.example.com/r/button.json",
})),
runDryRun: vi.fn(),
})
const bucket = result.state.registries["@acme"].daily[0]
expect(bucket.itemObservations).toBe(9)
expect(bucket.itemSuccesses).toBe(8)
expect(result.run.totals).toMatchObject({
challenges: 1,
itemChecks: 1,
})
})
it("runs one rotating weekly dry-run item", async () => {
const runDryRun = vi.fn(async () => ({
success: true,
durationMs: 250,
}))
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState: createState(
createEntryState({ itemNames: ["button", "dialog"] })
),
mode: "weekly",
now: NOW,
fetchJson: vi.fn(),
runDryRun,
})
expect(runDryRun).toHaveBeenCalledWith({
namespace: "@acme",
item: "button",
registryUrl: DIRECTORY[0].url,
})
expect(result.run.totals.dryRuns).toBe(1)
expect(result.state.registries["@acme"].recentDryRuns).toHaveLength(1)
expect(result.state.lastWeeklyRunAt).toBe(NOW.toISOString())
})
it("discards invalid pre-tsconfig dry runs and reruns the weekly check", async () => {
const now = new Date("2026-09-02T12:00:00.000Z")
const recentIndex = Array.from({ length: 24 }, (_, index) => ({
checkedAt: new Date(
now.getTime() - (24 - index) * 60 * 60 * 1000
).toISOString(),
outcome: "reachable" as const,
status: 200,
durationMs: 100,
redirectCount: 0,
schemaValid: true,
}))
const previousState = {
...createState(
createEntryState({
itemNames: ["button"],
recentIndex,
recentDryRuns: [
{
checkedAt: "2026-08-25T12:04:27.443Z",
item: "button",
success: false,
failureCode: "exit_1",
durationMs: 1500,
},
{
checkedAt: "2026-08-31T12:38:36.084Z",
item: "dialog",
success: false,
failureCode: "exit_1",
durationMs: 1700,
},
],
daily: [
{
date: "2026-08-25",
availabilitySuccesses: 0,
availabilityObservations: 0,
challengeObservations: 0,
schemaSuccesses: 0,
schemaObservations: 0,
itemSuccesses: 0,
itemObservations: 0,
dryRunSuccesses: 0,
dryRunObservations: 1,
},
{
date: "2026-08-31",
availabilitySuccesses: 0,
availabilityObservations: 0,
challengeObservations: 0,
schemaSuccesses: 0,
schemaObservations: 0,
itemSuccesses: 0,
itemObservations: 0,
dryRunSuccesses: 0,
dryRunObservations: 1,
},
],
latestHygiene: {
contentTypeJson: true,
noDuplicateNames: true,
nameMatches: true,
},
})
),
dryRunVersion: 0,
lastDailyRunAt: "2026-09-02T11:00:00.000Z",
lastWeeklyRunAt: "2026-08-31T12:38:36.084Z",
}
const runDryRun = vi.fn(async () => ({
success: true,
durationMs: 250,
}))
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState,
mode: "auto",
now,
fetchJson: vi.fn(async () => createSuccessfulFetch()),
runDryRun,
})
expect(runDryRun).toHaveBeenCalledOnce()
expect(result.state.registries["@acme"].recentDryRuns).toEqual([
{
checkedAt: now.toISOString(),
item: "item-0",
success: true,
durationMs: 250,
},
])
expect(result.state.registries["@acme"].daily.slice(0, 2)).toMatchObject([
{ date: "2026-08-25", dryRunSuccesses: 0, dryRunObservations: 0 },
{ date: "2026-08-31", dryRunSuccesses: 0, dryRunObservations: 0 },
])
expect(result.state.lastWeeklyRunAt).toBe(now.toISOString())
expect(result.run.diagnostics).toEqual([
"Discarded 2 invalid CLI dry-run observations from the pre-tsconfig scaffold.",
])
expect(result.snapshot.registries["@acme"]).toMatchObject({
status: "healthy",
breakdown: { installability: 18.5 },
})
})
it("continues weekly rotation after dry-run history is trimmed", async () => {
const runDryRun = vi.fn(async () => ({
success: true,
durationMs: 250,
}))
const recentDryRuns = Array.from({ length: 12 }, (_, index) => ({
checkedAt: `2026-08-${String(index + 1).padStart(2, "0")}T12:00:00.000Z`,
item: index === 11 ? "dialog" : "button",
success: true,
durationMs: 250,
}))
await runRegistryMonitor({
directory: DIRECTORY,
previousState: createState(
createEntryState({
itemNames: ["button", "dialog", "tooltip"],
recentDryRuns,
})
),
mode: "weekly",
now: NOW,
fetchJson: vi.fn(),
runDryRun,
})
expect(runDryRun).toHaveBeenCalledWith({
namespace: "@acme",
item: "tooltip",
registryUrl: DIRECTORY[0].url,
})
})
it("runs delayed auto checks using their last run timestamps", async () => {
const previousState = {
...createState(createEntryState({ itemNames: ["button"] })),
lastDailyRunAt: "2026-08-23T15:00:00.000Z",
lastWeeklyRunAt: "2026-08-18T12:00:00.000Z",
}
const fetchJson = vi.fn(async (url: string) =>
url.endsWith("/registry.json")
? createSuccessfulFetch()
: {
...createSuccessfulFetch(),
json: { name: "item-0", type: "registry:ui", files: [] },
finalUrl: url,
}
)
const runDryRun = vi.fn(async () => ({
success: true,
durationMs: 250,
}))
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState,
mode: "auto",
now: NOW,
fetchJson,
runDryRun,
})
expect(result.run.totals.itemChecks).toBe(1)
expect(result.run.totals.dryRuns).toBe(1)
expect(result.state.lastDailyRunAt).toBe(NOW.toISOString())
expect(result.state.lastWeeklyRunAt).toBe(NOW.toISOString())
})
it("skips auto checks until their elapsed intervals", async () => {
const previousState = {
...createState(createEntryState({ itemNames: ["button"] })),
lastDailyRunAt: "2026-08-23T17:00:00.000Z",
lastWeeklyRunAt: "2026-08-19T12:00:00.000Z",
}
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState,
mode: "auto",
now: NOW,
fetchJson: vi.fn(async () => createSuccessfulFetch()),
runDryRun: vi.fn(),
})
expect(result.run.totals.itemChecks).toBe(0)
expect(result.run.totals.dryRuns).toBe(0)
expect(result.state.lastDailyRunAt).toBe(previousState.lastDailyRunAt)
expect(result.state.lastWeeklyRunAt).toBe(previousState.lastWeeklyRunAt)
})
it("runs auto checks for state without last run timestamps", async () => {
const previousState = createState(
createEntryState({ itemNames: ["button"] })
)
const fetchJson = vi.fn(async (url: string) =>
url.endsWith("/registry.json")
? createSuccessfulFetch()
: {
...createSuccessfulFetch(),
json: { name: "item-0", type: "registry:ui", files: [] },
finalUrl: url,
}
)
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState,
mode: "auto",
now: NOW,
fetchJson,
runDryRun: vi.fn(async () => ({
success: true,
durationMs: 250,
})),
})
expect(result.run.totals.itemChecks).toBe(1)
expect(result.run.totals.dryRuns).toBe(1)
expect(result.state.lastDailyRunAt).toBe(NOW.toISOString())
expect(result.state.lastWeeklyRunAt).toBe(NOW.toISOString())
})
it("runs auto checks when last run timestamps are in the future", async () => {
const previousState = {
...createState(createEntryState({ itemNames: ["button"] })),
lastDailyRunAt: "2026-08-25T12:00:00.000Z",
lastWeeklyRunAt: "2026-08-25T12:00:00.000Z",
}
const result = await runRegistryMonitor({
directory: DIRECTORY,
previousState,
mode: "auto",
now: NOW,
fetchJson: vi.fn(async () => createSuccessfulFetch()),
runDryRun: vi.fn(async () => ({
success: true,
durationMs: 250,
})),
})
expect(result.run.totals.itemChecks).toBe(1)
expect(result.run.totals.dryRuns).toBe(1)
expect(result.state.lastDailyRunAt).toBe(NOW.toISOString())
expect(result.state.lastWeeklyRunAt).toBe(NOW.toISOString())
})
it("limits concurrent CLI dry runs to four", async () => {
const directory = Array.from({ length: 6 }, (_, index) => ({
name: `@registry${index}`,
homepage: `https://registry${index}.example.com`,
url: `https://registry${index}.example.com/r/{name}.json`,
description: `Registry ${index}`,
logo: `https://registry${index}.example.com/logo.svg`,
}))
const previousState: RegistryMonitorState = {
schemaVersion: 1,
scoreVersion: 1,
updatedAt: "2026-08-24T10:00:00.000Z",
registries: Object.fromEntries(
directory.map((entry) => [
entry.name,
createEntryState({ itemNames: ["button"] }),
])
),
}
let active = 0
let maximumActive = 0
const runDryRun = vi.fn(async () => {
active += 1
maximumActive = Math.max(maximumActive, active)
await new Promise((resolve) => setTimeout(resolve, 20))
active -= 1
return { success: true, durationMs: 20 }
})
const result = await runRegistryMonitor({
directory,
previousState,
mode: "weekly",
now: NOW,
fetchJson: vi.fn(),
runDryRun,
})
expect(result.run.totals.dryRuns).toBe(6)
expect(maximumActive).toBe(4)
})
it("resolves {style} urls with the default style", async () => {
const fetchJson = vi.fn(async () => createSuccessfulFetch())
await runRegistryMonitor({
directory: [
{
...DIRECTORY[0],
url: "https://acme.example.com/r/{style}/{name}.json",
},
],
mode: "daily",
now: NOW,
previousState: createState(createEntryState({ itemNames: ["button"] })),
fetchJson,
runDryRun: vi.fn(),
})
expect(fetchJson).toHaveBeenCalledWith(
"https://acme.example.com/r/radix-vega/button.json"
)
})
})

View File

@@ -0,0 +1,615 @@
import { registryItemSchema, registrySchema } from "shadcn/schema"
import {
normalizeRegistryName,
type RegistryDirectoryEntry,
} from "../registry-directory"
import { fetchRegistryJson, type RegistryJsonResult } from "./network"
import {
REGISTRY_DRY_RUN_VERSION,
REGISTRY_HEALTH_SCHEMA_VERSION,
REGISTRY_HEALTH_SCORE_VERSION,
registryHealthSnapshotSchema,
registryMonitorRunSchema,
registryMonitorStateSchema,
type RegistryDryRunObservation,
type RegistryHealthDailyBucket,
type RegistryHealthSnapshot,
type RegistryIndexObservation,
type RegistryItemObservation,
type RegistryMonitorEntryState,
type RegistryMonitorOutput,
type RegistryMonitorRun,
type RegistryMonitorState,
} from "./schema"
import { calculateGlobalMeans, calculateRegistryHealth } from "./score"
import { createRegistryMonitorEntryState } from "./state"
const DAY_MS = 24 * 60 * 60 * 1000
const RECENT_INDEX_RETENTION_MS = 8 * DAY_MS
const DAILY_CHECK_INTERVAL_MS = 20 * 60 * 60 * 1000
const WEEKLY_CHECK_INTERVAL_MS = 6 * DAY_MS
const MAX_DRY_RUN_CONCURRENCY = 4
// Registries cannot declare a default style yet, so resolve {style} urls
// with the canonical first base and style combination.
export const DEFAULT_STYLE = "radix-vega"
export type RegistryMonitorMode = RegistryMonitorRun["mode"]
export type RegistryDryRunResult = {
success: boolean
failureCode?: string
failureMessage?: string
durationMs: number
}
function createDailyBucket(date: string) {
return {
date,
availabilitySuccesses: 0,
availabilityObservations: 0,
challengeObservations: 0,
schemaSuccesses: 0,
schemaObservations: 0,
itemSuccesses: 0,
itemObservations: 0,
dryRunSuccesses: 0,
dryRunObservations: 0,
} satisfies RegistryHealthDailyBucket
}
function discardInvalidDryRunHistory(
previousState: RegistryMonitorState | null | undefined
) {
if (!previousState) {
return { state: previousState, discardedDryRuns: 0 }
}
const state = structuredClone(previousState)
if (state.dryRunVersion === REGISTRY_DRY_RUN_VERSION) {
return { state, discardedDryRuns: 0 }
}
let discardedRecentDryRuns = 0
let discardedDailyDryRuns = 0
for (const entry of Object.values(state.registries)) {
discardedRecentDryRuns += entry.recentDryRuns.length
entry.recentDryRuns = []
for (const bucket of entry.daily) {
discardedDailyDryRuns += bucket.dryRunObservations
bucket.dryRunSuccesses = 0
bucket.dryRunObservations = 0
}
}
const discardedDryRuns = Math.max(
discardedRecentDryRuns,
discardedDailyDryRuns
)
state.dryRunVersion = REGISTRY_DRY_RUN_VERSION
delete state.lastWeeklyRunAt
return { state, discardedDryRuns }
}
function getDailyBucket(state: RegistryMonitorEntryState, now: Date) {
const date = now.toISOString().slice(0, 10)
let bucket = state.daily.find((entry) => entry.date === date)
if (!bucket) {
bucket = createDailyBucket(date)
state.daily.push(bucket)
}
return bucket
}
function trimState(state: RegistryMonitorEntryState, now: Date) {
state.recentIndex = state.recentIndex
.filter(
(observation) =>
now.getTime() - new Date(observation.checkedAt).getTime() <
RECENT_INDEX_RETENTION_MS
)
.slice(-256)
state.recentDryRuns = state.recentDryRuns.slice(-12)
state.daily = state.daily
.filter(
(bucket) =>
now.getTime() - new Date(`${bucket.date}T00:00:00.000Z`).getTime() <
31 * DAY_MS
)
.toSorted((a, b) => a.date.localeCompare(b.date))
}
function isJsonMediaType(contentType: string) {
const mediaType = contentType.split(";", 1)[0].trim().toLowerCase()
return (
mediaType === "application/json" ||
mediaType === "text/json" ||
mediaType.endsWith("+json")
)
}
function getIndexObservation(
result: RegistryJsonResult,
entry: RegistryDirectoryEntry,
now: Date
) {
if (!result.ok) {
return {
observation: {
checkedAt: now.toISOString(),
outcome: result.botChallenge
? ("bot_challenge" as const)
: result.reachable
? ("reachable" as const)
: ("unreachable" as const),
status: result.status,
failureCode: result.failureCode,
durationMs: result.durationMs,
responseSize: result.responseSize,
redirectCount: result.redirectCount,
schemaValid: result.reachable ? false : undefined,
contentTypeJson: result.contentType
? isJsonMediaType(result.contentType)
: undefined,
} satisfies RegistryIndexObservation,
itemNames: null,
}
}
const parsed = registrySchema.safeParse(result.json)
const itemNames = parsed.success
? parsed.data.items.map((item) => item.name)
: null
const duplicateNames = itemNames
? new Set(itemNames).size !== itemNames.length
: undefined
const nameMatches = parsed.success
? normalizeRegistryName(parsed.data.name) ===
normalizeRegistryName(entry.name)
: undefined
return {
observation: {
checkedAt: now.toISOString(),
outcome: "reachable" as const,
status: result.status,
failureCode: parsed.success ? undefined : "invalid_schema",
durationMs: result.durationMs,
responseSize: result.responseSize,
redirectCount: result.redirectCount,
schemaValid: parsed.success,
contentTypeJson: isJsonMediaType(result.contentType),
duplicateNames,
nameMatches,
itemCount: itemNames?.length,
} satisfies RegistryIndexObservation,
itemNames,
}
}
function recordIndexObservation(
state: RegistryMonitorEntryState,
observation: RegistryIndexObservation,
itemNames: string[] | null,
now: Date
) {
state.recentIndex.push(observation)
const bucket = getDailyBucket(state, now)
if (observation.outcome !== "bot_challenge") {
if (observation.outcome === "unreachable") {
state.consecutiveSuccessfulIndexes = 0
state.consecutiveIndexFailures += 1
state.availabilityOutageSince ??=
state.lastSuccessfulCheck ?? observation.checkedAt
const outageDuration =
new Date(observation.checkedAt).getTime() -
new Date(state.availabilityOutageSince).getTime()
const outageRequiresRecovery = outageDuration >= DAY_MS
if (state.consecutiveIndexFailures >= 3 || outageRequiresRecovery) {
state.availabilityRecoveryRequired = true
}
} else {
const outageDuration = state.availabilityOutageSince
? new Date(observation.checkedAt).getTime() -
new Date(state.availabilityOutageSince).getTime()
: 0
const outageRequiresRecovery = outageDuration >= DAY_MS
state.consecutiveIndexFailures = 0
if (observation.schemaValid) {
state.consecutiveSuccessfulIndexes += 1
if (
(state.availabilityRecoveryRequired || outageRequiresRecovery) &&
state.consecutiveSuccessfulIndexes < 2
) {
state.availabilityRecoveryRequired = true
} else {
state.availabilityRecoveryRequired = false
delete state.availabilityOutageSince
}
} else {
state.consecutiveSuccessfulIndexes = 0
if (state.availabilityRecoveryRequired || outageRequiresRecovery) {
state.availabilityRecoveryRequired = true
} else {
delete state.availabilityOutageSince
}
}
}
}
if (observation.outcome === "bot_challenge") {
bucket.challengeObservations += 1
} else {
bucket.availabilityObservations += 1
bucket.availabilitySuccesses += observation.outcome === "reachable" ? 1 : 0
}
if (observation.outcome === "reachable") {
bucket.schemaObservations += 1
bucket.schemaSuccesses += observation.schemaValid ? 1 : 0
}
if (observation.outcome === "reachable") {
state.latestHygiene = {
contentTypeJson: observation.contentTypeJson ?? false,
noDuplicateNames:
observation.duplicateNames === undefined
? null
: !observation.duplicateNames,
nameMatches: observation.nameMatches ?? null,
}
}
if (observation.schemaValid && itemNames) {
state.itemNames = itemNames
state.lastSuccessfulCheck = observation.checkedAt
}
}
function getItemNamesForDailyCheck(state: RegistryMonitorEntryState) {
if (state.itemNames.length === 0) return []
const count = Math.max(1, Math.ceil(state.itemNames.length / 30))
const selected = Array.from({ length: count }, (_, offset) => {
const index = (state.itemCursor + offset) % state.itemNames.length
return state.itemNames[index]
})
state.itemCursor = (state.itemCursor + count) % state.itemNames.length
return selected
}
function recordItemObservation(
state: RegistryMonitorEntryState,
observation: RegistryItemObservation,
now: Date
) {
if (observation.failureCode === "bot_challenge") return
const bucket = getDailyBucket(state, now)
bucket.itemObservations += 1
bucket.itemSuccesses += observation.success ? 1 : 0
}
function getNextDryRunItem(state: RegistryMonitorEntryState) {
if (state.itemNames.length === 0) return null
const previousItem = state.recentDryRuns.at(-1)?.item
const previousIndex = previousItem
? state.itemNames.indexOf(previousItem)
: -1
return state.itemNames[(previousIndex + 1) % state.itemNames.length]
}
function recordDryRunObservation(
state: RegistryMonitorEntryState,
observation: RegistryDryRunObservation,
now: Date
) {
state.recentDryRuns.push(observation)
const bucket = getDailyBucket(state, now)
bucket.dryRunObservations += 1
bucket.dryRunSuccesses += observation.success ? 1 : 0
}
function replaceRegistryItem(url: string, item: string) {
return url.replace("{name}", item).replace("{style}", DEFAULT_STYLE)
}
function hasIntervalElapsed(
lastRunAt: string | undefined,
now: Date,
intervalMs: number
) {
if (!lastRunAt) return true
const elapsed = now.getTime() - new Date(lastRunAt).getTime()
return elapsed < 0 || elapsed >= intervalMs
}
function getChecks(
mode: RegistryMonitorMode,
now: Date,
previousState?: RegistryMonitorState | null
) {
if (mode === "all") {
return { index: true, items: true, dryRun: true }
}
if (mode !== "auto") {
return {
index: mode === "hourly",
items: mode === "daily",
dryRun: mode === "weekly",
}
}
return {
index: true,
items: hasIntervalElapsed(
previousState?.lastDailyRunAt,
now,
DAILY_CHECK_INTERVAL_MS
),
dryRun: hasIntervalElapsed(
previousState?.lastWeeklyRunAt,
now,
WEEKLY_CHECK_INTERVAL_MS
),
}
}
function createConcurrencyLimit(concurrency: number) {
let active = 0
const waiting: Array<() => void> = []
return async function limit<T>(task: () => Promise<T>) {
while (active >= concurrency) {
await new Promise<void>((resolve) => waiting.push(resolve))
}
active += 1
try {
return await task()
} finally {
active -= 1
waiting.shift()?.()
}
}
}
async function mapWithConcurrency<T>(
values: T[],
concurrency: number,
task: (value: T) => Promise<void>
) {
let cursor = 0
async function worker() {
while (cursor < values.length) {
const index = cursor
cursor += 1
await task(values[index])
}
}
await Promise.all(
Array.from({ length: Math.min(concurrency, values.length) }, () => worker())
)
}
function groupByHost(directory: RegistryDirectoryEntry[]) {
const groups = new Map<string, RegistryDirectoryEntry[]>()
for (const entry of directory) {
let host = entry.name
try {
host = new URL(entry.url).host
} catch {
host = entry.name
}
const group = groups.get(host) ?? []
group.push(entry)
groups.set(host, group)
}
return [...groups.values()]
}
export async function runRegistryMonitor({
directory,
previousState,
mode = "auto",
now = new Date(),
fetchJson = fetchRegistryJson,
runDryRun,
concurrency = 12,
}: {
directory: RegistryDirectoryEntry[]
previousState?: RegistryMonitorState | null
mode?: RegistryMonitorMode
now?: Date
fetchJson?: typeof fetchRegistryJson
runDryRun: (options: {
namespace: string
item: string
registryUrl: string
}) => Promise<RegistryDryRunResult>
concurrency?: number
}): Promise<RegistryMonitorOutput> {
const startedAt = now.toISOString()
const { state: repairedPreviousState, discardedDryRuns } =
discardInvalidDryRunHistory(previousState)
const checks = getChecks(mode, now, repairedPreviousState)
const state: RegistryMonitorState = {
schemaVersion: REGISTRY_HEALTH_SCHEMA_VERSION,
scoreVersion: REGISTRY_HEALTH_SCORE_VERSION,
dryRunVersion: REGISTRY_DRY_RUN_VERSION,
updatedAt: now.toISOString(),
...(repairedPreviousState?.lastDailyRunAt
? { lastDailyRunAt: repairedPreviousState.lastDailyRunAt }
: {}),
...(repairedPreviousState?.lastWeeklyRunAt
? { lastWeeklyRunAt: repairedPreviousState.lastWeeklyRunAt }
: {}),
registries: {},
}
const limitDryRun = createConcurrencyLimit(MAX_DRY_RUN_CONCURRENCY)
const runResults: RegistryMonitorRun["results"] = {}
const totals = {
registries: directory.length,
reachable: 0,
unavailable: 0,
challenges: 0,
itemChecks: 0,
dryRuns: 0,
}
for (const entry of directory) {
state.registries[entry.name] =
repairedPreviousState?.registries[entry.name] ??
createRegistryMonitorEntryState(now)
runResults[entry.name] = { items: [] }
}
await mapWithConcurrency(
groupByHost(directory),
concurrency,
async (group) => {
for (const entry of group) {
const entryState = state.registries[entry.name]
const result = runResults[entry.name]
if (checks.index) {
const indexResult = await fetchJson(
replaceRegistryItem(entry.url, "registry")
)
const { observation, itemNames } = getIndexObservation(
indexResult,
entry,
now
)
recordIndexObservation(entryState, observation, itemNames, now)
result.index = observation
if (observation.outcome === "reachable") totals.reachable += 1
if (observation.outcome === "unreachable") totals.unavailable += 1
if (observation.outcome === "bot_challenge") totals.challenges += 1
}
if (checks.items) {
for (const item of getItemNamesForDailyCheck(entryState)) {
const itemResult = await fetchJson(
replaceRegistryItem(entry.url, item)
)
const parsed = itemResult.ok
? registryItemSchema.safeParse(itemResult.json)
: null
const success = !!(parsed?.success && parsed.data.name === item)
const observation: RegistryItemObservation = {
checkedAt: now.toISOString(),
item,
success,
failureCode: success
? undefined
: itemResult.ok
? parsed?.success
? "name_mismatch"
: "invalid_schema"
: itemResult.failureCode,
durationMs: itemResult.durationMs,
}
recordItemObservation(entryState, observation, now)
result.items.push(observation)
totals.itemChecks += 1
if (!itemResult.ok && itemResult.botChallenge) {
totals.challenges += 1
}
}
}
const dryRunItem = checks.dryRun ? getNextDryRunItem(entryState) : null
if (dryRunItem) {
const dryRunResult = await limitDryRun(() =>
runDryRun({
namespace: entry.name,
item: dryRunItem,
registryUrl: entry.url,
})
)
const observation: RegistryDryRunObservation = {
checkedAt: now.toISOString(),
item: dryRunItem,
...dryRunResult,
}
recordDryRunObservation(entryState, observation, now)
result.dryRun = observation
totals.dryRuns += 1
}
trimState(entryState, now)
}
}
)
if (checks.items) {
state.lastDailyRunAt = now.toISOString()
}
if (checks.dryRun) {
state.lastWeeklyRunAt = now.toISOString()
}
const registryUrls = Object.fromEntries(
directory.map((entry) => [entry.name, entry.url])
)
const globalMeans = calculateGlobalMeans(state, registryUrls, now)
const snapshot: RegistryHealthSnapshot = {
schemaVersion: REGISTRY_HEALTH_SCHEMA_VERSION,
scoreVersion: REGISTRY_HEALTH_SCORE_VERSION,
generatedAt: now.toISOString(),
globalMeans,
registries: {},
}
for (const entry of directory) {
const entryState = state.registries[entry.name]
const health = calculateRegistryHealth({
state: entryState,
registryUrl: entry.url,
globalMeans,
now,
})
entryState.status = health.status
snapshot.registries[entry.name] = health
}
const run: RegistryMonitorRun = {
schemaVersion: REGISTRY_HEALTH_SCHEMA_VERSION,
startedAt,
completedAt: new Date().toISOString(),
mode,
totals,
results: runResults,
diagnostics:
discardedDryRuns > 0
? [
`Discarded ${discardedDryRuns} invalid CLI dry-run observations from the pre-tsconfig scaffold.`,
]
: [],
}
return {
state: registryMonitorStateSchema.parse(state),
snapshot: registryHealthSnapshotSchema.parse(snapshot),
run: registryMonitorRunSchema.parse(run),
}
}

View File

@@ -0,0 +1,242 @@
import { describe, expect, it, vi } from "vitest"
import {
createGuardedConnector,
detectBotChallenge,
fetchRegistryJson,
isForbiddenAddress,
validateRegistryUrl,
} from "./network"
describe("network destination safety", () => {
it.each([
"127.0.0.1",
"10.0.0.1",
"168.63.129.16",
"169.254.169.254",
"192.0.2.1",
"192.88.99.1",
"192.168.1.1",
"::1",
"64:ff9b::7f00:1",
"100::",
"2001::1",
"2002:7f00:1::",
"fe80::1",
"fec0::1",
"fd00:ec2::254",
"::ffff:127.0.0.1",
])("rejects non-public address %s", (address) => {
expect(isForbiddenAddress(address)).toBe(true)
})
it.each(["1.1.1.1", "8.8.8.8", "2606:4700:4700::1111"])(
"allows public address %s",
(address) => {
expect(isForbiddenAddress(address)).toBe(false)
}
)
it("rejects unsupported protocols and embedded credentials", () => {
expect(() => validateRegistryUrl("file:///etc/passwd")).toThrow(
"unsupported_protocol"
)
expect(() =>
validateRegistryUrl("https://user:secret@example.com/registry.json")
).toThrow("embedded_credentials")
})
it("pins the validated address used by the socket connector", async () => {
const connect = vi.fn((_options, callback) => callback(null, {}))
const connector = createGuardedConnector({
resolve: async () => [{ address: "203.0.114.10", family: 4 }],
connect,
})
await new Promise<void>((resolve, reject) => {
connector(
{
hostname: "registry.example.com",
protocol: "https:",
port: "443",
},
(error, _socket) => (error ? reject(error) : resolve())
)
})
expect(connect).toHaveBeenCalledWith(
expect.objectContaining({
hostname: "203.0.114.10",
servername: "registry.example.com",
}),
expect.any(Function)
)
})
it("rejects a hostname when DNS includes a private address", async () => {
const connector = createGuardedConnector({
resolve: async () => [
{ address: "203.0.114.10", family: 4 },
{ address: "127.0.0.1", family: 4 },
],
connect: vi.fn(),
})
await expect(
new Promise<void>((resolve, reject) => {
connector(
{
hostname: "registry.example.com",
protocol: "https:",
port: "443",
},
(error, _socket) => (error ? reject(error) : resolve())
)
})
).rejects.toThrow("forbidden_address")
})
})
describe("fetchRegistryJson", () => {
const dispatcher = {
close: vi.fn(async () => {}),
}
it("validates every manual redirect target", async () => {
const fetchImpl = vi.fn(
async () =>
new Response(null, {
status: 302,
headers: { location: "http://127.0.0.1/registry.json" },
})
)
await expect(
fetchRegistryJson("https://example.com/registry.json", {
fetchImpl,
createDispatcher: () => dispatcher as never,
attempts: 1,
})
).resolves.toMatchObject({
ok: false,
failureCode: "forbidden_address",
})
})
it("shares one timeout budget across redirects", async () => {
vi.useFakeTimers()
vi.setSystemTime(new Date("2026-08-24T12:00:00.000Z"))
try {
const fetchImpl = vi
.fn()
.mockImplementationOnce(async () => {
vi.setSystemTime(new Date("2026-08-24T12:00:00.080Z"))
return new Response(null, {
status: 302,
headers: { location: "https://example.com/final.json" },
})
})
.mockImplementationOnce(
async (_url: string, init: RequestInit) =>
new Promise<Response>((_resolve, reject) => {
init.signal?.addEventListener("abort", () => {
reject(new DOMException("Aborted", "AbortError"))
})
})
)
const resultPromise = fetchRegistryJson(
"https://example.com/registry.json",
{
fetchImpl,
createDispatcher: () => dispatcher as never,
attempts: 1,
timeoutMs: 100,
}
)
await vi.advanceTimersByTimeAsync(20)
await expect(resultPromise).resolves.toMatchObject({
ok: false,
failureCode: "timeout",
})
expect(fetchImpl).toHaveBeenCalledTimes(2)
} finally {
vi.useRealTimers()
}
})
it("retries a retryable response and honors Retry-After", async () => {
const wait = vi.fn(async () => {})
const fetchImpl = vi
.fn()
.mockResolvedValueOnce(
new Response("busy", {
status: 429,
headers: { "retry-after": "1" },
})
)
.mockResolvedValueOnce(
new Response('{"name":"acme"}', {
status: 200,
headers: { "content-type": "application/json" },
})
)
const result = await fetchRegistryJson(
"https://example.com/registry.json",
{
fetchImpl,
createDispatcher: () => dispatcher as never,
wait,
}
)
expect(result.ok).toBe(true)
expect(fetchImpl).toHaveBeenCalledTimes(2)
expect(wait).toHaveBeenCalledWith(1000)
})
it("enforces the response-size limit", async () => {
const result = await fetchRegistryJson(
"https://example.com/registry.json",
{
fetchImpl: async () => new Response("too large"),
createDispatcher: () => dispatcher as never,
maximumBytes: 3,
attempts: 1,
}
)
expect(result).toMatchObject({
ok: false,
failureCode: "body_too_large",
})
})
})
describe("detectBotChallenge", () => {
it("recognizes a provider challenge page", () => {
expect(
detectBotChallenge({
status: 403,
contentType: "text/html",
server: "cloudflare",
body: "<title>Just a moment...</title><div class='cf-chl-test'>",
})
).toBe(true)
})
it("does not classify an ordinary JSON 403 as a challenge", () => {
expect(
detectBotChallenge({
status: 403,
contentType: "application/json",
server: "",
body: '{"error":"forbidden"}',
})
).toBe(false)
})
})

View File

@@ -0,0 +1,586 @@
import { lookup } from "node:dns/promises"
import { isIP } from "node:net"
import {
Agent,
buildConnector,
fetch as undiciFetch,
type Dispatcher,
} from "undici"
const DEFAULT_TIMEOUT_MS = 10_000
const DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024
const DEFAULT_MAX_REDIRECTS = 5
const MAX_RETRY_AFTER_MS = 30_000
type ResolvedAddress = {
address: string
family: number
}
type ResolveHostname = (hostname: string) => Promise<ResolvedAddress[]>
type Connector = ReturnType<typeof buildConnector>
type FetchLike = (
url: string,
init: RequestInit & { dispatcher?: Dispatcher }
) => Promise<Response>
export type RegistryJsonResult =
| {
ok: true
json: unknown
status: number
durationMs: number
responseSize: number
contentType: string
redirectCount: number
finalUrl: string
}
| {
ok: false
failureCode: string
reachable: boolean
botChallenge: boolean
status?: number
durationMs: number
responseSize?: number
contentType?: string
redirectCount: number
finalUrl: string
}
type RegistryJsonAttemptResult =
| Extract<RegistryJsonResult, { ok: true }>
| (Extract<RegistryJsonResult, { ok: false }> & {
retryAfterMs?: number
})
function parseIpv4(address: string) {
if (isIP(address) !== 4) return null
return address.split(".").map(Number)
}
function parseIpv6(address: string) {
if (address.includes("%") || isIP(address) !== 6) return null
let value = address.toLowerCase()
const ipv4Match = value.match(/(\d+\.\d+\.\d+\.\d+)$/)
if (ipv4Match) {
const ipv4 = parseIpv4(ipv4Match[1])
if (!ipv4) return null
value = value.replace(
ipv4Match[1],
`${((ipv4[0] << 8) | ipv4[1]).toString(16)}:${(
(ipv4[2] << 8) |
ipv4[3]
).toString(16)}`
)
}
const [left = "", right = ""] = value.split("::")
const leftParts = left ? left.split(":") : []
const rightParts = right ? right.split(":") : []
const missing = 8 - leftParts.length - rightParts.length
const parts = value.includes("::")
? [
...leftParts,
...Array.from({ length: missing }, () => "0"),
...rightParts,
]
: leftParts
if (parts.length !== 8) return null
const parsed = parts.map((part) => Number.parseInt(part || "0", 16))
return parsed.every((part) => Number.isInteger(part) && part <= 0xffff)
? parsed
: null
}
function isForbiddenIpv4(address: string) {
const parts = parseIpv4(address)
if (!parts) return true
const [a, b, c] = parts
return (
a === 0 ||
a === 10 ||
a === 127 ||
(a === 100 && b >= 64 && b <= 127) ||
(a === 169 && b === 254) ||
(a === 172 && b >= 16 && b <= 31) ||
(a === 192 && b === 0 && c === 0) ||
(a === 192 && b === 0 && c === 2) ||
(a === 192 && b === 88 && c === 99) ||
(a === 192 && b === 168) ||
(a === 198 && (b === 18 || b === 19)) ||
(a === 198 && b === 51 && c === 100) ||
(a === 203 && b === 0 && c === 113) ||
(a === 168 && b === 63 && c === 129 && parts[3] === 16) ||
a >= 224
)
}
function isForbiddenIpv6(address: string) {
const parts = parseIpv6(address)
if (!parts) return true
const isUnspecified = parts.every((part) => part === 0)
const isLoopback =
parts.slice(0, 7).every((part) => part === 0) && parts[7] === 1
const isUniqueLocal = (parts[0] & 0xfe00) === 0xfc00
const isLinkLocal = (parts[0] & 0xffc0) === 0xfe80
const isSiteLocal = (parts[0] & 0xffc0) === 0xfec0
const isMulticast = (parts[0] & 0xff00) === 0xff00
const isDocumentation = parts[0] === 0x2001 && parts[1] === 0x0db8
const isProtocolAssignment =
parts[0] === 0x2001 && parts[1] >= 0 && parts[1] <= 0x01ff
const isSixToFour = parts[0] === 0x2002
const isDiscardOnly =
parts[0] === 0x0100 && parts.slice(1, 4).every((part) => part === 0)
const isNat64 =
(parts[0] === 0x0064 &&
parts[1] === 0xff9b &&
parts.slice(2, 6).every((part) => part === 0)) ||
(parts[0] === 0x0064 && parts[1] === 0xff9b && parts[2] === 0x0001)
const isIpv4Mapped =
parts.slice(0, 5).every((part) => part === 0) && parts[5] === 0xffff
const isIpv4Compatible = parts.slice(0, 6).every((part) => part === 0)
if (isIpv4Mapped) {
return isForbiddenIpv4(
`${parts[6] >> 8}.${parts[6] & 255}.${parts[7] >> 8}.${parts[7] & 255}`
)
}
return (
isUnspecified ||
isLoopback ||
isUniqueLocal ||
isLinkLocal ||
isSiteLocal ||
isMulticast ||
isDocumentation ||
isProtocolAssignment ||
isSixToFour ||
isDiscardOnly ||
isNat64 ||
isIpv4Compatible
)
}
export function isForbiddenAddress(address: string) {
const normalized = address.replace(/^\[|\]$/g, "")
const family = isIP(normalized)
if (family === 4) return isForbiddenIpv4(normalized)
if (family === 6) return isForbiddenIpv6(normalized)
return true
}
export function validateRegistryUrl(input: string) {
const url = new URL(input)
if (url.protocol !== "http:" && url.protocol !== "https:") {
throw new Error("unsupported_protocol")
}
if (url.username || url.password) {
throw new Error("embedded_credentials")
}
const hostname = url.hostname.replace(/^\[|\]$/g, "")
if (isIP(hostname) && isForbiddenAddress(hostname)) {
throw new Error("forbidden_address")
}
return url
}
const defaultResolve: ResolveHostname = (hostname) =>
lookup(hostname, { all: true, verbatim: true })
export function createGuardedConnector({
resolve = defaultResolve,
connect = buildConnector({ timeout: DEFAULT_TIMEOUT_MS }),
}: {
resolve?: ResolveHostname
connect?: Connector
} = {}): Connector {
return (options, callback) => {
const hostname = options.hostname.replace(/^\[|\]$/g, "")
void (async () => {
const addresses = isIP(hostname)
? [{ address: hostname, family: isIP(hostname) }]
: await resolve(hostname)
if (
addresses.length === 0 ||
addresses.some(({ address }) => isForbiddenAddress(address))
) {
throw new Error("forbidden_address")
}
const [{ address }] = addresses
connect(
{
...options,
hostname: address,
servername: options.servername ?? hostname,
},
callback
)
})().catch((error) =>
callback(error instanceof Error ? error : new Error("dns_failure"), null)
)
}
}
function createGuardedDispatcher() {
return new Agent({
connect: createGuardedConnector(),
headersTimeout: DEFAULT_TIMEOUT_MS,
bodyTimeout: DEFAULT_TIMEOUT_MS,
})
}
async function readLimitedBody(response: Response, maximumBytes: number) {
if (!response.body) return { text: "", size: 0 }
const reader = response.body.getReader()
const chunks: Uint8Array[] = []
let size = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
size += value.byteLength
if (size > maximumBytes) {
await reader.cancel()
throw new Error("body_too_large")
}
chunks.push(value)
}
const body = new Uint8Array(size)
let offset = 0
for (const chunk of chunks) {
body.set(chunk, offset)
offset += chunk.byteLength
}
return { text: new TextDecoder().decode(body), size }
}
export function detectBotChallenge({
status,
contentType,
server,
body,
}: {
status: number
contentType: string
server: string
body: string
}) {
const content = body.toLowerCase()
const provider = server.toLowerCase()
const isHtml = contentType.toLowerCase().includes("text/html")
const marker = [
"cf-chl-",
"challenge-platform",
"just a moment",
"attention required",
"vercel security checkpoint",
"captcha",
].some((value) => content.includes(value))
return (
marker &&
(isHtml || status === 403 || status === 429) &&
(provider.includes("cloudflare") ||
provider.includes("vercel") ||
content.includes("challenge") ||
content.includes("captcha"))
)
}
function isRedirect(status: number) {
return [301, 302, 303, 307, 308].includes(status)
}
function isRetryableStatus(status: number) {
return status === 408 || status === 429 || status >= 500
}
function parseRetryAfter(value: string | null, now: number) {
if (!value) return null
const seconds = Number(value)
const delay = Number.isFinite(seconds)
? seconds * 1000
: new Date(value).getTime() - now
if (!Number.isFinite(delay) || delay < 0) return null
return Math.min(delay, MAX_RETRY_AFTER_MS)
}
function sleep(milliseconds: number) {
return new Promise((resolve) => setTimeout(resolve, milliseconds))
}
async function fetchOnce(
input: string,
{
timeoutMs,
maximumBytes,
maximumRedirects,
fetchImpl,
createDispatcher,
}: {
timeoutMs: number
maximumBytes: number
maximumRedirects: number
fetchImpl: FetchLike
createDispatcher: () => Dispatcher
}
) {
const startedAt = Date.now()
let currentUrl = validateRegistryUrl(input)
let redirectCount = 0
while (true) {
validateRegistryUrl(currentUrl.toString())
const remainingMs = timeoutMs - (Date.now() - startedAt)
if (remainingMs <= 0) {
return {
ok: false,
failureCode: "timeout",
reachable: false,
botChallenge: false,
durationMs: Date.now() - startedAt,
redirectCount,
finalUrl: currentUrl.toString(),
} satisfies RegistryJsonAttemptResult
}
const dispatcher = createDispatcher()
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), remainingMs)
try {
const response = await fetchImpl(currentUrl.toString(), {
dispatcher,
redirect: "manual",
signal: controller.signal,
headers: {
Accept: "application/json",
"User-Agent": "shadcn-registry-health/1.0",
},
})
if (isRedirect(response.status)) {
const location = response.headers.get("location")
await response.body?.cancel()
if (!location) {
return {
ok: false,
failureCode: "redirect_without_location",
reachable: false,
botChallenge: false,
status: response.status,
durationMs: Date.now() - startedAt,
redirectCount,
finalUrl: currentUrl.toString(),
} satisfies RegistryJsonAttemptResult
}
redirectCount += 1
if (redirectCount > maximumRedirects) {
return {
ok: false,
failureCode: "too_many_redirects",
reachable: false,
botChallenge: false,
status: response.status,
durationMs: Date.now() - startedAt,
redirectCount,
finalUrl: currentUrl.toString(),
} satisfies RegistryJsonAttemptResult
}
currentUrl = validateRegistryUrl(
new URL(location, currentUrl).toString()
)
continue
}
let body: Awaited<ReturnType<typeof readLimitedBody>>
try {
body = await readLimitedBody(response, maximumBytes)
} catch {
return {
ok: false,
failureCode: "body_too_large",
reachable: response.ok,
botChallenge: false,
status: response.status,
durationMs: Date.now() - startedAt,
redirectCount,
finalUrl: currentUrl.toString(),
} satisfies RegistryJsonAttemptResult
}
const contentType = response.headers.get("content-type") ?? ""
const botChallenge = detectBotChallenge({
status: response.status,
contentType,
server: response.headers.get("server") ?? "",
body: body.text,
})
if (botChallenge) {
return {
ok: false,
failureCode: "bot_challenge",
reachable: false,
botChallenge: true,
status: response.status,
durationMs: Date.now() - startedAt,
responseSize: body.size,
contentType,
redirectCount,
finalUrl: currentUrl.toString(),
} satisfies RegistryJsonAttemptResult
}
if (!response.ok) {
return {
ok: false,
failureCode: `http_${response.status}`,
reachable: false,
botChallenge: false,
status: response.status,
durationMs: Date.now() - startedAt,
responseSize: body.size,
contentType,
redirectCount,
finalUrl: currentUrl.toString(),
retryAfterMs:
parseRetryAfter(response.headers.get("retry-after"), Date.now()) ??
undefined,
} satisfies RegistryJsonAttemptResult
}
try {
return {
ok: true,
json: JSON.parse(body.text),
status: response.status,
durationMs: Date.now() - startedAt,
responseSize: body.size,
contentType,
redirectCount,
finalUrl: currentUrl.toString(),
} satisfies RegistryJsonAttemptResult
} catch {
return {
ok: false,
failureCode: "invalid_json",
reachable: true,
botChallenge: false,
status: response.status,
durationMs: Date.now() - startedAt,
responseSize: body.size,
contentType,
redirectCount,
finalUrl: currentUrl.toString(),
} satisfies RegistryJsonAttemptResult
}
} catch (error) {
const messages = [
error instanceof Error ? error.message : "",
error instanceof Error && error.cause instanceof Error
? error.cause.message
: "",
]
const guardedFailure = [
"embedded_credentials",
"forbidden_address",
"unsupported_protocol",
].find((failureCode) => messages.includes(failureCode))
const failureCode =
guardedFailure ??
(error instanceof Error && error.name === "AbortError"
? "timeout"
: "transport_error")
return {
ok: false,
failureCode,
reachable: false,
botChallenge: false,
durationMs: Date.now() - startedAt,
redirectCount,
finalUrl: currentUrl.toString(),
} satisfies RegistryJsonAttemptResult
} finally {
clearTimeout(timeout)
await dispatcher.close()
}
}
}
export async function fetchRegistryJson(
input: string,
options: {
attempts?: number
timeoutMs?: number
maximumBytes?: number
maximumRedirects?: number
fetchImpl?: FetchLike
createDispatcher?: () => Dispatcher
wait?: (milliseconds: number) => Promise<unknown>
random?: () => number
} = {}
): Promise<RegistryJsonResult> {
const attempts = options.attempts ?? 3
const wait = options.wait ?? sleep
const random = options.random ?? Math.random
let result: Awaited<ReturnType<typeof fetchOnce>> | undefined
for (let attempt = 0; attempt < attempts; attempt += 1) {
result = await fetchOnce(input, {
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
maximumBytes: options.maximumBytes ?? DEFAULT_MAX_BODY_BYTES,
maximumRedirects: options.maximumRedirects ?? DEFAULT_MAX_REDIRECTS,
fetchImpl: options.fetchImpl ?? (undiciFetch as unknown as FetchLike),
createDispatcher: options.createDispatcher ?? createGuardedDispatcher,
})
const retryable =
!result.ok &&
!result.botChallenge &&
(result.failureCode === "transport_error" ||
result.failureCode === "timeout" ||
(result.status !== undefined && isRetryableStatus(result.status)))
if (!retryable || attempt === attempts - 1) {
return result
}
await wait(
result.retryAfterMs ??
Math.min(1000 * 2 ** attempt + random() * 250, 5000)
)
}
return result!
}

View File

@@ -0,0 +1,75 @@
import { describe, expect, it } from "vitest"
import { registryMonitorEntryStateSchema } from "./schema"
describe("registryMonitorEntryStateSchema", () => {
it("migrates legacy availability recovery state from retained history", () => {
const failures = Array.from({ length: 3 }, (_, index) => ({
checkedAt: `2026-08-16T0${index}:00:00.000Z`,
outcome: "unreachable" as const,
durationMs: 100,
redirectCount: 0,
}))
const recovery = {
checkedAt: "2026-08-24T11:00:00.000Z",
outcome: "reachable" as const,
durationMs: 100,
redirectCount: 0,
schemaValid: true,
}
const state = registryMonitorEntryStateSchema.parse({
firstObservedAt: "2026-08-01T00:00:00.000Z",
lastSuccessfulCheck: recovery.checkedAt,
status: "unavailable",
itemCursor: 0,
itemNames: [],
recentIndex: [...failures, recovery],
recentDryRuns: [],
daily: [],
latestHygiene: {
contentTypeJson: true,
noDuplicateNames: true,
nameMatches: true,
},
})
expect(state).toMatchObject({
consecutiveSuccessfulIndexes: 1,
consecutiveIndexFailures: 0,
availabilityOutageSince: failures[0].checkedAt,
availabilityRecoveryRequired: true,
})
})
it("preserves the last success as the start of a legacy outage", () => {
const lastSuccessfulCheck = "2026-08-23T08:00:00.000Z"
const state = registryMonitorEntryStateSchema.parse({
firstObservedAt: "2026-08-01T00:00:00.000Z",
lastSuccessfulCheck,
status: "degraded",
itemCursor: 0,
itemNames: [],
recentIndex: Array.from({ length: 3 }, (_, index) => ({
checkedAt: `2026-08-24T0${index}:00:00.000Z`,
outcome: "unreachable" as const,
durationMs: 100,
redirectCount: 0,
})),
recentDryRuns: [],
daily: [],
latestHygiene: {
contentTypeJson: true,
noDuplicateNames: true,
nameMatches: true,
},
})
expect(state).toMatchObject({
consecutiveSuccessfulIndexes: 0,
consecutiveIndexFailures: 3,
availabilityOutageSince: lastSuccessfulCheck,
availabilityRecoveryRequired: true,
})
})
})

View File

@@ -0,0 +1,315 @@
import { z } from "zod"
import { registryNamespaceSchema } from "../registry-directory"
export const REGISTRY_HEALTH_SCHEMA_VERSION = 1 as const
export const REGISTRY_HEALTH_SCORE_VERSION = 1 as const
export const REGISTRY_DRY_RUN_VERSION = 1 as const
export const REGISTRY_DRY_RUN_FAILURE_MESSAGE_MAX_LENGTH = 1000
export const registryHealthStatusSchema = z.enum([
"healthy",
"observing",
"degraded",
"unavailable",
])
export const registryHealthStatusReasonCodeSchema = z.enum([
"healthy_thresholds",
"collecting_baseline",
"index_unavailable",
"recovery_pending",
"consecutive_index_failures",
"index_schema_invalid",
"item_validation_failures",
"dry_run_failures",
])
export const registryHealthBreakdownSchema = z
.object({
reliability: z.number().min(0).max(45),
correctness: z.number().min(0).max(25),
installability: z.number().min(0).max(20),
hygiene: z.number().min(0).max(10),
})
.strict()
export const registryHealthSchema = z
.object({
schemaVersion: z.literal(REGISTRY_HEALTH_SCHEMA_VERSION),
scoreVersion: z.literal(REGISTRY_HEALTH_SCORE_VERSION),
status: registryHealthStatusSchema,
statusReason: z
.object({
code: registryHealthStatusReasonCodeSchema,
message: z.string().min(1),
})
.strict()
.optional(),
score: z.number().min(0).max(100),
breakdown: registryHealthBreakdownSchema,
availability7d: z.number().min(0).max(1),
availability30d: z.number().min(0).max(1),
monitoringLimited: z.boolean(),
firstObservedAt: z.string().datetime().optional(),
checkedAt: z.string().datetime(),
lastSuccessfulCheck: z.string().datetime().optional(),
hidden: z.boolean(),
})
.strict()
export const registryHealthGlobalMeansSchema = z
.object({
availability7d: z.number().min(0).max(1),
availability30d: z.number().min(0).max(1),
indexSchema: z.number().min(0).max(1),
itemValidity: z.number().min(0).max(1),
dryRun: z.number().min(0).max(1),
})
.strict()
export const registryHealthSnapshotSchema = z
.object({
schemaVersion: z.literal(REGISTRY_HEALTH_SCHEMA_VERSION),
scoreVersion: z.literal(REGISTRY_HEALTH_SCORE_VERSION),
generatedAt: z.string().datetime(),
globalMeans: registryHealthGlobalMeansSchema,
registries: z.record(registryNamespaceSchema, registryHealthSchema),
})
.strict()
export const registryIndexObservationSchema = z
.object({
checkedAt: z.string().datetime(),
outcome: z.enum(["reachable", "unreachable", "bot_challenge"]),
status: z.number().int().min(100).max(599).optional(),
failureCode: z.string().optional(),
durationMs: z.number().int().nonnegative(),
responseSize: z.number().int().nonnegative().optional(),
redirectCount: z.number().int().nonnegative(),
schemaValid: z.boolean().optional(),
contentTypeJson: z.boolean().optional(),
duplicateNames: z.boolean().optional(),
nameMatches: z.boolean().optional(),
itemCount: z.number().int().nonnegative().optional(),
})
.strict()
export const registryItemObservationSchema = z
.object({
checkedAt: z.string().datetime(),
item: z.string(),
success: z.boolean(),
failureCode: z.string().optional(),
durationMs: z.number().int().nonnegative(),
})
.strict()
export const registryDryRunObservationSchema = z
.object({
checkedAt: z.string().datetime(),
item: z.string(),
success: z.boolean(),
failureCode: z.string().optional(),
failureMessage: z
.string()
.max(REGISTRY_DRY_RUN_FAILURE_MESSAGE_MAX_LENGTH)
.optional(),
durationMs: z.number().int().nonnegative(),
})
.strict()
export const registryHealthDailyBucketSchema = z
.object({
date: z.string().date(),
availabilitySuccesses: z.number().int().nonnegative(),
availabilityObservations: z.number().int().nonnegative(),
challengeObservations: z.number().int().nonnegative(),
schemaSuccesses: z.number().int().nonnegative(),
schemaObservations: z.number().int().nonnegative(),
itemSuccesses: z.number().int().nonnegative(),
itemObservations: z.number().int().nonnegative(),
dryRunSuccesses: z.number().int().nonnegative(),
dryRunObservations: z.number().int().nonnegative(),
})
.strict()
const registryMonitorEntryStateInputSchema = z
.object({
firstObservedAt: z.string().datetime(),
lastSuccessfulCheck: z.string().datetime().optional(),
status: registryHealthStatusSchema,
consecutiveSuccessfulIndexes: z.number().int().nonnegative().optional(),
consecutiveIndexFailures: z.number().int().nonnegative().optional(),
availabilityOutageSince: z.string().datetime().optional(),
availabilityRecoveryRequired: z.boolean().optional(),
itemCursor: z.number().int().nonnegative(),
itemNames: z.array(z.string()),
recentIndex: z.array(registryIndexObservationSchema),
recentDryRuns: z.array(registryDryRunObservationSchema),
daily: z.array(registryHealthDailyBucketSchema),
latestHygiene: z
.object({
contentTypeJson: z.boolean().nullable(),
noDuplicateNames: z.boolean().nullable(),
nameMatches: z.boolean().nullable(),
})
.strict(),
})
.strict()
function getLegacyAvailabilityState(
entry: z.infer<typeof registryMonitorEntryStateInputSchema>
) {
const observations = entry.recentIndex.filter(
(observation) => observation.outcome !== "bot_challenge"
)
let cursor = observations.length - 1
let consecutiveSuccessfulIndexes = 0
while (cursor >= 0) {
const observation = observations[cursor]
if (observation.outcome !== "reachable" || !observation.schemaValid) {
break
}
consecutiveSuccessfulIndexes += 1
cursor -= 1
}
const failureEnd = cursor
let precedingFailures = 0
while (cursor >= 0 && observations[cursor].outcome === "unreachable") {
precedingFailures += 1
cursor -= 1
}
const consecutiveIndexFailures =
consecutiveSuccessfulIndexes === 0 ? precedingFailures : 0
const recoveryRequired =
entry.status === "unavailable" ||
(entry.status === "degraded" && precedingFailures >= 3)
const failureStart =
precedingFailures > 0
? observations[failureEnd - precedingFailures + 1]
: undefined
const outageStart =
consecutiveSuccessfulIndexes > 0
? failureStart?.checkedAt
: (entry.lastSuccessfulCheck ?? failureStart?.checkedAt)
const availabilityOutageSince =
recoveryRequired || consecutiveIndexFailures > 0
? (outageStart ?? entry.firstObservedAt)
: undefined
return {
consecutiveSuccessfulIndexes,
consecutiveIndexFailures,
availabilityOutageSince,
availabilityRecoveryRequired: recoveryRequired,
}
}
export const registryMonitorEntryStateSchema =
registryMonitorEntryStateInputSchema.transform((entry) => {
if (
entry.consecutiveSuccessfulIndexes !== undefined &&
entry.consecutiveIndexFailures !== undefined &&
entry.availabilityRecoveryRequired !== undefined
) {
return entry as typeof entry & {
consecutiveSuccessfulIndexes: number
consecutiveIndexFailures: number
availabilityRecoveryRequired: boolean
}
}
return {
...entry,
...getLegacyAvailabilityState(entry),
}
})
export const registryMonitorStateSchema = z
.object({
schemaVersion: z.literal(REGISTRY_HEALTH_SCHEMA_VERSION),
scoreVersion: z.literal(REGISTRY_HEALTH_SCORE_VERSION),
dryRunVersion: z.number().int().nonnegative().optional(),
updatedAt: z.string().datetime(),
lastDailyRunAt: z.string().datetime().optional(),
lastWeeklyRunAt: z.string().datetime().optional(),
registries: z.record(
registryNamespaceSchema,
registryMonitorEntryStateSchema
),
})
.strict()
export const registryMonitorRunSchema = z
.object({
schemaVersion: z.literal(REGISTRY_HEALTH_SCHEMA_VERSION),
startedAt: z.string().datetime(),
completedAt: z.string().datetime(),
mode: z.enum(["auto", "hourly", "daily", "weekly", "all"]),
totals: z
.object({
registries: z.number().int().nonnegative(),
reachable: z.number().int().nonnegative(),
unavailable: z.number().int().nonnegative(),
challenges: z.number().int().nonnegative(),
itemChecks: z.number().int().nonnegative(),
dryRuns: z.number().int().nonnegative(),
})
.strict(),
results: z.record(
registryNamespaceSchema,
z
.object({
index: registryIndexObservationSchema.optional(),
items: z.array(registryItemObservationSchema),
dryRun: registryDryRunObservationSchema.optional(),
})
.strict()
),
diagnostics: z.array(z.string()),
})
.strict()
export const registryMonitorOutputSchema = z
.object({
state: registryMonitorStateSchema,
snapshot: registryHealthSnapshotSchema,
run: registryMonitorRunSchema,
})
.strict()
export type RegistryHealth = z.infer<typeof registryHealthSchema>
export type RegistryHealthStatusReasonCode = z.infer<
typeof registryHealthStatusReasonCodeSchema
>
export type RegistryHealthBreakdown = z.infer<
typeof registryHealthBreakdownSchema
>
export type RegistryHealthGlobalMeans = z.infer<
typeof registryHealthGlobalMeansSchema
>
export type RegistryHealthSnapshot = z.infer<
typeof registryHealthSnapshotSchema
>
export type RegistryIndexObservation = z.infer<
typeof registryIndexObservationSchema
>
export type RegistryItemObservation = z.infer<
typeof registryItemObservationSchema
>
export type RegistryDryRunObservation = z.infer<
typeof registryDryRunObservationSchema
>
export type RegistryHealthDailyBucket = z.infer<
typeof registryHealthDailyBucketSchema
>
export type RegistryMonitorEntryState = z.infer<
typeof registryMonitorEntryStateSchema
>
export type RegistryMonitorState = z.infer<typeof registryMonitorStateSchema>
export type RegistryMonitorRun = z.infer<typeof registryMonitorRunSchema>
export type RegistryMonitorOutput = z.infer<typeof registryMonitorOutputSchema>

View File

@@ -0,0 +1,393 @@
import { describe, expect, it } from "vitest"
import type {
RegistryHealthDailyBucket,
RegistryIndexObservation,
RegistryMonitorEntryState,
} from "./schema"
import {
calculateRegistryHealth,
DEFAULT_GLOBAL_MEANS,
getRegistryHealthSignals,
} from "./score"
const NOW = new Date("2026-08-24T12:00:00.000Z")
function createDailyBucket(overrides: Partial<RegistryHealthDailyBucket> = {}) {
return {
date: "2026-08-24",
availabilitySuccesses: 0,
availabilityObservations: 0,
challengeObservations: 0,
schemaSuccesses: 0,
schemaObservations: 0,
itemSuccesses: 0,
itemObservations: 0,
dryRunSuccesses: 0,
dryRunObservations: 0,
...overrides,
} satisfies RegistryHealthDailyBucket
}
function createIndexObservations(
count = 25,
outcome: RegistryIndexObservation["outcome"] = "reachable"
) {
return Array.from({ length: count }, (_, index) => ({
checkedAt: new Date(
NOW.getTime() - (count - index) * 60 * 60 * 1000
).toISOString(),
outcome,
durationMs: 100,
redirectCount: 0,
schemaValid: outcome === "reachable",
})) satisfies RegistryIndexObservation[]
}
function createState(overrides: Partial<RegistryMonitorEntryState> = {}) {
return {
firstObservedAt: "2026-08-22T10:00:00.000Z",
lastSuccessfulCheck: "2026-08-24T11:00:00.000Z",
status: "healthy",
consecutiveSuccessfulIndexes: 25,
consecutiveIndexFailures: 0,
availabilityRecoveryRequired: false,
itemCursor: 0,
itemNames: [],
recentIndex: createIndexObservations(),
recentDryRuns: [],
daily: [
createDailyBucket({
availabilitySuccesses: 25,
availabilityObservations: 25,
schemaSuccesses: 25,
schemaObservations: 25,
}),
],
latestHygiene: {
contentTypeJson: null,
noDuplicateNames: null,
nameMatches: null,
},
...overrides,
} satisfies RegistryMonitorEntryState
}
describe("calculateRegistryHealth", () => {
it("derives the published total from rounded components", () => {
const health = calculateRegistryHealth({
state: createState(),
registryUrl: "https://example.com/r/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(health.score).toBe(
health.breakdown.reliability +
health.breakdown.correctness +
health.breakdown.installability +
health.breakdown.hygiene
)
expect(health.score).toBeGreaterThanOrEqual(0)
expect(health.score).toBeLessThanOrEqual(100)
expect(health.statusReason).toEqual({
code: "healthy_thresholds",
message: "Recent checks are within healthy thresholds",
})
})
it("explains when baseline observations are still being collected", () => {
const health = calculateRegistryHealth({
state: createState({ recentIndex: createIndexObservations(3) }),
registryUrl: "https://example.com/r/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(health.status).toBe("observing")
expect(health.statusReason).toEqual({
code: "collecting_baseline",
message: "Collecting baseline data (3 of 24 checks)",
})
})
it("gives unknown hygiene signals half credit", () => {
const health = calculateRegistryHealth({
state: createState(),
registryUrl: "https://example.com/r/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(health.breakdown.hygiene).toBe(6.25)
})
it("uses a cadence-scaled prior for weekly dry runs", () => {
const state = createState({
daily: [
createDailyBucket({
availabilitySuccesses: 25,
availabilityObservations: 25,
schemaSuccesses: 25,
schemaObservations: 25,
dryRunSuccesses: 1,
dryRunObservations: 1,
}),
],
})
const health = calculateRegistryHealth({
state,
registryUrl: "https://example.com/r/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(health.breakdown.installability).toBe(18.5)
})
it("excludes challenge observations from availability", () => {
const state = createState({
recentIndex: [
...createIndexObservations(),
{
checkedAt: NOW.toISOString(),
outcome: "bot_challenge",
durationMs: 100,
redirectCount: 0,
},
],
})
const signals = getRegistryHealthSignals(
state,
"https://example.com/{name}.json",
NOW
)
const health = calculateRegistryHealth({
state,
registryUrl: "https://example.com/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(signals.availability7d).toEqual({
successes: 25,
observations: 25,
})
expect(health.monitoringLimited).toBe(true)
expect(health.status).toBe("healthy")
})
it("hides a registry only after seven days unavailable", () => {
const state = createState({
firstObservedAt: "2026-08-01T00:00:00.000Z",
lastSuccessfulCheck: "2026-08-16T00:00:00.000Z",
status: "unavailable",
consecutiveSuccessfulIndexes: 0,
consecutiveIndexFailures: 8,
availabilityOutageSince: "2026-08-16T00:00:00.000Z",
availabilityRecoveryRequired: true,
recentIndex: createIndexObservations(8, "unreachable"),
})
const health = calculateRegistryHealth({
state,
registryUrl: "https://example.com/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(health.status).toBe("unavailable")
expect(health.statusReason).toEqual({
code: "index_unavailable",
message: "No successful index check in the last 24 hours",
})
expect(health.hidden).toBe(true)
})
it("keeps an unavailable registry visible before seven days", () => {
const state = createState({
firstObservedAt: "2026-08-01T00:00:00.000Z",
lastSuccessfulCheck: "2026-08-20T00:00:00.000Z",
status: "unavailable",
consecutiveSuccessfulIndexes: 0,
consecutiveIndexFailures: 8,
availabilityOutageSince: "2026-08-20T00:00:00.000Z",
availabilityRecoveryRequired: true,
recentIndex: createIndexObservations(8, "unreachable"),
})
const health = calculateRegistryHealth({
state,
registryUrl: "https://example.com/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(health.status).toBe("unavailable")
expect(health.hidden).toBe(false)
})
it("keeps a long outage hidden through the first recovery success", () => {
const recovery = createIndexObservations(1)[0]
const state = createState({
firstObservedAt: "2026-08-01T00:00:00.000Z",
lastSuccessfulCheck: recovery.checkedAt,
status: "unavailable",
consecutiveSuccessfulIndexes: 1,
consecutiveIndexFailures: 0,
availabilityOutageSince: "2026-08-16T00:00:00.000Z",
availabilityRecoveryRequired: true,
recentIndex: [...createIndexObservations(24, "unreachable"), recovery],
})
const health = calculateRegistryHealth({
state,
registryUrl: "https://example.com/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(health.status).toBe("unavailable")
expect(health.statusReason?.code).toBe("recovery_pending")
expect(health.hidden).toBe(true)
})
it("requires two successful checks to recover from availability degradation", () => {
const history = createIndexObservations()
const failures = createIndexObservations(3, "unreachable")
const successes = createIndexObservations(2)
const state = createState({
status: "degraded",
consecutiveSuccessfulIndexes: 1,
consecutiveIndexFailures: 0,
availabilityOutageSince: failures[0].checkedAt,
availabilityRecoveryRequired: true,
recentIndex: [...history, ...failures, successes[0]],
})
const recovering = calculateRegistryHealth({
state,
registryUrl: "https://example.com/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
const recovered = calculateRegistryHealth({
state: {
...state,
consecutiveSuccessfulIndexes: 2,
availabilityOutageSince: undefined,
availabilityRecoveryRequired: false,
recentIndex: [...state.recentIndex, successes[1]],
},
registryUrl: "https://example.com/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(recovering.status).toBe("degraded")
expect(recovering.statusReason).toEqual({
code: "recovery_pending",
message: "Waiting for a second successful recovery check",
})
expect(recovered.status).toBe("healthy")
})
it("explains each degraded status condition", () => {
const failures = calculateRegistryHealth({
state: createState({
consecutiveSuccessfulIndexes: 0,
consecutiveIndexFailures: 3,
availabilityOutageSince: "2026-08-24T09:00:00.000Z",
availabilityRecoveryRequired: true,
recentIndex: [
...createIndexObservations(),
...createIndexObservations(3, "unreachable"),
],
}),
registryUrl: "https://example.com/r/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
const observations = createIndexObservations()
const invalidSchema = calculateRegistryHealth({
state: createState({
recentIndex: [
...observations.slice(0, -1),
{ ...observations.at(-1)!, schemaValid: false },
],
}),
registryUrl: "https://example.com/r/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
const invalidItems = calculateRegistryHealth({
state: createState({
daily: [
createDailyBucket({
availabilitySuccesses: 25,
availabilityObservations: 25,
schemaSuccesses: 25,
schemaObservations: 25,
itemSuccesses: 8,
itemObservations: 10,
}),
],
}),
registryUrl: "https://example.com/r/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
const dryRuns = calculateRegistryHealth({
state: createState({
recentDryRuns: [
{
checkedAt: "2026-08-17T12:00:00.000Z",
item: "button",
success: false,
durationMs: 100,
},
{
checkedAt: "2026-08-24T12:00:00.000Z",
item: "button",
success: false,
durationMs: 100,
},
],
}),
registryUrl: "https://example.com/r/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(failures.statusReason).toEqual({
code: "consecutive_index_failures",
message: "The registry index failed 3 consecutive checks",
})
expect(invalidSchema.statusReason).toEqual({
code: "index_schema_invalid",
message: "The registry index failed schema validation",
})
expect(invalidItems.statusReason).toEqual({
code: "item_validation_failures",
message: "Sampled registry items are failing validation",
})
expect(dryRuns.statusReason).toEqual({
code: "dry_run_failures",
message: "The last two CLI dry-run checks failed",
})
})
it("recovers correctness degradation when its condition clears", () => {
const state = createState({
status: "degraded",
recentIndex: createIndexObservations(),
})
const health = calculateRegistryHealth({
state,
registryUrl: "https://example.com/{name}.json",
globalMeans: DEFAULT_GLOBAL_MEANS,
now: NOW,
})
expect(health.status).toBe("healthy")
})
})

View File

@@ -0,0 +1,442 @@
import {
REGISTRY_HEALTH_SCHEMA_VERSION,
REGISTRY_HEALTH_SCORE_VERSION,
type RegistryHealth,
type RegistryHealthGlobalMeans,
type RegistryMonitorEntryState,
type RegistryMonitorState,
} from "./schema"
const DAY_MS = 24 * 60 * 60 * 1000
const DEFAULT_GLOBAL_MEANS: RegistryHealthGlobalMeans = {
availability7d: 0.85,
availability30d: 0.85,
indexSchema: 0.9,
itemValidity: 0.9,
dryRun: 0.9,
}
const PRIOR_WEIGHTS = {
availability7d: 24,
availability30d: 24,
indexSchema: 12,
itemValidity: 10,
dryRun: 3,
} as const
type RateSignal = {
successes: number
observations: number
}
export type RegistryHealthSignals = {
availability7d: RateSignal
availability30d: RateSignal
indexSchema: RateSignal
itemValidity: RateSignal
dryRun: RateSignal
hygiene: {
https: boolean
contentTypeJson: boolean | null
noDuplicateNames: boolean | null
nameMatches: boolean | null
}
}
function round(value: number, digits: number) {
const factor = 10 ** digits
return Math.round((value + Number.EPSILON) * factor) / factor
}
function isWithinWindow(timestamp: string, now: Date, days: number) {
const age = now.getTime() - new Date(timestamp).getTime()
return age >= 0 && age < days * DAY_MS
}
function isBucketWithinWindow(date: string, now: Date, days: number) {
return isWithinWindow(`${date}T00:00:00.000Z`, now, days)
}
function sumDailySignal(
state: RegistryMonitorEntryState,
now: Date,
days: number,
successesKey:
| "availabilitySuccesses"
| "schemaSuccesses"
| "itemSuccesses"
| "dryRunSuccesses",
observationsKey:
| "availabilityObservations"
| "schemaObservations"
| "itemObservations"
| "dryRunObservations"
) {
return state.daily.reduce<RateSignal>(
(signal, bucket) => {
if (!isBucketWithinWindow(bucket.date, now, days)) {
return signal
}
signal.successes += bucket[successesKey]
signal.observations += bucket[observationsKey]
return signal
},
{ successes: 0, observations: 0 }
)
}
export function getRegistryHealthSignals(
state: RegistryMonitorEntryState,
registryUrl: string,
now: Date
): RegistryHealthSignals {
const availability7d = state.recentIndex.reduce<RateSignal>(
(signal, observation) => {
if (
observation.outcome === "bot_challenge" ||
!isWithinWindow(observation.checkedAt, now, 7)
) {
return signal
}
signal.observations += 1
signal.successes += observation.outcome === "reachable" ? 1 : 0
return signal
},
{ successes: 0, observations: 0 }
)
return {
availability7d,
availability30d: sumDailySignal(
state,
now,
30,
"availabilitySuccesses",
"availabilityObservations"
),
indexSchema: sumDailySignal(
state,
now,
30,
"schemaSuccesses",
"schemaObservations"
),
itemValidity: sumDailySignal(
state,
now,
30,
"itemSuccesses",
"itemObservations"
),
dryRun: sumDailySignal(
state,
now,
30,
"dryRunSuccesses",
"dryRunObservations"
),
hygiene: {
https: registryUrl.startsWith("https://"),
contentTypeJson: state.latestHygiene.contentTypeJson,
noDuplicateNames: state.latestHygiene.noDuplicateNames,
nameMatches: state.latestHygiene.nameMatches,
},
}
}
function getObservedMean(
signals: RateSignal[],
minimumObservations: number,
fallback: number
) {
const eligible = signals.filter(
(signal) => signal.observations >= minimumObservations
)
const observations = eligible.reduce(
(total, signal) => total + signal.observations,
0
)
if (observations === 0) {
return fallback
}
const successes = eligible.reduce(
(total, signal) => total + signal.successes,
0
)
return successes / observations
}
export function calculateGlobalMeans(
state: RegistryMonitorState,
registryUrls: Record<string, string>,
now: Date
): RegistryHealthGlobalMeans {
const signals = Object.entries(state.registries).map(([name, entry]) =>
getRegistryHealthSignals(entry, registryUrls[name] ?? "http://invalid", now)
)
return {
availability7d: getObservedMean(
signals.map((signal) => signal.availability7d),
24,
DEFAULT_GLOBAL_MEANS.availability7d
),
availability30d: getObservedMean(
signals.map((signal) => signal.availability30d),
24,
DEFAULT_GLOBAL_MEANS.availability30d
),
indexSchema: getObservedMean(
signals.map((signal) => signal.indexSchema),
12,
DEFAULT_GLOBAL_MEANS.indexSchema
),
itemValidity: getObservedMean(
signals.map((signal) => signal.itemValidity),
10,
DEFAULT_GLOBAL_MEANS.itemValidity
),
dryRun: getObservedMean(
signals.map((signal) => signal.dryRun),
3,
DEFAULT_GLOBAL_MEANS.dryRun
),
}
}
function smooth(signal: RateSignal, globalMean: number, priorWeight: number) {
return (
(signal.successes + globalMean * priorWeight) /
(signal.observations + priorWeight)
)
}
function getHygienePoints(value: boolean | null) {
if (value === null) {
return 1.25
}
return value ? 2.5 : 0
}
function getNonChallengeObservations(state: RegistryMonitorEntryState) {
return state.recentIndex.filter(
(observation) => observation.outcome !== "bot_challenge"
)
}
function getUnavailableSince(state: RegistryMonitorEntryState) {
return state.availabilityOutageSince ?? state.firstObservedAt
}
function deriveStatus(
state: RegistryMonitorEntryState,
signals: RegistryHealthSignals,
now: Date
) {
const observations = getNonChallengeObservations(state)
const latest = observations.at(-1)
const unavailableFor =
now.getTime() - new Date(getUnavailableSince(state)).getTime()
const unavailable =
unavailableFor >= DAY_MS &&
(state.availabilityRecoveryRequired || latest?.outcome === "unreachable")
if (unavailable) {
if (latest?.outcome === "reachable") {
return {
status: "unavailable" as const,
statusReason: {
code: "recovery_pending" as const,
message: "Waiting for a second successful recovery check",
},
}
}
return {
status: "unavailable" as const,
statusReason: {
code: "index_unavailable" as const,
message: "No successful index check in the last 24 hours",
},
}
}
const observationSpan = latest
? new Date(latest.checkedAt).getTime() -
new Date(state.firstObservedAt).getTime()
: 0
if (observations.length < 24 || observationSpan < DAY_MS) {
return {
status: "observing" as const,
statusReason: {
code: "collecting_baseline" as const,
message:
observations.length < 24
? `Collecting baseline data (${observations.length} of 24 checks)`
: "Collecting a full day of baseline data",
},
}
}
const trailingFailures = state.consecutiveIndexFailures
const latestSchemaInvalid =
latest?.outcome === "reachable" && latest.schemaValid === false
const itemDegraded =
signals.itemValidity.observations >= 10 &&
signals.itemValidity.successes / signals.itemValidity.observations < 0.9
const recentDryRuns = state.recentDryRuns.slice(-2)
const dryRunDegraded =
recentDryRuns.length === 2 &&
recentDryRuns.every((observation) => !observation.success)
if (trailingFailures >= 3) {
return {
status: "degraded" as const,
statusReason: {
code: "consecutive_index_failures" as const,
message: `The registry index failed ${trailingFailures} consecutive checks`,
},
}
}
if (latestSchemaInvalid) {
return {
status: "degraded" as const,
statusReason: {
code: "index_schema_invalid" as const,
message: "The registry index failed schema validation",
},
}
}
if (itemDegraded) {
return {
status: "degraded" as const,
statusReason: {
code: "item_validation_failures" as const,
message: "Sampled registry items are failing validation",
},
}
}
if (dryRunDegraded) {
return {
status: "degraded" as const,
statusReason: {
code: "dry_run_failures" as const,
message: "The last two CLI dry-run checks failed",
},
}
}
if (state.availabilityRecoveryRequired) {
return {
status: "degraded" as const,
statusReason: {
code: "recovery_pending" as const,
message: "Waiting for a second successful recovery check",
},
}
}
return {
status: "healthy" as const,
statusReason: {
code: "healthy_thresholds" as const,
message: "Recent checks are within healthy thresholds",
},
}
}
export function calculateRegistryHealth({
state,
registryUrl,
globalMeans,
now,
}: {
state: RegistryMonitorEntryState
registryUrl: string
globalMeans: RegistryHealthGlobalMeans
now: Date
}): RegistryHealth {
const signals = getRegistryHealthSignals(state, registryUrl, now)
const availability7d = smooth(
signals.availability7d,
globalMeans.availability7d,
PRIOR_WEIGHTS.availability7d
)
const availability30d = smooth(
signals.availability30d,
globalMeans.availability30d,
PRIOR_WEIGHTS.availability30d
)
const indexSchema = smooth(
signals.indexSchema,
globalMeans.indexSchema,
PRIOR_WEIGHTS.indexSchema
)
const itemValidity = smooth(
signals.itemValidity,
globalMeans.itemValidity,
PRIOR_WEIGHTS.itemValidity
)
const dryRun = smooth(
signals.dryRun,
globalMeans.dryRun,
PRIOR_WEIGHTS.dryRun
)
const breakdown = {
reliability: round(
45 * (0.65 * availability7d + 0.35 * availability30d),
3
),
correctness: round(10 * indexSchema + 15 * itemValidity, 3),
installability: round(20 * dryRun, 3),
hygiene: round(
(signals.hygiene.https ? 2.5 : 0) +
getHygienePoints(signals.hygiene.contentTypeJson) +
getHygienePoints(signals.hygiene.noDuplicateNames) +
getHygienePoints(signals.hygiene.nameMatches),
3
),
}
const score = round(
breakdown.reliability +
breakdown.correctness +
breakdown.installability +
breakdown.hygiene,
3
)
const { status, statusReason } = deriveStatus(state, signals, now)
const unavailableFor =
now.getTime() - new Date(getUnavailableSince(state)).getTime()
const hidden = status === "unavailable" && unavailableFor >= 7 * DAY_MS
const latestObservation = state.recentIndex.at(-1)
const checkedAt = latestObservation?.checkedAt ?? state.firstObservedAt
return {
schemaVersion: REGISTRY_HEALTH_SCHEMA_VERSION,
scoreVersion: REGISTRY_HEALTH_SCORE_VERSION,
status,
statusReason,
score,
breakdown,
availability7d: round(availability7d, 6),
availability30d: round(availability30d, 6),
monitoringLimited: latestObservation?.outcome === "bot_challenge",
firstObservedAt: state.firstObservedAt,
checkedAt,
lastSuccessfulCheck: state.lastSuccessfulCheck,
hidden,
}
}
export { DEFAULT_GLOBAL_MEANS, PRIOR_WEIGHTS }

View File

@@ -0,0 +1,23 @@
import type { RegistryMonitorEntryState } from "./schema"
function createRegistryMonitorEntryState(now: Date) {
return {
firstObservedAt: now.toISOString(),
status: "observing",
consecutiveSuccessfulIndexes: 0,
consecutiveIndexFailures: 0,
availabilityRecoveryRequired: false,
itemCursor: 0,
itemNames: [],
recentIndex: [],
recentDryRuns: [],
daily: [],
latestHygiene: {
contentTypeJson: null,
noDuplicateNames: null,
nameMatches: null,
},
} satisfies RegistryMonitorEntryState
}
export { createRegistryMonitorEntryState }