src/ops-session-auth.ts
64 lines and 3 functions in DrDustinEdwards/capsid, at commit daa875b on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| sessionCallerRefusal | 34 to 40 | 5 | 100% | OkCRAP 5 | No data: mutation testing has not run on this repo yet |
| sessionJobFor | 43 to 52 | 5 | 100% | OkCRAP 5 | No data: mutation testing has not run on this repo yet |
| resolveSessionCaller | 55 to 70 | 5 | 75% | WatchCRAP 5.4 | No data: mutation testing has not run on this repo yet |
Imports
It imports src/agents.ts, src/http.ts, src/scope.ts. Imported by src/ops-hooks.ts, src/ops-otlp.ts.
Source
import { resolveAgent, type Agent } from "./agents";
import { checkScope, JOBS_TOOL } from "./scope";
import { textResponse as text } from "./http";
// Who is reporting on a Claude Code session, and which job that session is working.
// The one caller check for the receivers a session posts to on its own: the hook
// receiver (src/ops-hooks.ts) and the OTLP receiver (src/ops-otlp.ts).
//
// The bearer is the session's own driver key. Accepted:
// - a minted driver (kind driver, row-backed),
// - the admin (a legacy write operator key), for testing.
// Anything else that resolves is 403; nothing that resolves is 401.
//
// The write grant is asked of checkScope (one enforcement point, test/scope.test.ts): what
// these routes write is part of a job's record, so a read-only caller may not send it.
// The tool named is "jobs", which every caller that works jobs holds.
//
// The job: a driver is bound to the single
// job it holds claimed, or to none when it holds zero or several. The admin is bound to
// none.
const SESSION_REALM = "capsid-hooks";
export interface SessionCaller {
agent: Agent;
job_id: string | null;
// The bound job's namespace, or null with no job.
namespace: string | null;
// last_seen, best effort, after the answer (see handleOperatorMcp in src/routes.ts).
touch: () => Promise<void>;
}
/** Why this agent may not report on a session, or null. */
export function sessionCallerRefusal(agent: Agent): string | null {
if (agent.admin) return null;
if (!agent.row || (agent.kind !== "driver" && agent.kind !== "session")) {
return `forbidden: ${agent.actor} is not a driver. Hooks and telemetry are posted with the session's own driver key (docs/hooks.md, docs/telemetry.md).`;
}
return checkScope(agent, { tool: JOBS_TOOL, grant: "write" });
}
/** The job a caller's session is working: the one job a driver holds claimed. None, or more than one, is no binding. */
async function sessionJobFor(db: D1Database, agent: Agent): Promise<{ job_id: string; namespace: string } | null> {
if (!agent.row || agent.kind !== "driver") return null;
// LIMIT 2, so "holds several" is told apart from "holds one" without reading them all.
const { results } = await db
.prepare("SELECT id, namespace FROM jobs WHERE claimed_by = ?1 AND status = 'claimed' LIMIT 2")
.bind(agent.actor)
.all<{ id: string; namespace: string }>();
const held = results ?? [];
return held.length === 1 ? { job_id: held[0].id, namespace: held[0].namespace } : null;
}
/** The caller and its job, or the refusal to send. */
export async function resolveSessionCaller(
request: Request,
env: { DB: D1Database; OPERATOR_KEY_HASH?: string },
now: Date = new Date()
): Promise<SessionCaller | Response> {
const resolved = await resolveAgent(request, env, now);
if (!resolved) {
return text("unauthorized: a driver key is required as Authorization: Bearer <key>", 401, {
"WWW-Authenticate": `Bearer realm="${SESSION_REALM}"`,
});
}
const refusal = sessionCallerRefusal(resolved.agent);
if (refusal) return text(refusal, 403);
const binding = await sessionJobFor(env.DB, resolved.agent);
return { agent: resolved.agent, job_id: binding?.job_id ?? null, namespace: binding?.namespace ?? null, touch: resolved.touch };
}