@byollm/server 0.1.0-alpha.3 → 0.1.0-alpha.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -12
- package/bin/keygen.mjs +21 -0
- package/dist/chunk-K5E6JS5A.js +655 -0
- package/dist/chunk-K5E6JS5A.js.map +1 -0
- package/dist/{handlers-D7lWfwno.d.ts → handlers-BJYm2kdq.d.ts} +27 -4
- package/dist/index.d.ts +221 -17
- package/dist/index.js +549 -44
- package/dist/index.js.map +1 -1
- package/dist/next.d.ts +40 -8
- package/dist/next.js +8 -2
- package/dist/next.js.map +1 -1
- package/dist/store-Dno2fnHH.d.ts +436 -0
- package/dist/supabase/index.d.ts +1 -1
- package/dist/supabase/index.js +115 -42
- package/dist/supabase/index.js.map +1 -1
- package/package.json +7 -3
- package/supabase/migrations/20260809000000_byollm_runner.sql +22 -3
- package/supabase/migrations/20260819000000_drop_runner_token.sql +87 -0
- package/supabase/migrations/20260819010000_completed_by_lease_id.sql +25 -0
- package/supabase/migrations/20260821000000_rename_collected.sql +91 -0
- package/dist/chunk-HL6EYHQ7.js +0 -422
- package/dist/chunk-HL6EYHQ7.js.map +0 -1
- package/dist/store-D23N6iiP.d.ts +0 -255
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ids.ts","../src/handlers.ts","../src/http.ts"],"sourcesContent":["import {\n createHash,\n randomBytes,\n randomUUID,\n timingSafeEqual,\n} from \"node:crypto\";\n\n/**\n * Alphabet for the user-facing pairing code.\n *\n * Excludes `0/O`, `1/I/L`, `5/S` and `U/V` — a code is read aloud or copied\n * off a terminal into a browser, and a user who mistypes it gets a failure\n * they cannot diagnose. 27 symbols over 8 characters is ~38 bits, which is\n * ample for a code that lives ten minutes, is single-use, and is rate-limited.\n */\nconst USER_CODE_ALPHABET = \"ABCDEFGHJKMNPQRTWXYZ2346789\";\n\n/** A device code: the secret the daemon polls with. Never shown to a user. */\nexport function generateDeviceCode(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/** A runner bearer token. */\nexport function generateRunnerToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/** A runner id. */\nexport function generateRunnerId(): string {\n return `runner_${randomUUID()}`;\n}\n\n/** A job id. */\nexport function generateJobId(): string {\n return `job_${randomUUID()}`;\n}\n\n/**\n * A short code the user reads and confirms, formatted `XXXX-XXXX`.\n * Drawn with rejection sampling so the alphabet stays uniform.\n */\nexport function generateUserCode(): string {\n const chars: string[] = [];\n while (chars.length < 8) {\n for (const byte of randomBytes(16)) {\n // 256 % 28 !== 0, so bytes at or above the largest whole multiple are\n // discarded rather than folded — folding would bias the low symbols.\n const limit = 256 - (256 % USER_CODE_ALPHABET.length);\n if (byte >= limit) continue;\n const symbol = USER_CODE_ALPHABET[byte % USER_CODE_ALPHABET.length];\n if (symbol === undefined) continue;\n chars.push(symbol);\n if (chars.length === 8) break;\n }\n }\n return `${chars.slice(0, 4).join(\"\")}-${chars.slice(4).join(\"\")}`;\n}\n\n/** SHA-256, hex. Tokens and device codes are stored only as this. */\nexport function hashSecret(secret: string): string {\n return createHash(\"sha256\").update(secret, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Compare two hex digests without leaking their difference through timing.\n * Lengths are compared first because `timingSafeEqual` throws on a mismatch.\n */\nexport function secretsMatch(aHex: string, bHex: string): boolean {\n if (aHex.length !== bHex.length) return false;\n return timingSafeEqual(Buffer.from(aHex, \"hex\"), Buffer.from(bHex, \"hex\"));\n}\n","import {\n ClaimRequest,\n type ClaimRequest as ClaimRequestType,\n type HeartbeatRequest as HeartbeatRequestType,\n type ReleaseRequest as ReleaseRequestType,\n type ResultRequest as ResultRequestType,\n ERROR_STATUS,\n HeartbeatRequest,\n PairRequest,\n PROTOCOL_VERSION,\n ReleaseRequest,\n ResultRequest,\n provenanceFor,\n type ClaimResponse,\n type Endpoint,\n type HeartbeatResponse,\n type PairPollResponse,\n type PairStartResponse,\n type ReleaseResponse,\n type ResultResponse,\n type WireErrorCode,\n} from \"@byollm/protocol\";\nimport { generateDeviceCode, generateUserCode, hashSecret } from \"./ids.js\";\nimport type { RunnerRecord } from \"./records.js\";\nimport type { ByollmStore } from \"./store.js\";\n\n/** Everything a mount needs to serve the protocol. */\nexport interface HandlerConfig {\n readonly store: ByollmStore;\n /**\n * Absolute URL of the page where a user approves a pairing. The device code\n * is *not* appended — the user types the short code into the app's own\n * authenticated page, which is what keeps pairing interactive.\n */\n readonly verificationUrl: string;\n /** How long a lease lasts. Default 60s — six heartbeats of headroom. */\n readonly leaseMs?: number;\n /** How long an unapproved pairing code lives. Default 10 minutes. */\n readonly pairingTtlMs?: number;\n /** How often a daemon may poll for pairing approval. Default 2s. */\n readonly pollIntervalMs?: number;\n /** Injectable clock, so tests can move time without sleeping. */\n readonly now?: () => number;\n}\n\nconst DEFAULTS = {\n leaseMs: 60_000,\n pairingTtlMs: 10 * 60_000,\n pollIntervalMs: 2_000,\n} as const;\n\n/** A handled protocol call: a status and a JSON body. */\nexport interface HandlerResult {\n readonly status: number;\n readonly body: unknown;\n /** Set for `rate-limited` and `server-error`. */\n readonly retryAfterSeconds?: number;\n}\n\nfunction fail(\n error: WireErrorCode,\n message: string,\n retryAfterSeconds?: number,\n): HandlerResult {\n return {\n status: ERROR_STATUS[error],\n body: {\n error,\n message,\n ...(retryAfterSeconds === undefined\n ? {}\n : { retryAfter: retryAfterSeconds }),\n },\n ...(retryAfterSeconds === undefined ? {} : { retryAfterSeconds }),\n };\n}\n\nfunction ok(body: unknown): HandlerResult {\n return { status: 200, body };\n}\n\n/**\n * The five protocol endpoints, over any {@link ByollmStore}.\n *\n * Transport-free on purpose: a mount adapts `Request`/`Response` (or Express,\n * or whatever) onto {@link ByollmHandlers.handle}, and everything the\n * protocol actually specifies lives here where the conformance kit can reach\n * it without an HTTP server in the way.\n */\nexport class ByollmHandlers {\n readonly #store: ByollmStore;\n readonly #verificationUrl: string;\n readonly #leaseMs: number;\n readonly #pairingTtlMs: number;\n readonly #pollIntervalMs: number;\n readonly #now: () => number;\n\n constructor(config: HandlerConfig) {\n this.#store = config.store;\n this.#verificationUrl = config.verificationUrl;\n this.#leaseMs = config.leaseMs ?? DEFAULTS.leaseMs;\n this.#pairingTtlMs = config.pairingTtlMs ?? DEFAULTS.pairingTtlMs;\n this.#pollIntervalMs = config.pollIntervalMs ?? DEFAULTS.pollIntervalMs;\n this.#now = config.now ?? Date.now;\n }\n\n /**\n * Dispatch one protocol call.\n *\n * @param endpoint - which of the five, already routed from the path\n * @param body - the parsed JSON request body, untrusted\n * @param bearer - the `Authorization: Bearer` value, if any\n */\n async handle(\n endpoint: Endpoint,\n body: unknown,\n bearer: string | undefined,\n ): Promise<HandlerResult> {\n switch (endpoint) {\n case \"pair\":\n return this.#pair(body);\n case \"claim\":\n return this.#authed(bearer, body, ClaimRequest, this.#claim.bind(this));\n case \"heartbeat\":\n // Heartbeat is the channel revocation travels on, so a revoked runner\n // must reach the handler and be told `revoked: true` rather than be\n // bounced with a 403 it would treat as a transport problem\n // ({@link MUSTS.REVOCATION_HONORED}).\n return this.#authed(\n bearer,\n body,\n HeartbeatRequest,\n this.#heartbeat.bind(this),\n { allowRevoked: true },\n );\n case \"result\":\n return this.#authed(\n bearer,\n body,\n ResultRequest,\n this.#result.bind(this),\n );\n case \"release\":\n return this.#authed(\n bearer,\n body,\n ReleaseRequest,\n this.#release.bind(this),\n );\n }\n }\n\n /**\n * Shared preamble for the four authenticated endpoints: resolve the bearer\n * token to a runner, reject a revoked one, and parse the body.\n *\n * The token→runner lookup happens before schema validation so a stranger\n * probing the endpoint learns nothing about the wire format.\n */\n async #authed<T>(\n bearer: string | undefined,\n body: unknown,\n schema: { safeParse: (v: unknown) => { success: boolean; data?: T } },\n run: (request: T, runner: RunnerRecord) => Promise<HandlerResult>,\n options: { allowRevoked?: boolean } = {},\n ): Promise<HandlerResult> {\n if (bearer === undefined || bearer.length === 0) {\n return fail(\"unauthorized\", \"a runner token is required\");\n }\n const runner = await this.#store.getRunnerByTokenHash(hashSecret(bearer));\n if (!runner) {\n return fail(\"unauthorized\", \"this runner token is not recognised\");\n }\n if (runner.revokedAt !== null && options.allowRevoked !== true) {\n // A distinct truth from \"unauthorized\": the daemon should stop and say\n // so, not retry or re-pair silently.\n return fail(\"revoked\", \"this runner has been revoked by its owner\");\n }\n\n const parsed = schema.safeParse(body);\n if (!parsed.success || parsed.data === undefined) {\n return fail(\"bad-request\", \"request body failed schema validation\");\n }\n return run(parsed.data, runner);\n }\n\n // -- 1. pair --------------------------------------------------------------\n\n async #pair(body: unknown): Promise<HandlerResult> {\n const parsed = PairRequest.safeParse(body);\n if (!parsed.success) {\n return fail(\"bad-request\", \"pair request failed schema validation\");\n }\n const request = parsed.data;\n const now = this.#now();\n\n if (request.action === \"start\") {\n const deviceCode = generateDeviceCode();\n const userCode = generateUserCode();\n const expiresAt = now + this.#pairingTtlMs;\n\n await this.#store.createPairing({\n deviceCodeHash: hashSecret(deviceCode),\n userCode,\n state: \"pending\",\n owner: null,\n runnerId: null,\n runnerTokenOnce: null,\n label: request.daemon.label,\n platform: request.daemon.platform,\n daemonVersion: request.daemon.version,\n capabilities: request.capabilities,\n expiresAt,\n createdAt: now,\n });\n\n const response: PairStartResponse = {\n deviceCode,\n userCode,\n verificationUrl: this.#verificationUrl,\n expiresAt,\n pollIntervalMs: this.#pollIntervalMs,\n };\n return ok(response);\n }\n\n // action === \"poll\"\n const pairing = await this.#store.getPairingByDeviceCodeHash(\n hashSecret(request.deviceCode),\n );\n if (!pairing) {\n return fail(\"not-found\", \"unknown device code\");\n }\n if (pairing.state === \"denied\") {\n return ok({ status: \"denied\" } satisfies PairPollResponse);\n }\n // Expiry is checked before approval state so a code approved after it\n // lapsed is still dead ({@link MUSTS.PAIR_CODE_EXPIRES}).\n if (pairing.expiresAt <= now && pairing.state === \"pending\") {\n return ok({ status: \"expired\" } satisfies PairPollResponse);\n }\n if (\n pairing.state === \"approved\" &&\n pairing.runnerTokenOnce !== null &&\n pairing.runnerId !== null &&\n pairing.owner !== null\n ) {\n const response: PairPollResponse = {\n status: \"approved\",\n runnerToken: pairing.runnerTokenOnce,\n runnerId: pairing.runnerId,\n owner: pairing.owner,\n };\n // Delivered exactly once — a replayed device code gets nothing.\n await this.#store.consumePairingToken(pairing.deviceCodeHash);\n return ok(response);\n }\n if (pairing.state === \"approved\") {\n return fail(\"not-found\", \"this pairing has already been collected\");\n }\n return ok({ status: \"pending\" } satisfies PairPollResponse);\n }\n\n // -- 2. claim -------------------------------------------------------------\n\n async #claim(\n request: ClaimRequestType,\n runner: RunnerRecord,\n ): Promise<HandlerResult> {\n if (request.runnerId !== runner.id) {\n return fail(\"unauthorized\", \"runner id does not match the bearer token\");\n }\n const now = this.#now();\n\n // Capabilities from *this* request, never the stored matrix — a daemon\n // that just lost a backend must not be handed work for it\n // ({@link MUSTS.CLAIM_REQUIRES_CAPABILITY}).\n const jobs = await this.#store.claim({\n runnerId: runner.id,\n runnerOwner: runner.owner,\n capabilities: request.capabilities,\n max: request.max,\n leaseMs: this.#leaseMs,\n now,\n });\n\n const response: ClaimResponse = {\n jobs: jobs.map((job) => ({\n id: job.id,\n kind: job.kind,\n payload: job.payload,\n audience: job.audience,\n owner: job.owner,\n ...(job.audienceAllow === undefined\n ? {}\n : { audienceAllow: [...job.audienceAllow] }),\n lease: job.lease ?? {\n runnerId: runner.id,\n expiresAt: now + this.#leaseMs,\n },\n })),\n leaseMs: this.#leaseMs,\n };\n return ok(response);\n }\n\n // -- 3. heartbeat ---------------------------------------------------------\n\n async #heartbeat(\n request: HeartbeatRequestType,\n runner: RunnerRecord,\n ): Promise<HandlerResult> {\n if (request.runnerId !== runner.id) {\n return fail(\"unauthorized\", \"runner id does not match the bearer token\");\n }\n const now = this.#now();\n const revoked = runner.revokedAt !== null;\n\n if (revoked) {\n // Nothing is renewed for a revoked runner: every job it holds is\n // reported lost so it abandons the queue rather than finishing it.\n const held = await this.#store.listClaimedBy(runner.id);\n const response: HeartbeatResponse = {\n revoked: true,\n cancel: [],\n leases: [],\n lost: held.map((job) => job.id),\n serverTime: now,\n };\n return ok(response);\n }\n\n await this.#store.touchRunner({\n runnerId: runner.id,\n capabilities: request.capabilities,\n daemonVersion: request.daemonVersion,\n paused: request.paused,\n now,\n });\n\n const { renewed, lost } = await this.#store.renewLeases({\n runnerId: runner.id,\n jobIds: request.activeJobIds,\n leaseMs: this.#leaseMs,\n now,\n });\n\n const cancel = await this.#store.listCancelRequests(runner.id);\n\n const response: HeartbeatResponse = {\n revoked: false,\n cancel,\n leases: renewed.map((r) => ({ jobId: r.jobId, expiresAt: r.expiresAt })),\n lost: [...lost],\n serverTime: now,\n };\n return ok(response);\n }\n\n // -- 4. result ------------------------------------------------------------\n\n async #result(\n request: ResultRequestType,\n runner: RunnerRecord,\n ): Promise<HandlerResult> {\n if (request.runnerId !== runner.id) {\n return fail(\"unauthorized\", \"runner id does not match the bearer token\");\n }\n const now = this.#now();\n const job = await this.#store.get(request.jobId);\n if (!job) return fail(\"not-found\", \"unknown job\");\n\n // Provenance is built here, from the job's audience and the authenticated\n // runner — never from anything the daemon asserted\n // ({@link MUSTS.RESULT_PROVENANCE}).\n const provenance = provenanceFor({\n audience: job.audience,\n runnerId: runner.id,\n runnerOwner: runner.owner,\n backendClass: request.backendClass,\n model: request.model,\n });\n\n const { accepted, job: updated } = await this.#store.complete({\n jobId: request.jobId,\n runnerId: runner.id,\n outcome: request.outcome,\n provenance,\n now,\n });\n\n const response: ResultResponse = {\n accepted,\n state: updated?.state ?? job.state,\n };\n return ok(response);\n }\n\n // -- 5. release -----------------------------------------------------------\n\n async #release(\n request: ReleaseRequestType,\n runner: RunnerRecord,\n ): Promise<HandlerResult> {\n if (request.runnerId !== runner.id) {\n return fail(\"unauthorized\", \"runner id does not match the bearer token\");\n }\n const released = await this.#store.release({\n runnerId: runner.id,\n jobIds: request.jobIds,\n reason: request.reason,\n now: this.#now(),\n });\n const response: ReleaseResponse = { released };\n return ok(response);\n }\n}\n\n/** The protocol version this build speaks. */\nexport const SERVED_PROTOCOL_VERSION = PROTOCOL_VERSION;\n","import { ENDPOINTS, PROTOCOL_PREFIX, type Endpoint } from \"@byollm/protocol\";\nimport { ByollmHandlers, type HandlerConfig } from \"./handlers.js\";\n\n/**\n * Largest protocol request body accepted, before schema validation.\n *\n * A payload is capped at 4 MB of text by the protocol; this leaves room for\n * JSON overhead and a batch of results, and refuses anything wilder at the\n * door rather than after parsing it.\n */\nconst MAX_BODY_BYTES = 8 * 1024 * 1024;\n\n/** Pull the endpoint name out of a URL path, or null if it isn't ours. */\nexport function routeEndpoint(pathname: string): Endpoint | null {\n const index = pathname.lastIndexOf(\"/\");\n const last = index === -1 ? pathname : pathname.slice(index + 1);\n return (ENDPOINTS as readonly string[]).includes(last)\n ? (last as Endpoint)\n : null;\n}\n\n/** Read the bearer token from an `Authorization` header. */\nexport function bearerFrom(header: string | null): string | undefined {\n if (!header) return undefined;\n const match = /^Bearer[ ]+(.+)$/i.exec(header.trim());\n return match?.[1];\n}\n\n/**\n * A `Request` → `Response` handler for the whole protocol.\n *\n * Web-standard types, so this works unchanged in Next.js route handlers, Hono,\n * Bun, Deno, Cloudflare Workers, and anything else that speaks fetch.\n */\nexport function createFetchHandler(\n config: HandlerConfig,\n): (request: Request) => Promise<Response> {\n const handlers = new ByollmHandlers(config);\n\n return async function handle(request: Request): Promise<Response> {\n if (request.method !== \"POST\") {\n return json(405, {\n error: \"bad-request\",\n message: \"protocol endpoints accept POST only\",\n });\n }\n\n const endpoint = routeEndpoint(new URL(request.url).pathname);\n if (endpoint === null) {\n return json(404, {\n error: \"not-found\",\n message: `not a ${PROTOCOL_PREFIX} endpoint`,\n });\n }\n\n const declared = request.headers.get(\"content-length\");\n if (declared !== null && Number(declared) > MAX_BODY_BYTES) {\n return json(400, {\n error: \"bad-request\",\n message: \"request body too large\",\n });\n }\n\n let body: unknown;\n try {\n const text = await request.text();\n if (text.length > MAX_BODY_BYTES) {\n return json(400, {\n error: \"bad-request\",\n message: \"request body too large\",\n });\n }\n body = JSON.parse(text);\n } catch {\n // Deliberately not echoing the parse error: it would quote attacker\n // input back into a response an operator later reads in a terminal.\n return json(400, {\n error: \"bad-request\",\n message: \"request body is not valid JSON\",\n });\n }\n\n const result = await handlers.handle(\n endpoint,\n body,\n bearerFrom(request.headers.get(\"authorization\")),\n );\n\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n };\n if (result.retryAfterSeconds !== undefined) {\n headers[\"retry-after\"] = String(result.retryAfterSeconds);\n }\n return new Response(JSON.stringify(result.body), {\n status: result.status,\n headers,\n });\n };\n}\n\nfunction json(status: number, body: unknown): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n });\n}\n"],"mappings":"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","names":["response"]}
|
package/dist/store-D23N6iiP.d.ts
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import { JobKind, JobPayload, Audience, JobState, Lease, JobOutcome, ResultProvenance, Capability } from '@byollm/protocol';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A job as the server stores it.
|
|
5
|
-
*
|
|
6
|
-
* Adapters map this shape onto their own storage; the field meanings are
|
|
7
|
-
* normative because the conformance kit asserts behaviour that depends on
|
|
8
|
-
* them (TTL clock start, dependency gating, refusal tracking).
|
|
9
|
-
*/
|
|
10
|
-
interface JobRecord {
|
|
11
|
-
readonly id: string;
|
|
12
|
-
readonly kind: JobKind;
|
|
13
|
-
readonly payload: JobPayload;
|
|
14
|
-
readonly audience: Audience;
|
|
15
|
-
/** The app's id for the user who enqueued it. */
|
|
16
|
-
readonly owner: string;
|
|
17
|
-
/** Server-side restriction on which runner owners may take a `named` job. */
|
|
18
|
-
readonly audienceAllow: readonly string[] | undefined;
|
|
19
|
-
/** Job ids that must all be `ok` before this becomes claimable. */
|
|
20
|
-
readonly dependsOn: readonly string[];
|
|
21
|
-
readonly state: JobState;
|
|
22
|
-
readonly lease: Lease | null;
|
|
23
|
-
readonly createdAt: number;
|
|
24
|
-
/**
|
|
25
|
-
* When the job became claimable — enqueue time for a job with no
|
|
26
|
-
* dependencies, or the moment its last dependency reached `ok`.
|
|
27
|
-
*
|
|
28
|
-
* **The TTL clock starts here, not at `createdAt`.** Starting it at enqueue
|
|
29
|
-
* would expire a dependent job for the crime of waiting on a slow
|
|
30
|
-
* dependency (byollm_001 Rev 1 §D, TTL clock resolved in build review).
|
|
31
|
-
* `null` means still blocked.
|
|
32
|
-
*/
|
|
33
|
-
readonly claimableAt: number | null;
|
|
34
|
-
/** How long an unclaimed job may wait once claimable. */
|
|
35
|
-
readonly ttlMs: number;
|
|
36
|
-
/** Optional absolute deadline, independent of the TTL. */
|
|
37
|
-
readonly deadlineAt: number | null;
|
|
38
|
-
/**
|
|
39
|
-
* Runners that released this job with reason `refused` — their local
|
|
40
|
-
* allowlist declined it. Never offered to them again
|
|
41
|
-
* ({@link MUSTS.REFUSAL_NOT_REOFFERED}).
|
|
42
|
-
*/
|
|
43
|
-
readonly refusedBy: readonly string[];
|
|
44
|
-
/** How many times this job has been claimed, including lease-expiry retries. */
|
|
45
|
-
readonly attempts: number;
|
|
46
|
-
readonly outcome: JobOutcome | null;
|
|
47
|
-
readonly provenance: ResultProvenance | null;
|
|
48
|
-
readonly updatedAt: number;
|
|
49
|
-
}
|
|
50
|
-
/** A paired daemon as the server stores it. */
|
|
51
|
-
interface RunnerRecord {
|
|
52
|
-
readonly id: string;
|
|
53
|
-
/** The app's id for the user this runner is bound to — exactly one. */
|
|
54
|
-
readonly owner: string;
|
|
55
|
-
/** SHA-256 of the bearer token. The token itself is never stored. */
|
|
56
|
-
readonly tokenHash: string;
|
|
57
|
-
readonly label: string;
|
|
58
|
-
readonly platform: "darwin" | "linux" | "win32";
|
|
59
|
-
readonly daemonVersion: string;
|
|
60
|
-
readonly capabilities: readonly Capability[];
|
|
61
|
-
readonly paused: boolean;
|
|
62
|
-
/** Set once; a revoked runner never un-revokes. */
|
|
63
|
-
readonly revokedAt: number | null;
|
|
64
|
-
readonly lastHeartbeatAt: number;
|
|
65
|
-
readonly createdAt: number;
|
|
66
|
-
}
|
|
67
|
-
/** An in-flight device-code pairing. */
|
|
68
|
-
interface PairingRecord {
|
|
69
|
-
/** SHA-256 of the device code. The code itself is never stored. */
|
|
70
|
-
readonly deviceCodeHash: string;
|
|
71
|
-
/** The short code the user reads. Unique among live pairings. */
|
|
72
|
-
readonly userCode: string;
|
|
73
|
-
readonly state: "pending" | "approved" | "denied";
|
|
74
|
-
/** Set when approved — learned from the approving user's own session. */
|
|
75
|
-
readonly owner: string | null;
|
|
76
|
-
readonly runnerId: string | null;
|
|
77
|
-
/**
|
|
78
|
-
* The bearer token, held until the daemon's next poll collects it, then
|
|
79
|
-
* cleared. Delivered exactly once.
|
|
80
|
-
*/
|
|
81
|
-
readonly runnerTokenOnce: string | null;
|
|
82
|
-
readonly label: string;
|
|
83
|
-
readonly platform: "darwin" | "linux" | "win32";
|
|
84
|
-
readonly daemonVersion: string;
|
|
85
|
-
readonly capabilities: readonly Capability[];
|
|
86
|
-
readonly expiresAt: number;
|
|
87
|
-
readonly createdAt: number;
|
|
88
|
-
}
|
|
89
|
-
/** What the app supplies to enqueue a job. */
|
|
90
|
-
interface EnqueueInput {
|
|
91
|
-
readonly kind: JobKind;
|
|
92
|
-
readonly payload: JobPayload;
|
|
93
|
-
readonly owner: string;
|
|
94
|
-
/** Defaults to `self` — the safe direction. */
|
|
95
|
-
readonly audience?: Audience;
|
|
96
|
-
readonly audienceAllow?: readonly string[];
|
|
97
|
-
readonly dependsOn?: readonly string[];
|
|
98
|
-
/** Defaults to the server config's `defaultTtlMs`. */
|
|
99
|
-
readonly ttlMs?: number;
|
|
100
|
-
readonly deadlineAt?: number;
|
|
101
|
-
/** Caller-supplied id, for idempotent enqueue. */
|
|
102
|
-
readonly id?: string;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* The adapter seam.
|
|
107
|
-
*
|
|
108
|
-
* Everything in `@byollm/server` above this interface is storage-agnostic;
|
|
109
|
-
* everything below it is one adapter. `MemoryJobStore` is the reference
|
|
110
|
-
* implementation and the thing the conformance kit certifies first — an
|
|
111
|
-
* adapter is correct when the same kit passes against it.
|
|
112
|
-
*/
|
|
113
|
-
interface JobStore {
|
|
114
|
-
/** Create a job. Idempotent when `input.id` is supplied and already exists. */
|
|
115
|
-
create(input: EnqueueInput, now: number): Promise<JobRecord>;
|
|
116
|
-
get(jobId: string): Promise<JobRecord | null>;
|
|
117
|
-
/**
|
|
118
|
-
* Atomically claim up to `max` jobs for a runner
|
|
119
|
-
* ({@link MUSTS.CLAIM_ATOMIC}).
|
|
120
|
-
*
|
|
121
|
-
* An implementation MUST apply, inside the same atomic step:
|
|
122
|
-
* - state is `queued`;
|
|
123
|
-
* - `claimableAt` is non-null and `<= now` (dependencies satisfied,
|
|
124
|
-
* {@link MUSTS.DEPENDS_ON_GATING});
|
|
125
|
-
* - the job's kind appears in `capabilities`
|
|
126
|
-
* ({@link MUSTS.CLAIM_REQUIRES_CAPABILITY});
|
|
127
|
-
* - the audience rules admit this runner
|
|
128
|
-
* ({@link MUSTS.AUDIENCE_BOTH_SIDES});
|
|
129
|
-
* - `runnerId` is not in the job's `refusedBy`
|
|
130
|
-
* ({@link MUSTS.REFUSAL_NOT_REOFFERED}).
|
|
131
|
-
*
|
|
132
|
-
* SQL-backed adapters SHOULD use `FOR UPDATE SKIP LOCKED`.
|
|
133
|
-
*/
|
|
134
|
-
claim(args: ClaimArgs): Promise<JobRecord[]>;
|
|
135
|
-
/**
|
|
136
|
-
* Renew leases for the jobs a runner believes it holds, and report which it
|
|
137
|
-
* has lost. A job whose lease expired un-renewed returns to `queued`
|
|
138
|
-
* ({@link MUSTS.LEASE_RECLAIMABLE}).
|
|
139
|
-
*/
|
|
140
|
-
renewLeases(args: RenewArgs): Promise<RenewResult>;
|
|
141
|
-
/**
|
|
142
|
-
* Record a terminal outcome. Idempotent by job id: the first terminal
|
|
143
|
-
* outcome wins ({@link MUSTS.RESULT_IDEMPOTENT}).
|
|
144
|
-
*
|
|
145
|
-
* Recording `ok` MUST also unblock dependents whose remaining dependencies
|
|
146
|
-
* are all `ok`, setting their `claimableAt` — which is when their TTL clock
|
|
147
|
-
* starts ({@link MUSTS.TTL_EXPIRY}).
|
|
148
|
-
*/
|
|
149
|
-
complete(args: CompleteArgs): Promise<CompleteResult>;
|
|
150
|
-
/**
|
|
151
|
-
* Return jobs to `queued`. When `reason` is `refused`, the runner MUST be
|
|
152
|
-
* added to each job's `refusedBy`.
|
|
153
|
-
*/
|
|
154
|
-
release(args: ReleaseArgs): Promise<string[]>;
|
|
155
|
-
/**
|
|
156
|
-
* Move every claimable-but-unclaimed job past its TTL, and every job past
|
|
157
|
-
* its absolute deadline, to `expired`. Returns what changed.
|
|
158
|
-
*
|
|
159
|
-
* Called opportunistically by the handlers; an adapter MAY also run it on a
|
|
160
|
-
* schedule. It MUST be idempotent — firing twice is always safe.
|
|
161
|
-
*/
|
|
162
|
-
expireDue(now: number): Promise<JobRecord[]>;
|
|
163
|
-
/** Cancel a job by app request. Returns the job, or null if unknown. */
|
|
164
|
-
cancel(jobId: string, now: number): Promise<JobRecord | null>;
|
|
165
|
-
/** Jobs a runner currently holds — used to build the heartbeat cancel list. */
|
|
166
|
-
listClaimedBy(runnerId: string): Promise<JobRecord[]>;
|
|
167
|
-
/** Jobs awaiting cancellation that a given runner holds. */
|
|
168
|
-
listCancelRequests(runnerId: string): Promise<string[]>;
|
|
169
|
-
}
|
|
170
|
-
interface ClaimArgs {
|
|
171
|
-
readonly runnerId: string;
|
|
172
|
-
/** The runner's owner, for audience matching. */
|
|
173
|
-
readonly runnerOwner: string;
|
|
174
|
-
readonly capabilities: readonly Capability[];
|
|
175
|
-
readonly max: number;
|
|
176
|
-
readonly leaseMs: number;
|
|
177
|
-
readonly now: number;
|
|
178
|
-
}
|
|
179
|
-
interface RenewArgs {
|
|
180
|
-
readonly runnerId: string;
|
|
181
|
-
readonly jobIds: readonly string[];
|
|
182
|
-
readonly leaseMs: number;
|
|
183
|
-
readonly now: number;
|
|
184
|
-
}
|
|
185
|
-
interface RenewResult {
|
|
186
|
-
readonly renewed: readonly {
|
|
187
|
-
jobId: string;
|
|
188
|
-
expiresAt: number;
|
|
189
|
-
}[];
|
|
190
|
-
/** Jobs the runner claimed to hold but no longer does. */
|
|
191
|
-
readonly lost: readonly string[];
|
|
192
|
-
}
|
|
193
|
-
interface CompleteArgs {
|
|
194
|
-
readonly jobId: string;
|
|
195
|
-
readonly runnerId: string;
|
|
196
|
-
readonly outcome: JobOutcome;
|
|
197
|
-
readonly provenance: ResultProvenance;
|
|
198
|
-
readonly now: number;
|
|
199
|
-
}
|
|
200
|
-
interface CompleteResult {
|
|
201
|
-
/** False when this submission lost an idempotency race or the lease was gone. */
|
|
202
|
-
readonly accepted: boolean;
|
|
203
|
-
readonly job: JobRecord | null;
|
|
204
|
-
}
|
|
205
|
-
interface ReleaseArgs {
|
|
206
|
-
readonly runnerId: string;
|
|
207
|
-
readonly jobIds: readonly string[];
|
|
208
|
-
readonly reason: "shutdown" | "pause" | "revoked" | "backend-down" | "refused";
|
|
209
|
-
readonly now: number;
|
|
210
|
-
}
|
|
211
|
-
/** Runner registry and pairing state. */
|
|
212
|
-
interface RunnerStore {
|
|
213
|
-
/** Begin a device-code pairing. */
|
|
214
|
-
createPairing(record: PairingRecord): Promise<void>;
|
|
215
|
-
getPairingByDeviceCodeHash(hash: string): Promise<PairingRecord | null>;
|
|
216
|
-
getPairingByUserCode(userCode: string): Promise<PairingRecord | null>;
|
|
217
|
-
/**
|
|
218
|
-
* Approve a pairing on behalf of an authenticated user, creating the
|
|
219
|
-
* runner. The `owner` MUST come from the approving user's own session — a
|
|
220
|
-
* daemon can never assert who it is ({@link MUSTS.PAIR_ONE_USER}).
|
|
221
|
-
*/
|
|
222
|
-
approvePairing(args: ApproveArgs): Promise<RunnerRecord>;
|
|
223
|
-
denyPairing(userCode: string, now: number): Promise<void>;
|
|
224
|
-
/** Clear the one-shot token after the daemon collects it. */
|
|
225
|
-
consumePairingToken(deviceCodeHash: string): Promise<void>;
|
|
226
|
-
getRunnerByTokenHash(hash: string): Promise<RunnerRecord | null>;
|
|
227
|
-
getRunner(runnerId: string): Promise<RunnerRecord | null>;
|
|
228
|
-
/** Record a heartbeat: capabilities, version, pause state, liveness. */
|
|
229
|
-
touchRunner(args: TouchArgs): Promise<RunnerRecord | null>;
|
|
230
|
-
/** Revoke a runner. Once revoked, never un-revoked. */
|
|
231
|
-
revokeRunner(runnerId: string, now: number): Promise<void>;
|
|
232
|
-
/** Live runners for an owner — used by the no-runner signal. */
|
|
233
|
-
listRunners(owner?: string): Promise<RunnerRecord[]>;
|
|
234
|
-
}
|
|
235
|
-
interface ApproveArgs {
|
|
236
|
-
readonly userCode: string;
|
|
237
|
-
/** From the approving user's session, never from the daemon. */
|
|
238
|
-
readonly owner: string;
|
|
239
|
-
readonly runnerId: string;
|
|
240
|
-
readonly runnerToken: string;
|
|
241
|
-
readonly tokenHash: string;
|
|
242
|
-
readonly now: number;
|
|
243
|
-
}
|
|
244
|
-
interface TouchArgs {
|
|
245
|
-
readonly runnerId: string;
|
|
246
|
-
readonly capabilities: readonly Capability[];
|
|
247
|
-
readonly daemonVersion: string;
|
|
248
|
-
readonly paused: boolean;
|
|
249
|
-
readonly now: number;
|
|
250
|
-
}
|
|
251
|
-
/** A store providing both halves. Most adapters implement one object. */
|
|
252
|
-
interface ByollmStore extends JobStore, RunnerStore {
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export type { ApproveArgs as A, ByollmStore as B, ClaimArgs as C, EnqueueInput as E, JobRecord as J, PairingRecord as P, RunnerRecord as R, TouchArgs as T, RenewArgs as a, RenewResult as b, CompleteArgs as c, CompleteResult as d, ReleaseArgs as e, JobStore as f, RunnerStore as g };
|