Skip to the content

Capsid menu

src/ops-types/claims.ts

157 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/job-claims-read.ts, src/job-claims.ts, src/portal-claims.ts.

Source

// ---------------------------------------------------------------------------
// Claims apart from verified outcomes (migrations/0023_job_claims.sql). The
// `capsid_admin_read_claims` tool and GET /portal/api/claims return these, from the same readers
// (src/job-claims-read.ts). Every column is as stored: JSON columns stay JSON text,
// and NULL stays null, because NULL is not zero.

export type ClaimsAgreement = "agree" | "disagree" | "unclaimed" | "unchecked";

export interface ClaimsFilter {
  namespace: string | null;
  // The claiming agent's actor string, e.g. agent:sample-driver.
  agent: string | null;
  // ISO 8601, inclusive lower bound and exclusive upper bound on each row's own time
  // (recorded_at for claims and evaluations, at for touches).
  since: string | null;
  until: string | null;
}

export interface ClaimsAgreementCounts {
  agree: number;
  disagree: number;
  unclaimed: number;
  unchecked: number;
}

interface ClaimsTouchSummary {
  count: number;
  by_kind: Record<string, number>;
  by_actor_kind: Record<string, number>;
  // Over the touches that carry a wait. Null when none does: no wait was measured,
  // which is not a wait of zero.
  waits: number;
  waited_ms_total: number | null;
  waited_ms_median: number | null;
}

export interface ClaimsGroup {
  // Null for touches on a job no agent has made a claim on yet.
  agent: string | null;
  namespace: string;
  // Distinct jobs with a claim, and claim rows.
  jobs: number;
  claims: number;
  // Per evaluation name (pr_merged, prs_opened, ...), the claim-to-verified agreement.
  evaluations: Record<string, ClaimsAgreementCounts>;
  touches: ClaimsTouchSummary;
}

// One ended wait: a touch with a waited_ms, tied to the gate it ended. gate_class is a
// class of capsid/policy/gates.md (compound commands joined with "+"), "needs_human"
// when the gate's command matched none or recorded no command, or "unreadable_gate"
// when the gate row's detail was not the JSON the writer produces.
export interface ClaimsWaitRow {
  namespace: string;
  gate_class: string;
  // Who ended the wait: human, policy, seat and so on (job_touches.actor_kind).
  ended_by: string;
  waits: number;
  waited_ms_total: number;
  waited_ms_median: number;
}

// Cost, tokens and active time for one source, summed over the rows that carried it.
// Every figure is null when no row carried it, never 0.
export interface ClaimsUsageTotals {
  // Jobs (telemetry) or claims (reported) that carried any usage at all.
  rows: number;
  cost_usd: number | null;
  active_seconds: number | null;
  tokens_input: number | null;
  tokens_output: number | null;
  tokens_cache_read: number | null;
  tokens_cache_creation: number | null;
}

// Per namespace, the two sources side by side and never added together: telemetry from
// job_outcomes (src/job-outcomes.ts readJobUsage) and what the agent reported in
// claim.usage. A job can appear in both, so a sum across them would count it twice.
export interface ClaimsUsageRow {
  namespace: string;
  telemetry: ClaimsUsageTotals;
  reported: ClaimsUsageTotals;
}

export interface ClaimsAggregate {
  filter: ClaimsFilter;
  groups: ClaimsGroup[];
  // Ended waits by namespace, gate class and who ended them, longest total first.
  waits: ClaimsWaitRow[];
  usage: ClaimsUsageRow[];
  // Each bounded read that hit its bound, by name. Empty means the answer is whole.
  truncated: string[];
}

export interface JobClaimRow {
  id: number;
  job_id: string;
  action: "complete" | "fail" | "block";
  agent: string;
  namespace: string;
  raw: string;
  prs_opened_urls: string | null;
  prs_merged_urls: string | null;
  prs_opened: number | null;
  prs_merged: number | null;
  commits: number | null;
  files_changed: number | null;
  tests_added: number | null;
  tests_run: number | null;
  tests_passed: number | null;
  tests_failed: number | null;
  tests_result: string | null;
  deploy_state: string | null;
  files_touched: string | null;
  model_id: string | null;
  client_name: string | null;
  client_version: string | null;
  permission_mode: string | null;
  capsid_sha: string | null;
  recorded_at: string;
}

export interface JobEvaluationRow {
  id: number;
  job_id: string;
  claim_id: number | null;
  name: string;
  score_value: number | null;
  score_label: "pass" | "fail" | "unknown";
  claimed: string | null;
  verified: string | null;
  agreement: ClaimsAgreement;
  evaluator: "worker" | "model" | "human";
  evaluator_id: string;
  explanation: string | null;
  recorded_at: string;
}

export interface JobTouchRow {
  id: number;
  job_id: string;
  namespace: string;
  kind: string;
  actor: string;
  actor_kind: string;
  waited_ms: number | null;
  detail: string | null;
  at: string;
}

export interface ClaimsJob {
  job: { id: string; namespace: string; title: string; status: string; claimed_by: string | null };
  // The job_outcomes row as stored, or null when the job has not ended.
  outcome: Record<string, unknown> | null;
  // Oldest first, each at most `limit`.
  claims: JobClaimRow[];
  evaluations: JobEvaluationRow[];
  touches: JobTouchRow[];
  limit: number;
  truncated: string[];
}

// GET /portal/api/claims?namespace=&agent=&since=&until=  -> PortalClaimsAggregate
// GET /portal/api/claims?job=<id>                         -> PortalClaimsJob (404 JSON for no such job)
// A since or until that is not an ISO time is a text/plain 400.
export interface PortalClaimsAggregate extends ClaimsAggregate {
  generated: string;
}

export interface PortalClaimsJob extends ClaimsJob {
  generated: string;
}