Skip to the content

Capsid menu

src/public-stats.ts

210 lines and 43 functions in DrDustinEdwards/capsid, at commit daa875b on 2026-10-24. This file on GitHub.

Functions

The functions of public-stats.ts
FunctionLinesComplexityCoveredCRAPMutation
rate83 to 852100%OkCRAP 2No data: mutation testing has not run on this repo yet
median87 to 923100%OkCRAP 3No data: mutation testing has not run on this repo yet
<anonymous:89>89 to 891100%OkCRAP 1No data: mutation testing has not run on this repo yet
verifiedFields94 to 102567%WatchCRAP 5.9No data: mutation testing has not run on this repo yet
windowEnd105 to 1071100%OkCRAP 1No data: mutation testing has not run on this repo yet
since109 to 1091100%OkCRAP 1No data: mutation testing has not run on this repo yet
summarize112 to 1887100%WatchCRAP 7No data: mutation testing has not run on this repo yet
inWindow115 to 1152100%OkCRAP 2No data: mutation testing has not run on this repo yet
weekly118 to 1231100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:119>119 to 1231100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:122>122 to 1222100%OkCRAP 2No data: mutation testing has not run on this repo yet
<anonymous:124>124 to 1241100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:125>125 to 1251100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:125>125 to 1251100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:126>126 to 1261100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:126>126 to 1261100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:126>126 to 1261100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:128>128 to 1281100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:129>129 to 1291100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:131>131 to 1342100%OkCRAP 2No data: mutation testing has not run on this repo yet
<anonymous:135>135 to 1352100%OkCRAP 2No data: mutation testing has not run on this repo yet
<anonymous:136>136 to 1362100%OkCRAP 2No data: mutation testing has not run on this repo yet
<anonymous:137>137 to 1372100%OkCRAP 2No data: mutation testing has not run on this repo yet
agreement139 to 1421100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:140>140 to 1401100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:141>141 to 1411100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:143>143 to 1431100%OkCRAP 1No data: mutation testing has not run on this repo yet
openOf154 to 1541100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:154>154 to 1541100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:154>154 to 1541100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:161>161 to 1611100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:162>162 to 1621100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:163>163 to 1632100%OkCRAP 2No data: mutation testing has not run on this repo yet
<anonymous:168>168 to 1681100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:168>168 to 1681100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:174>174 to 1741100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:175>175 to 1751100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:179>179 to 1791100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:180>180 to 1801100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:181>181 to 1811100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:181>181 to 1811100%OkCRAP 1No data: mutation testing has not run on this repo yet
readStatsRows191 to 2299100%WatchCRAP 9No data: mutation testing has not run on this repo yet
<anonymous:223>223 to 2231100%OkCRAP 1No data: mutation testing has not run on this repo yet

Imports

It imports src/env.ts, src/limits.ts, src/ops-snapshot.ts, src/time.ts. Imported by src/public-data.ts.

Source

import { z } from "zod";
import type { Env } from "./env";
import { bounded } from "./limits";
import { readSnapshot } from "./ops-snapshot";
import { MINUTE_MS, DAY_MS } from "./time";

// The public aggregate numbers (ruling capsid/rulings/system-explorer-2026-10-10.md,
// section 3 and answer 5; design capsid/research/design-system-explorer.md, section 2;
// docs/public-data.md). Counts, rates and medians over every namespace, agent and site
// at once, computed once a UTC day over whole days ending at midnight. Never a title, a
// body, a name, an email, error text, or a time of day: the only strings are the date
// and the short sha /health already shows.
//
// A rate or median over fewer than FLOOR rows is null, so one job cannot be read off it.

export const FLOOR = 10;

const Count = z.number().int().min(0);
const Rate = z.number().min(0).max(1).nullable();

export const PublicStatsSchema = z.strictObject({
  version: z.literal(1),
  as_of: bounded(10).regex(/^\d{4}-\d{2}-\d{2}$/),
  jobs: z.strictObject({
    completed_7d: Count,
    completed_30d: Count,
    failed_30d: Count,
    // Eight whole weeks, oldest first, each ending at a midnight.
    completed_by_week: z.array(Count).length(8),
    // The same eight weeks. The three weekly series added on 2026-10-23 are optional only so the
    // explorer still parses a document the public build wrote before they existed.
    failed_by_week: z.array(Count).length(8).optional(),
    open: Count,
    blocked: Count,
    median_duration_minutes_30d: Count.nullable(),
  }),
  verification: z.strictObject({
    evaluations_90d: Count,
    agreement_rate_90d: Rate,
    pr_merge_rate_90d: Rate,
    ci_green_rate_90d: Rate,
    // Agreement in each of the eight weeks, null for a week under the floor.
    agreement_by_week: z.array(Rate).length(8).optional(),
  }),
  gates: z.strictObject({
    waits_30d: Count,
    median_wait_minutes_30d: Count.nullable(),
    share_ended_by_policy_30d: Rate,
    waits_by_week: z.array(Count).length(8).optional(),
  }),
  sites: z.strictObject({
    watched: Count,
    uptime_7d: Rate,
    slots_measured_7d: Count,
  }),
  store: z.strictObject({ documents: Count, namespaces: Count }),
  systems: z.strictObject({ public: Count, private: Count }).nullable(),
  capsid: z.strictObject({ sha7: bounded(7).regex(/^[0-9a-f]{7}$|^unknown$/) }),
});

export type PublicStats = z.infer<typeof PublicStatsSchema>;

/** The rows the numbers are computed from, each already limited to its window. */
export interface StatsRows {
  /** Jobs that finished (done or failed) in the last 56 days: status and when. */
  finished: Array<{ status: string; updated_at: string }>;
  /** Open jobs by status, read at the build. */
  open: Array<{ status: string; n: number }>;
  /** job_outcomes in the last 90 days. */
  outcomes: Array<{ recorded_at: string; duration_minutes: number | null; prs_opened: number | null; prs_merged: number | null; ci_green: number | null; verified: string }>;
  /** job_evaluations' agreement in the last 90 days, and when each was recorded. */
  evaluations: Array<{ agreement: string; recorded_at: string }>;
  /** Touches that ended a wait in the last 56 days, and when. */
  waits: Array<{ actor_kind: string; waited_ms: number; at: string }>;
  /** Each watched site's uptime ring ('1' up, '0' down, '-' not measured). */
  rings: string[];
  documents: number;
  namespaces: number;
  systems: { public: number; private: number } | null;
  sha: string | undefined;
}

function rate(num: number, den: number): number | null {
  return den < FLOOR ? null : Math.round((num / den) * 1000) / 1000;
}

function median(values: number[]): number | null {
  if (values.length < FLOOR) return null;
  const s = [...values].sort((a, b) => a - b);
  const mid = Math.floor(s.length / 2);
  return Math.round(s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2);
}

function verifiedFields(json: string): Record<string, unknown> {
  try {
    const parsed = JSON.parse(json);
    return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
  } catch {
    // Corrupt means nothing verified, as in src/agent-record.ts.
    return {};
  }
}

/** Midnight UTC at the start of `now`'s day: every window ends here. */
function windowEnd(now: Date): Date {
  return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
}

const since = (end: Date, days: number) => new Date(end.getTime() - days * DAY_MS).toISOString();

/** The public numbers from their rows. Pure, and the only place the rules live. */
export function summarize(rows: StatsRows, now: Date): PublicStats {
  const end = windowEnd(now);
  const endIso = end.toISOString();
  const inWindow = (at: string, days: number) => at >= since(end, days) && at < endIso;

  // Eight whole weeks, oldest first: each week's rows by the time field `at` names.
  const weekly = <T>(items: T[], at: (item: T) => string): T[][] =>
    Array.from({ length: 8 }, (_, i) => {
      const from = since(end, (8 - i) * 7);
      const to = since(end, (7 - i) * 7);
      return items.filter((r) => at(r) >= from && at(r) < to);
    });
  const done = rows.finished.filter((r) => r.status === "done");
  const byWeek = weekly(done, (r) => r.updated_at).map((w) => w.length);
  const failedByWeek = weekly(rows.finished.filter((r) => r.status === "failed"), (r) => r.updated_at).map((w) => w.length);

  const outcomes30 = rows.outcomes.filter((o) => inWindow(o.recorded_at, 30));
  const outcomes90 = rows.outcomes.filter((o) => inWindow(o.recorded_at, 90));
  // As src/agent-record.ts reads them: only fields the Worker verified feed a rate.
  const mergeRows = outcomes90.filter((o) => {
    const v = verifiedFields(o.verified);
    return v.prs_opened === true && v.prs_merged === true;
  });
  const opened = mergeRows.reduce((t, o) => t + (o.prs_opened ?? 0), 0);
  const merged = mergeRows.reduce((t, o) => t + (o.prs_merged ?? 0), 0);
  const ciRows = outcomes90.filter((o) => verifiedFields(o.verified).ci_green === true && o.ci_green !== null);

  const agreement = (evaluations: StatsRows["evaluations"]) => {
    const agree = evaluations.filter((e) => e.agreement === "agree").length;
    return rate(agree, agree + evaluations.filter((e) => e.agreement === "disagree").length);
  };
  const waits30 = rows.waits.filter((w) => inWindow(w.at, 30));

  let up = 0;
  let down = 0;
  for (const ring of rows.rings) {
    for (const c of ring) {
      if (c === "1") up++;
      else if (c === "0") down++;
    }
  }

  const openOf = (status: string) => rows.open.filter((r) => r.status === status).reduce((t, r) => t + r.n, 0);
  const sha = rows.sha && /^[0-9a-f]{7,40}$/.test(rows.sha) ? rows.sha.slice(0, 7) : "unknown";

  return {
    version: 1,
    as_of: endIso.slice(0, 10),
    jobs: {
      completed_7d: done.filter((r) => inWindow(r.updated_at, 7)).length,
      completed_30d: done.filter((r) => inWindow(r.updated_at, 30)).length,
      failed_30d: rows.finished.filter((r) => r.status === "failed" && inWindow(r.updated_at, 30)).length,
      completed_by_week: byWeek,
      failed_by_week: failedByWeek,
      open: openOf("queued") + openOf("claimed") + openOf("blocked"),
      blocked: openOf("blocked"),
      median_duration_minutes_30d: median(outcomes30.map((o) => o.duration_minutes).filter((d): d is number => typeof d === "number")),
    },
    verification: {
      evaluations_90d: rows.evaluations.length,
      agreement_rate_90d: agreement(rows.evaluations),
      pr_merge_rate_90d: rate(merged, opened),
      ci_green_rate_90d: rate(ciRows.filter((o) => o.ci_green === 1).length, ciRows.length),
      agreement_by_week: weekly(rows.evaluations, (e) => e.recorded_at).map(agreement),
    },
    gates: {
      waits_30d: waits30.length,
      median_wait_minutes_30d: median(waits30.map((w) => w.waited_ms / MINUTE_MS)),
      share_ended_by_policy_30d: rate(waits30.filter((w) => w.actor_kind === "policy").length, waits30.length),
      waits_by_week: weekly(rows.waits, (w) => w.at).map((w) => w.length),
    },
    sites: { watched: rows.rings.length, uptime_7d: rate(up, up + down), slots_measured_7d: up + down },
    store: { documents: rows.documents, namespaces: rows.namespaces },
    systems: rows.systems,
    capsid: { sha7: sha },
  };
}

/** Reads the rows for summarize. Single-table reads, each bounded by its window. */
export async function readStatsRows(env: Env, now: Date, systems: StatsRows["systems"]): Promise<StatsRows> {
  const end = windowEnd(now);
  const endIso = end.toISOString();
  const db = env.DB;
  const finished = await db
    .prepare("SELECT status, updated_at FROM jobs WHERE status IN ('done', 'failed') AND updated_at >= ?1 AND updated_at < ?2")
    .bind(since(end, 56), endIso)
    .all<StatsRows["finished"][number]>();
  const open = await db
    .prepare("SELECT status, COUNT(*) AS n FROM jobs WHERE status IN ('queued', 'claimed', 'blocked') GROUP BY status")
    .all<StatsRows["open"][number]>();
  const outcomes = await db
    .prepare("SELECT recorded_at, duration_minutes, prs_opened, prs_merged, ci_green, verified FROM job_outcomes WHERE recorded_at >= ?1 AND recorded_at < ?2")
    .bind(since(end, 90), endIso)
    .all<StatsRows["outcomes"][number]>();
  const evaluations = await db
    .prepare("SELECT agreement, recorded_at FROM job_evaluations WHERE recorded_at >= ?1 AND recorded_at < ?2")
    .bind(since(end, 90), endIso)
    .all<StatsRows["evaluations"][number]>();
  const waits = await db
    .prepare("SELECT actor_kind, waited_ms, at FROM job_touches WHERE waited_ms IS NOT NULL AND at >= ?1 AND at < ?2")
    .bind(since(end, 56), endIso)
    .all<StatsRows["waits"][number]>();
  const documents = await db.prepare("SELECT COUNT(*) AS n FROM documents").first<{ n: number }>();
  const namespaces = await db.prepare("SELECT COUNT(*) AS n FROM namespaces").first<{ n: number }>();
  const snapshot = await readSnapshot(env);
  return {
    finished: finished.results ?? [],
    open: open.results ?? [],
    outcomes: outcomes.results ?? [],
    evaluations: evaluations.results ?? [],
    waits: waits.results ?? [],
    rings: (snapshot?.sites ?? []).map((s) => s.ring),
    documents: documents?.n ?? 0,
    namespaces: namespaces?.n ?? 0,
    systems,
    sha: env.BUILD_SHA,
  };
}