Skip to the content

Capsid menu

src/portal-maintenance.ts

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

Functions

The functions of portal-maintenance.ts
FunctionLinesComplexityCoveredCRAPMutation
sum17 to 171100%OkCRAP 1No data: mutation testing has not run on this repo yet
<anonymous:17>17 to 171100%OkCRAP 1No data: mutation testing has not run on this repo yet
handlePortalMaintenance20 to 38332%WatchCRAP 5.9No data: mutation testing has not run on this repo yet

Imports

It imports src/env.ts, src/maintenance.ts, src/ops-feed.ts, src/ops-types/views.ts, src/portal-auth.ts, src/roster.ts. Imported by src/routes.ts.

Source

import type { Env } from "./env";
import { ROSTER } from "./roster";
import { readMaintenance } from "./maintenance";
import { OPS_RETURN_TO } from "./ops-feed";
import type { PortalMaintenance } from "./ops-types/views";
import { portalGate } from "./portal-auth";

// The Portal's Maintenance list: GET /portal/api/maintenance (job_549550d73d4e). The list
// the daily maintenance pass stored, the same one capsid_run_improve action status serves per namespace, so
// the Portal and chat cannot disagree. It reads KV only and runs nothing: the pass runs on
// the five-minute tick once a day. A route, not a tool, so no grant is checked here
// (one enforcement point, test/route-gates.test.ts): it answers to portalGate, and src/scope.ts lists
// it among the routes gated some other way.

export const PORTAL_MAINTENANCE_PATH = "/portal/api/maintenance";

const sum = (counts: Record<string, number>): number => Object.values(counts).reduce((a, b) => a + b, 0);

/** GET /portal/api/maintenance: the last pass's list and what it read. */
export async function handlePortalMaintenance(request: Request, env: Env, now: Date = new Date()): Promise<Response> {
  const gate = await portalGate(request, env, now, OPS_RETURN_TO);
  if (!gate.ok) return gate.response;
  const list = await readMaintenance(env);
  const body: PortalMaintenance = list
    ? {
        generated: list.generated,
        items: list.items,
        read: {
          prs: sum(list.prs_read),
          branches: sum(list.branches_read),
          repos: Object.keys(list.prs_read).length,
          roster: ROSTER.length,
          deploys: Object.keys(list.deploys_read).length,
        },
      }
    : { generated: null, items: [], read: { prs: 0, branches: 0, repos: 0, roster: ROSTER.length, deploys: 0 } };
  return new Response(JSON.stringify(body), { headers: { "Content-Type": "application/json", "Cache-Control": "no-store" } });
}