src/ops-types/sites.ts
183 lines and 0 functions in DrDustinEdwards/capsid, at commit daa875b on 2026-10-24. This file on GitHub.
Functions
None: it only declares types or values.
Imports
It imports no other module of this repo. Imported by src/health.ts, src/live-checks.ts, src/maintenance-deploys.ts, src/ops-cloudflare.ts, src/ops-packages.ts, src/ops-sites.ts, src/ops-snapshot.ts, src/ops-types/feed.ts, src/shared-code.ts, src/site-convergence.ts, src/site-operator.ts, src/site-repair.ts, src/site-watch.ts, src/watcher-detect.ts, src/watcher-gather.ts, src/watcher.ts.
Source
// The Watch Floor contract: what GET /portal/api/ops returns
// (capsid/research/design-ops-console.md). Types only, no runtime code, so the
// dashboard app under dashboard/ imports it with `import type` and src/ never imports
// from the app. Changing a field here changes both sides, and both typecheck against it.
export type ProbeState = "ok" | "degraded" | "liveness" | "down";
type CheckState = "clear" | "finding" | "could-not-run";
export interface SiteProbe {
namespace: string;
name: string;
origin: string;
health_path: string | null;
platform: "cloudflare" | "vercel";
state: ProbeState;
// The status of the URL the state rests on: the health route, or the root.
http_status: number | null;
latency_ms: number | null;
// Reported by the site's own health route, where it reports one.
sha: string | null;
error: string | null;
checked_at: string;
}
export interface CfDeploy {
id: string;
created_on: string;
// The version serving the largest share of traffic in this deployment.
version_id: string | null;
// annotations["workers/message"] and ["workers/triggered_by"], as Cloudflare
// records them.
message: string | null;
triggered_by: string | null;
author_email: string | null;
}
export interface HourBucket {
// The hour's start, ISO.
hour: string;
requests: number;
errors: number;
}
// Cloudflare's view of one site. Every state but "ok" is shown as no data, with its
// reason, never as zero.
export type SiteCloudflare =
| { state: "no-token"; reason: string }
| { state: "not-cloudflare"; reason: string }
| { state: "unresolved"; reason: string }
| { state: "error"; reason: string }
| {
state: "ok";
script: string;
// Newest first, at most 10.
deploys: CfDeploy[];
// 24 hourly buckets, oldest first; null with a reason when the analytics query
// failed while the deployments read did not.
errors24: HourBucket[] | null;
errors_reason: string | null;
};
export interface SiteSnapshot extends SiteProbe {
// '1' up, '0' down, '-' no pass, one character per half-hour slot, 7 days.
ring: string;
// The clock slot (minutes since the epoch / 30) of the ring's last character.
ring_slot: number;
// Absent on snapshots written before the Cloudflare read existed.
cloudflare?: SiteCloudflare;
}
export interface CiObservation {
namespace: string;
// Null when the repo's runs could not be read this pass.
latest: { head_sha: string; status: string; conclusion: string | null; created_at: string; url: string | null } | null;
}
export interface MirrorObservation {
newest_dump: string | null;
last_run: { at: string | null; conclusion: string | null; url: string | null } | null;
}
export interface SiteMapDrift {
unmapped: string[];
unknown: string[];
}
// One row of the Portal's site configuration (ops_sites, src/ops-sites.ts). A row with
// no origin says its namespace serves no site; platform is null exactly then.
export interface OpsSiteConfig {
namespace: string;
name: string;
origin: string | null;
health_path: string | null;
platform: "cloudflare" | "vercel" | null;
script: string | null;
// Probed in-process rather than over HTTP: Capsid's own row. Not settable from the Portal.
self_probe: boolean;
// Counts edits. site_edit and site_remove name the revision they previewed.
revision: number;
updated_at: string;
// The site's operator API, where it opted in (migrations/0035, src/site-operator.ts);
// null where it has none. operator_problem says why a stored value was not usable.
operator: SiteOperator | null;
operator_problem: string | null;
}
// A site's operator API as Capsid may use it: where it is, which Capsid Worker secret
// holds its token (the name only), which repair each failing health check maps to (in
// repair order), the tools run once a week, and the names of the site Worker's secrets
// to report set or not.
export interface SiteOperator {
path: string;
// The name of the Capsid Worker secret holding the site's operator token.
auth_var: string;
repairs: Record<string, string>;
weekly: string[];
secrets: string[];
}
export interface HealthSnapshot {
status: "ok" | "degraded";
sha: string;
dirty: boolean;
builtAt: string | null;
schema_version: string | null;
store: { d1: string; fts: string };
bindings: { media: string; app_kv: string };
backup: { last_ok: string | null; age_hours: number | null; warning?: string };
}
export interface OpsSnapshot {
version: 1;
pass_at: string;
pass_ms: number;
cadence_min: number;
checks: Array<{ id: string; state: CheckState; findings: string[] }>;
health: HealthSnapshot | null;
mirror: MirrorObservation | null;
ci: CiObservation[];
site_map: SiteMapDrift | null;
sites: SiteSnapshot[];
// Each configured package as this pass read it (src/ops-packages.ts). Optional, so a
// snapshot written before the panel existed still parses; absent or empty with no
// package configured.
packages?: PackageSnapshot[];
}
// One row of the Portal's package configuration (ops_packages, migrations/0028).
export interface OpsPackageConfig {
// The npm name, the row's key.
name: string;
registry: "npm";
// owner/name on GitHub, or null.
repo: string | null;
// An earlier npm name whose download history is shown joined to this one, or null.
formerly: string | null;
// Counts edits. package_edit and package_remove name the revision they previewed.
revision: number;
updated_at: string;
}
// One part of a package's read: what it said, or why it could not be read. "none" is
// an answer (no repository configured, no dependents on record), not a failure.
type PackagePart<T> = ({ state: "ok" } & T) | { state: "none"; reason: string } | { state: "error"; reason: string };
// What one watcher pass read for one package. Every source is named where it is used
// (src/ops-packages.ts), with what it counts and what it misses.
export interface PackageSnapshot {
name: string;
registry: "npm";
at: string;
// registry.npmjs.org, the abbreviated document.
npm: PackagePart<{ latest: string | null; dist_tags: Record<string, string>; versions: number; modified: string | null }>;
// api.npmjs.org: the last 7 and 30 days, and per version for the last 7 days only
// (npm keeps no per-version history). through is the last day npm has counted.
downloads: PackagePart<{ last_week: number; last_month: number; through: string | null; by_version_last_week: Record<string, number> }>;
// deps.dev v3alpha, for the default version: distinct packages that depend on it,
// directly or through another package. Counts only; no public API lists them.
dependents: PackagePart<{ version: string; direct: number; indirect: number; total: number }>;
// The GitHub App's read of the repository.
github: PackagePart<{ repo: string; stars: number; open_issues: number; open_prs: number; open_prs_capped: boolean; latest_release: { tag: string; published_at: string | null } | null }>;
}
// GET /portal/api/packages/history?name=: the daily download history of one configured
// package, joined to its former name's, and its weekly GitHub numbers.
export interface PortalPackageHistory {
name: string;
formerly: string | null;
generated: string;
// Served from the cache when it is younger than its TTL; the time it was fetched.
fetched_at: string;
// One entry per day with any count, oldest first; each day names which package the
// count came from.
days: Array<{ day: string; downloads: number; name: string }>;
first_day: string | null;
last_day: string | null;
// Why a range could not be read, and anything else a reader should know.
notes: string[];
weeks: Array<{ week: string; stars: number; open_issues: number; open_prs: number; latest_release: string | null }>;
}