@byollm/server 0.1.0-alpha.8 → 0.1.0-alpha.80
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 +166 -9
- package/dist/{chunk-4NIHWQAT.js → chunk-DG6XZQU3.js} +76 -39
- package/dist/chunk-DG6XZQU3.js.map +1 -0
- package/dist/{chunk-7RKXFPBZ.js → chunk-I3ER27QG.js} +19 -5
- package/dist/chunk-I3ER27QG.js.map +1 -0
- package/dist/{delivery-36nIe-b3.d.ts → delivery-CaGbp0Tc.d.ts} +35 -5
- package/dist/{handlers-DgW0QNTf.d.ts → handlers-CTV3Jc6Q.d.ts} +2 -2
- package/dist/index.d.ts +92 -24
- package/dist/index.js +316 -98
- package/dist/index.js.map +1 -1
- package/dist/next.d.ts +2 -2
- package/dist/next.js +1 -1
- package/dist/{store-Cj5b6A9j.d.ts → store-Cx2_bck1.d.ts} +130 -18
- package/dist/supabase/index.d.ts +2 -2
- package/dist/supabase/index.js +62 -29
- package/dist/supabase/index.js.map +1 -1
- package/package.json +2 -2
- 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/supabase/migrations/20260824000000_one_vocabulary.sql +109 -0
- package/supabase/migrations/20260825000000_job_service.sql +20 -0
- package/supabase/migrations/20260827000000_job_purpose.sql +36 -0
- package/dist/chunk-4NIHWQAT.js.map +0 -1
- package/dist/chunk-7RKXFPBZ.js.map +0 -1
|
@@ -41,15 +41,17 @@ var PollingDelivery = class {
|
|
|
41
41
|
options.signal?.throwIfAborted();
|
|
42
42
|
const current = await this.#deps.read(jobId);
|
|
43
43
|
if (current && isTerminalState(current.state)) return current;
|
|
44
|
-
const availability = await this.#deps.availability(jobId);
|
|
45
|
-
if (availability.available || availability.blocked) {
|
|
44
|
+
const availability = await this.#deps.availability?.(jobId);
|
|
45
|
+
if (availability === void 0 || availability.available || availability.blocked) {
|
|
46
46
|
noRunnerSince = null;
|
|
47
47
|
} else {
|
|
48
48
|
noRunnerSince ??= now();
|
|
49
49
|
if (now() - noRunnerSince >= graceMs) {
|
|
50
50
|
const reason = availability.reason ?? "no-runner-online";
|
|
51
51
|
const substitute = await options.onNoRunner?.(reason);
|
|
52
|
-
if (substitute)
|
|
52
|
+
if (substitute !== void 0) {
|
|
53
|
+
return labelFallback(jobId, substitute);
|
|
54
|
+
}
|
|
53
55
|
throw new NoRunnerAvailableError(jobId, reason);
|
|
54
56
|
}
|
|
55
57
|
}
|
|
@@ -63,10 +65,22 @@ var PollingDelivery = class {
|
|
|
63
65
|
function isTerminalState(state) {
|
|
64
66
|
return state === "ok" || state === "error" || state === "canceled" || state === "expired";
|
|
65
67
|
}
|
|
68
|
+
function labelFallback(jobId, substitute) {
|
|
69
|
+
if (typeof substitute === "string") {
|
|
70
|
+
return {
|
|
71
|
+
jobId,
|
|
72
|
+
state: "ok",
|
|
73
|
+
outcome: { outcome: "ok", text: substitute },
|
|
74
|
+
fallback: true
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return { ...substitute, fallback: true };
|
|
78
|
+
}
|
|
66
79
|
|
|
67
80
|
export {
|
|
68
81
|
NoRunnerAvailableError,
|
|
69
82
|
ResultTimeoutError,
|
|
70
|
-
PollingDelivery
|
|
83
|
+
PollingDelivery,
|
|
84
|
+
labelFallback
|
|
71
85
|
};
|
|
72
|
-
//# sourceMappingURL=chunk-
|
|
86
|
+
//# sourceMappingURL=chunk-I3ER27QG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/delivery.ts"],"sourcesContent":["import type { DeliveredResult } from \"@byollm/protocol\";\n\n/** Why a wait ended without a result. */\nexport class NoRunnerAvailableError extends Error {\n override readonly name = \"NoRunnerAvailableError\";\n constructor(\n readonly jobId: string,\n readonly reason: string,\n ) {\n super(\n `no runner is available to execute job ${jobId} (${reason}). ` +\n `Fall back to a hosted model, or prompt the user to start their runner.`,\n );\n }\n}\n\n/** The wait exceeded its timeout while a runner was still plausibly working. */\nexport class ResultTimeoutError extends Error {\n override readonly name = \"ResultTimeoutError\";\n constructor(\n readonly jobId: string,\n readonly timeoutMs: number,\n ) {\n super(`job ${jobId} did not finish within ${String(timeoutMs)}ms`);\n }\n}\n\nexport interface WaitOptions {\n /** Give up after this long. Default 5 minutes. */\n readonly timeoutMs?: number;\n /**\n * Called instead of throwing when no runner can take the job. Return a\n * substitute and the wait resolves with it; return nothing and\n * {@link NoRunnerAvailableError} is thrown.\n *\n * **A string is enough.** It is the app's own fallback answer — a hosted\n * model's text, a cached reply — not wire data, and requiring a whole\n * `DeliveredResult` for it was ceremony that invited invented shapes. The\n * README's own example got it wrong, which is how this was found.\n *\n * **Whatever comes back is labelled `fallback: true` by the wait, not by\n * the caller** — {@link MUSTS.FALLBACK_LABELED}. Work that did not come\n * from the user's own compute must not be reportable as though it did, and\n * that stays true whether an app returns a bare string or a full record it\n * assembled itself. The stamp is applied after this function returns, so\n * there is no shape an app can hand back that hides what it is.\n */\n readonly onNoRunner?: (\n reason: string,\n ) =>\n | string\n | DeliveredResult\n | undefined\n | Promise<string | DeliveredResult | undefined>;\n /** Abort the wait. */\n readonly signal?: AbortSignal;\n}\n\n/**\n * How an app learns a job finished.\n *\n * byollm_003 Rev 1 is explicit that this is a *channel* — webhook, Realtime\n * subscription, or poll — and never an implied in-request `await`. The\n * polling implementation below is the portable default; the Supabase adapter\n * substitutes Realtime for the same interface.\n */\nexport interface ResultDelivery {\n waitFor(jobId: string, options?: WaitOptions): Promise<DeliveredResult>;\n}\n\nexport interface PollingDeliveryDeps {\n /** Current state of the job, or null if unknown. */\n readonly read: (jobId: string) => Promise<DeliveredResult | null>;\n /**\n * Whether a runner could still take this job — **when there is anybody to\n * ask.**\n *\n * Optional since alpha.66, and its absence is the answer rather than a\n * missing dependency. On the cloud lane nothing writes runners into this\n * site's store: devices pair with the relay, so the question has no local\n * answer and `runnerAvailability` refuses to invent one.\n *\n * The refusal was correct and it landed in a loop that asked every 500ms.\n * `job.result()` threw on its first poll for every cloud-lane site — a\n * refusal aimed at outsiders that our own delivery tripped over.\n *\n * Not fixed by catching the throw here. That is a swallowed error in\n * costume, and a catch wide enough to hold it would also eat a store that\n * had genuinely gone away. The instrument is simply not handed over on a\n * lane where it cannot see, and this loop does not ask a question nobody\n * can answer.\n */\n readonly availability?: (\n jobId: string,\n ) => Promise<{ available: boolean; reason?: string; blocked: boolean }>;\n readonly sleep?: (ms: number) => Promise<void>;\n /**\n * Injectable clock. It must advance in step with {@link sleep}: a test that\n * stubs one and not the other gets a loop whose grace window never elapses.\n */\n readonly now?: () => number;\n /**\n * How long a sustained no-runner signal must persist before it is believed.\n * Defaults to {@link NO_RUNNER_GRACE_MS}.\n */\n readonly graceMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60_000;\nconst POLL_INTERVAL_MS = 500;\n/**\n * How long to let a job sit with no available runner before giving up.\n *\n * Not zero: a daemon restarting, or one whose heartbeat is momentarily late,\n * would otherwise fail every job in flight. The signal has to be sustained\n * before it is believed.\n */\nconst NO_RUNNER_GRACE_MS = 10_000;\n\nconst defaultSleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * The portable delivery channel: poll the store until the job is terminal.\n *\n * Correct everywhere and adequate for most apps. An adapter with a push\n * channel should replace it — see the Supabase adapter's Realtime delivery.\n */\nexport class PollingDelivery implements ResultDelivery {\n readonly #deps: PollingDeliveryDeps;\n\n constructor(deps: PollingDeliveryDeps) {\n this.#deps = deps;\n }\n\n async waitFor(\n jobId: string,\n options: WaitOptions = {},\n ): Promise<DeliveredResult> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const sleep = this.#deps.sleep ?? defaultSleep;\n const now = this.#deps.now ?? Date.now;\n const graceMs = this.#deps.graceMs ?? NO_RUNNER_GRACE_MS;\n const started = now();\n let noRunnerSince: number | null = null;\n\n for (;;) {\n options.signal?.throwIfAborted();\n\n const current = await this.#deps.read(jobId);\n if (current && isTerminalState(current.state)) return current;\n\n /**\n * The no-runner signal, when this deployment has one.\n *\n * With no instrument there is no sustained-absence signal and no\n * `NoRunnerAvailableError` — the wait ends when the job reaches a\n * terminal state or the timeout does. That is the honest behaviour on\n * the cloud lane, where an unsatisfiable slot is refused at enqueue and\n * a job with nowhere to run expires, both of which arrive through\n * `read` as states rather than as guesses made here.\n */\n const availability = await this.#deps.availability?.(jobId);\n if (\n availability === undefined ||\n availability.available ||\n availability.blocked\n ) {\n // `blocked` means the job is waiting on a dependency, which is not the\n // same event as \"nobody can run this\" ({@link MUSTS.NO_RUNNER_SIGNAL}).\n noRunnerSince = null;\n } else {\n noRunnerSince ??= now();\n if (now() - noRunnerSince >= graceMs) {\n const reason = availability.reason ?? \"no-runner-online\";\n const substitute = await options.onNoRunner?.(reason);\n if (substitute !== undefined) {\n return labelFallback(jobId, substitute);\n }\n throw new NoRunnerAvailableError(jobId, reason);\n }\n }\n\n if (now() - started >= timeoutMs) {\n throw new ResultTimeoutError(jobId, timeoutMs);\n }\n await sleep(POLL_INTERVAL_MS);\n }\n }\n}\n\nfunction isTerminalState(state: string): boolean {\n return (\n state === \"ok\" ||\n state === \"error\" ||\n state === \"canceled\" ||\n state === \"expired\"\n );\n}\n\n/**\n * Turn an app's fallback into a delivered result, marked as one.\n *\n * Exported because there are two delivery channels — polling here, Supabase\n * Realtime next door — and a label applied by one of them is a label an app\n * gets or does not get depending on which store it chose. That is exactly the\n * kind of divergence a \"delivery adapter must not change what a result means\"\n * rule exists to prevent.\n *\n * Two jobs, and the second is the one that matters. A string becomes the\n * obvious record — that is the sugar. Everything, string or record, gets\n * `fallback: true` — that is {@link MUSTS.FALLBACK_LABELED}, and it is\n * applied here rather than trusted from the caller because an app that\n * assembled its own record could otherwise return something indistinguishable\n * from a runner's answer. Spreading the caller's object first and the flag\n * second is deliberate: a supplied `fallback` cannot overwrite it.\n */\nexport function labelFallback(\n jobId: string,\n substitute: string | DeliveredResult,\n): DeliveredResult {\n if (typeof substitute === \"string\") {\n return {\n jobId,\n state: \"ok\",\n outcome: { outcome: \"ok\", text: substitute },\n fallback: true,\n };\n }\n return { ...substitute, fallback: true };\n}\n"],"mappings":";AAGO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAEhD,YACW,OACA,QACT;AACA;AAAA,MACE,yCAAyC,KAAK,KAAK,MAAM;AAAA,IAE3D;AANS;AACA;AAAA,EAMX;AAAA,EAPW;AAAA,EACA;AAAA,EAHO,OAAO;AAU3B;AAGO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAE5C,YACW,OACA,WACT;AACA,UAAM,OAAO,KAAK,0BAA0B,OAAO,SAAS,CAAC,IAAI;AAHxD;AACA;AAAA,EAGX;AAAA,EAJW;AAAA,EACA;AAAA,EAHO,OAAO;AAO3B;AAmFA,IAAM,qBAAqB,IAAI;AAC/B,IAAM,mBAAmB;AAQzB,IAAM,qBAAqB;AAE3B,IAAM,eAAe,CAAC,OACpB,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAQ3C,IAAM,kBAAN,MAAgD;AAAA,EAC5C;AAAA,EAET,YAAY,MAA2B;AACrC,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,MAAM,QACJ,OACA,UAAuB,CAAC,GACE;AAC1B,UAAM,YAAY,QAAQ,aAAa;AACvC,UAAM,QAAQ,KAAK,MAAM,SAAS;AAClC,UAAM,MAAM,KAAK,MAAM,OAAO,KAAK;AACnC,UAAM,UAAU,KAAK,MAAM,WAAW;AACtC,UAAM,UAAU,IAAI;AACpB,QAAI,gBAA+B;AAEnC,eAAS;AACP,cAAQ,QAAQ,eAAe;AAE/B,YAAM,UAAU,MAAM,KAAK,MAAM,KAAK,KAAK;AAC3C,UAAI,WAAW,gBAAgB,QAAQ,KAAK,EAAG,QAAO;AAYtD,YAAM,eAAe,MAAM,KAAK,MAAM,eAAe,KAAK;AAC1D,UACE,iBAAiB,UACjB,aAAa,aACb,aAAa,SACb;AAGA,wBAAgB;AAAA,MAClB,OAAO;AACL,0BAAkB,IAAI;AACtB,YAAI,IAAI,IAAI,iBAAiB,SAAS;AACpC,gBAAM,SAAS,aAAa,UAAU;AACtC,gBAAM,aAAa,MAAM,QAAQ,aAAa,MAAM;AACpD,cAAI,eAAe,QAAW;AAC5B,mBAAO,cAAc,OAAO,UAAU;AAAA,UACxC;AACA,gBAAM,IAAI,uBAAuB,OAAO,MAAM;AAAA,QAChD;AAAA,MACF;AAEA,UAAI,IAAI,IAAI,WAAW,WAAW;AAChC,cAAM,IAAI,mBAAmB,OAAO,SAAS;AAAA,MAC/C;AACA,YAAM,MAAM,gBAAgB;AAAA,IAC9B;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,OAAwB;AAC/C,SACE,UAAU,QACV,UAAU,WACV,UAAU,cACV,UAAU;AAEd;AAmBO,SAAS,cACd,OACA,YACiB;AACjB,MAAI,OAAO,eAAe,UAAU;AAClC,WAAO;AAAA,MACL;AAAA,MACA,OAAO;AAAA,MACP,SAAS,EAAE,SAAS,MAAM,MAAM,WAAW;AAAA,MAC3C,UAAU;AAAA,IACZ;AAAA,EACF;AACA,SAAO,EAAE,GAAG,YAAY,UAAU,KAAK;AACzC;","names":[]}
|
|
@@ -19,10 +19,22 @@ interface WaitOptions {
|
|
|
19
19
|
readonly timeoutMs?: number;
|
|
20
20
|
/**
|
|
21
21
|
* Called instead of throwing when no runner can take the job. Return a
|
|
22
|
-
* substitute
|
|
23
|
-
*
|
|
22
|
+
* substitute and the wait resolves with it; return nothing and
|
|
23
|
+
* {@link NoRunnerAvailableError} is thrown.
|
|
24
|
+
*
|
|
25
|
+
* **A string is enough.** It is the app's own fallback answer — a hosted
|
|
26
|
+
* model's text, a cached reply — not wire data, and requiring a whole
|
|
27
|
+
* `DeliveredResult` for it was ceremony that invited invented shapes. The
|
|
28
|
+
* README's own example got it wrong, which is how this was found.
|
|
29
|
+
*
|
|
30
|
+
* **Whatever comes back is labelled `fallback: true` by the wait, not by
|
|
31
|
+
* the caller** — {@link MUSTS.FALLBACK_LABELED}. Work that did not come
|
|
32
|
+
* from the user's own compute must not be reportable as though it did, and
|
|
33
|
+
* that stays true whether an app returns a bare string or a full record it
|
|
34
|
+
* assembled itself. The stamp is applied after this function returns, so
|
|
35
|
+
* there is no shape an app can hand back that hides what it is.
|
|
24
36
|
*/
|
|
25
|
-
readonly onNoRunner?: (reason: string) => DeliveredResult | undefined | Promise<DeliveredResult | undefined>;
|
|
37
|
+
readonly onNoRunner?: (reason: string) => string | DeliveredResult | undefined | Promise<string | DeliveredResult | undefined>;
|
|
26
38
|
/** Abort the wait. */
|
|
27
39
|
readonly signal?: AbortSignal;
|
|
28
40
|
}
|
|
@@ -40,8 +52,26 @@ interface ResultDelivery {
|
|
|
40
52
|
interface PollingDeliveryDeps {
|
|
41
53
|
/** Current state of the job, or null if unknown. */
|
|
42
54
|
readonly read: (jobId: string) => Promise<DeliveredResult | null>;
|
|
43
|
-
/**
|
|
44
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Whether a runner could still take this job — **when there is anybody to
|
|
57
|
+
* ask.**
|
|
58
|
+
*
|
|
59
|
+
* Optional since alpha.66, and its absence is the answer rather than a
|
|
60
|
+
* missing dependency. On the cloud lane nothing writes runners into this
|
|
61
|
+
* site's store: devices pair with the relay, so the question has no local
|
|
62
|
+
* answer and `runnerAvailability` refuses to invent one.
|
|
63
|
+
*
|
|
64
|
+
* The refusal was correct and it landed in a loop that asked every 500ms.
|
|
65
|
+
* `job.result()` threw on its first poll for every cloud-lane site — a
|
|
66
|
+
* refusal aimed at outsiders that our own delivery tripped over.
|
|
67
|
+
*
|
|
68
|
+
* Not fixed by catching the throw here. That is a swallowed error in
|
|
69
|
+
* costume, and a catch wide enough to hold it would also eat a store that
|
|
70
|
+
* had genuinely gone away. The instrument is simply not handed over on a
|
|
71
|
+
* lane where it cannot see, and this loop does not ask a question nobody
|
|
72
|
+
* can answer.
|
|
73
|
+
*/
|
|
74
|
+
readonly availability?: (jobId: string) => Promise<{
|
|
45
75
|
available: boolean;
|
|
46
76
|
reason?: string;
|
|
47
77
|
blocked: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StoredKeys, Endpoint } from '@byollm/protocol';
|
|
2
|
-
import { B as ByollmStore } from './store-
|
|
2
|
+
import { B as ByollmStore } from './store-Cx2_bck1.js';
|
|
3
3
|
|
|
4
4
|
/** Everything a mount needs to serve the protocol. */
|
|
5
5
|
/**
|
|
@@ -70,6 +70,6 @@ declare class ByollmHandlers {
|
|
|
70
70
|
handle(endpoint: Endpoint, body: unknown, auth: AuthContext): Promise<HandlerResult>;
|
|
71
71
|
}
|
|
72
72
|
/** The protocol version this build speaks. */
|
|
73
|
-
declare const SERVED_PROTOCOL_VERSION: "
|
|
73
|
+
declare const SERVED_PROTOCOL_VERSION: "1";
|
|
74
74
|
|
|
75
75
|
export { ByollmHandlers as B, type HandlerConfig as H, SERVED_PROTOCOL_VERSION as S, type HandlerResult as a };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { StoredKeys, JobKind, DeliveredResult, Endpoint, Capability } from '@byollm/protocol';
|
|
2
|
-
import { P as PollingDeliveryDeps, R as ResultDelivery, W as WaitOptions } from './delivery-
|
|
3
|
-
export { N as NoRunnerAvailableError, a as PollingDelivery, b as ResultTimeoutError } from './delivery-
|
|
4
|
-
import { B as ByollmStore, J as JobRecord, E as EnqueueInput, R as RunnerRecord, S as StoredJobInput, C as ClaimArgs, a as RenewArgs, b as RenewResult, A as AdoptArgs, c as CompleteArgs, d as CompleteResult, e as ReleaseArgs, P as PairingRecord, f as ApproveArgs, T as TouchArgs } from './store-
|
|
5
|
-
export { g as CompleteHolder, h as JobStore, i as RunnerStore } from './store-
|
|
6
|
-
import { H as HandlerConfig } from './handlers-
|
|
7
|
-
export { B as ByollmHandlers, a as HandlerResult, S as SERVED_PROTOCOL_VERSION } from './handlers-
|
|
1
|
+
import { StoredKeys, JobKind, Audience, DeliveredResult, Endpoint, Capability } from '@byollm/protocol';
|
|
2
|
+
import { P as PollingDeliveryDeps, R as ResultDelivery, W as WaitOptions } from './delivery-CaGbp0Tc.js';
|
|
3
|
+
export { N as NoRunnerAvailableError, a as PollingDelivery, b as ResultTimeoutError } from './delivery-CaGbp0Tc.js';
|
|
4
|
+
import { B as ByollmStore, J as JobRecord, E as EnqueueInput, R as RunnerRecord, S as StoredJobInput, C as ClaimArgs, a as RenewArgs, b as RenewResult, A as AdoptArgs, c as CompleteArgs, d as CompleteResult, e as ReleaseArgs, P as PairingRecord, f as ApproveArgs, T as TouchArgs } from './store-Cx2_bck1.js';
|
|
5
|
+
export { g as CompleteHolder, h as JobStore, i as RunnerStore } from './store-Cx2_bck1.js';
|
|
6
|
+
import { H as HandlerConfig } from './handlers-CTV3Jc6Q.js';
|
|
7
|
+
export { B as ByollmHandlers, a as HandlerResult, S as SERVED_PROTOCOL_VERSION } from './handlers-CTV3Jc6Q.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The cloud lane — cloud_004 §9.4.
|
|
@@ -49,6 +49,44 @@ interface CloudLaneOptions {
|
|
|
49
49
|
readonly fetch?: typeof fetch;
|
|
50
50
|
}
|
|
51
51
|
/** What one pump cycle did, for logging and for tests. */
|
|
52
|
+
/**
|
|
53
|
+
* A relay that could not answer this request — alpha.31.
|
|
54
|
+
*
|
|
55
|
+
* `retryable` is the whole point: a draining pod and a bad signature are both
|
|
56
|
+
* failures, and treating them alike is how a site either falls over on every
|
|
57
|
+
* deploy or stays silently disconnected for a week.
|
|
58
|
+
*/
|
|
59
|
+
declare class RelayUnavailable extends Error {
|
|
60
|
+
readonly retryable: boolean;
|
|
61
|
+
/** The protocol's own code, when the relay sent one. */
|
|
62
|
+
readonly code: string;
|
|
63
|
+
constructor(message: string, retryable: boolean, code: string);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The job was not queued, and waiting will not change that.
|
|
67
|
+
*
|
|
68
|
+
* Distinct from {@link RelayUnavailable} because it is the opposite situation:
|
|
69
|
+
* the relay answered, promptly and correctly, and the answer is that this job
|
|
70
|
+
* has nowhere to go. Catching "the relay is down" to handle "nobody has chosen
|
|
71
|
+
* a model" would retry forever against a fact.
|
|
72
|
+
*
|
|
73
|
+
* Two codes, and they belong to two different people.
|
|
74
|
+
*
|
|
75
|
+
* `purpose-not-declared` is the site's own manifest. It names the purpose and
|
|
76
|
+
* the remedy, because a developer reading their own logs is entitled to both
|
|
77
|
+
* and neither says anything about a person.
|
|
78
|
+
*
|
|
79
|
+
* `slot-unsatisfiable` is the person's own dashboard, and says only that.
|
|
80
|
+
* Which service, whose device, whether one exists at all — none of it travels,
|
|
81
|
+
* and the sentence is the same for everybody. A site learns *that* a slot
|
|
82
|
+
* cannot be satisfied, which is exactly what the README has always promised
|
|
83
|
+
* and what this class finally delivers.
|
|
84
|
+
*/
|
|
85
|
+
declare class EnqueueRefused extends Error {
|
|
86
|
+
/** `purpose-not-declared` or `slot-unsatisfiable`. */
|
|
87
|
+
readonly code: string;
|
|
88
|
+
constructor(message: string, code: string);
|
|
89
|
+
}
|
|
52
90
|
interface PumpReport {
|
|
53
91
|
/** Jobs sealed to a claiming device this cycle. */
|
|
54
92
|
readonly sealed: string[];
|
|
@@ -62,6 +100,20 @@ interface PumpReport {
|
|
|
62
100
|
* exactly the case `awaiting-payload` exists to bound.
|
|
63
101
|
*/
|
|
64
102
|
readonly refused: string[];
|
|
103
|
+
/**
|
|
104
|
+
* Why this cycle stopped early, when it did — alpha.31.
|
|
105
|
+
*
|
|
106
|
+
* A relay can legitimately say "ask me later": a pod draining through its
|
|
107
|
+
* `preStop` window answers `503 not-ready` to every routed call, and that
|
|
108
|
+
* happens on **every deploy**. Before this existed the lane read the body
|
|
109
|
+
* of that answer, found no `jobs` in it, and threw `TypeError: finished.jobs
|
|
110
|
+
* is not iterable` — a site falling over because its relay was polite.
|
|
111
|
+
*
|
|
112
|
+
* Absent on an ordinary cycle. Present, with the reason, when the lane
|
|
113
|
+
* deferred: a site that quietly did nothing and a site that was told to wait
|
|
114
|
+
* must not look the same in a log.
|
|
115
|
+
*/
|
|
116
|
+
readonly deferred?: string;
|
|
65
117
|
}
|
|
66
118
|
declare class CloudLane {
|
|
67
119
|
#private;
|
|
@@ -79,6 +131,19 @@ declare class CloudLane {
|
|
|
79
131
|
* field on `JobStub` to put one in.
|
|
80
132
|
*/
|
|
81
133
|
publish(record: JobRecord): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Withdraw a job at the relay — cloud_008 §2.2.
|
|
136
|
+
*
|
|
137
|
+
* `app.cancel()` marks the site's own row terminal, which stops the *next*
|
|
138
|
+
* seal. It cannot stop a device that is already running the work, because
|
|
139
|
+
* on this lane the site is not the upstream: only the relay talks to the
|
|
140
|
+
* daemon, and it answered `cancel: []` unconditionally.
|
|
141
|
+
*
|
|
142
|
+
* So the cancellation has to travel. The relay marks the job, stops
|
|
143
|
+
* offering it, and names it to the holding device at its next heartbeat —
|
|
144
|
+
* the same path the direct plane has always had, arriving one hop later.
|
|
145
|
+
*/
|
|
146
|
+
cancel(jobId: string): Promise<void>;
|
|
82
147
|
/**
|
|
83
148
|
* One cycle: seal for anything claimed, collect anything finished.
|
|
84
149
|
*
|
|
@@ -91,7 +156,18 @@ declare class CloudLane {
|
|
|
91
156
|
}
|
|
92
157
|
|
|
93
158
|
/** Why a job cannot presently run. */
|
|
94
|
-
type NoRunnerReason = "no-runner-paired" | "no-runner-online" | "no-matching-capability" | "audience-admits-nobody"
|
|
159
|
+
type NoRunnerReason = "no-runner-paired" | "no-runner-online" | "no-matching-capability" | "audience-admits-nobody"
|
|
160
|
+
/**
|
|
161
|
+
* The owner's default for this kind can never serve *this* requester —
|
|
162
|
+
* byollm_016's defaults-meet-audiences corner.
|
|
163
|
+
*
|
|
164
|
+
* The specimen: a default of `claude-cli`, self-locked by
|
|
165
|
+
* `SUBSCRIPTION_SELF_LOCK`, and a team member's unselected job. It resolves
|
|
166
|
+
* to something that will never run it. Reported rather than left to time
|
|
167
|
+
* out, because a wait that can never end is indistinguishable from one that
|
|
168
|
+
* has not ended yet, and only one of them is worth waiting through.
|
|
169
|
+
*/
|
|
170
|
+
| "default-unusable";
|
|
95
171
|
/**
|
|
96
172
|
* The no-runner signal (byollm_001 Rev 1 §D).
|
|
97
173
|
*
|
|
@@ -110,7 +186,7 @@ interface RunnerAvailability {
|
|
|
110
186
|
interface AvailabilityQuery {
|
|
111
187
|
readonly kind: JobKind;
|
|
112
188
|
readonly owner: string;
|
|
113
|
-
readonly audience?:
|
|
189
|
+
readonly audience?: Audience;
|
|
114
190
|
readonly audienceAllow?: readonly string[];
|
|
115
191
|
}
|
|
116
192
|
interface ByollmAppOptions {
|
|
@@ -163,14 +239,6 @@ interface JobHandle {
|
|
|
163
239
|
/** Ask the runner to stop. */
|
|
164
240
|
cancel(): Promise<void>;
|
|
165
241
|
}
|
|
166
|
-
/**
|
|
167
|
-
* The app-facing half of `@byollm/server`.
|
|
168
|
-
*
|
|
169
|
-
* The daemon talks to {@link ByollmHandlers}; the app talks to this. Keeping
|
|
170
|
-
* them separate is what makes "one door per state write" hold — an app
|
|
171
|
-
* enqueues and cancels through these methods and never writes job rows by
|
|
172
|
-
* hand.
|
|
173
|
-
*/
|
|
174
242
|
declare class ByollmApp {
|
|
175
243
|
#private;
|
|
176
244
|
/** Present only in the cloud lane; the site's side of the relay. */
|
|
@@ -183,7 +251,7 @@ declare class ByollmApp {
|
|
|
183
251
|
* result comes back marked untrusted (see {@link ByollmApp.result}), and
|
|
184
252
|
* the app is obliged to disclose that to whoever reads it.
|
|
185
253
|
*/
|
|
186
|
-
enqueue(input: EnqueueInput): Promise<JobHandle>;
|
|
254
|
+
enqueue<K extends JobKind>(input: EnqueueInput<K>): Promise<JobHandle>;
|
|
187
255
|
/** Read a job's current state. */
|
|
188
256
|
job(jobId: string): Promise<JobRecord | null>;
|
|
189
257
|
/**
|
|
@@ -192,7 +260,7 @@ declare class ByollmApp {
|
|
|
192
260
|
* Check `provenance.untrusted` before rendering. It is true for every
|
|
193
261
|
* `named`/`public` job, because that text came from someone else's machine
|
|
194
262
|
* and the app must not present it as its own AI's answer
|
|
195
|
-
* ({@link MUSTS.
|
|
263
|
+
* ({@link MUSTS.PROVENANCE_NAMES_DEVICE}).
|
|
196
264
|
*/
|
|
197
265
|
result(jobId: string): Promise<DeliveredResult | null>;
|
|
198
266
|
/** Ask a runner to stop. Queued jobs cancel at once; held jobs at the next heartbeat. */
|
|
@@ -315,8 +383,6 @@ declare function formatSiteKeys(keys: StoredKeys): string;
|
|
|
315
383
|
|
|
316
384
|
/** A device code: the secret the daemon polls with. Never shown to a user. */
|
|
317
385
|
declare function generateDeviceCode(): string;
|
|
318
|
-
/** A runner bearer token. */
|
|
319
|
-
declare function generateRunnerToken(): string;
|
|
320
386
|
/** A runner id. */
|
|
321
387
|
declare function generateRunnerId(): string;
|
|
322
388
|
/** A job id. */
|
|
@@ -366,14 +432,16 @@ declare class MemoryStore implements ByollmStore {
|
|
|
366
432
|
expireDue(now: number): Promise<JobRecord[]>;
|
|
367
433
|
cancel(jobId: string, now: number): Promise<JobRecord | null>;
|
|
368
434
|
listClaimedBy(runnerId: string): Promise<JobRecord[]>;
|
|
369
|
-
listCancelRequests(runnerId: string): Promise<
|
|
435
|
+
listCancelRequests(runnerId: string): Promise<{
|
|
436
|
+
jobId: string;
|
|
437
|
+
leaseId: string;
|
|
438
|
+
}[]>;
|
|
370
439
|
createPairing(record: PairingRecord): Promise<void>;
|
|
371
440
|
getPairingByDeviceCodeHash(hash: string): Promise<PairingRecord | null>;
|
|
372
441
|
getPairingByUserCode(userCode: string): Promise<PairingRecord | null>;
|
|
373
442
|
approvePairing(args: ApproveArgs): Promise<RunnerRecord>;
|
|
374
443
|
denyPairing(userCode: string, _now: number): Promise<void>;
|
|
375
444
|
consumePairingToken(deviceCodeHash: string): Promise<void>;
|
|
376
|
-
getRunnerByTokenHash(hash: string): Promise<RunnerRecord | null>;
|
|
377
445
|
getRunner(runnerId: string): Promise<RunnerRecord | null>;
|
|
378
446
|
touchRunner(args: TouchArgs): Promise<RunnerRecord | null>;
|
|
379
447
|
revokeRunner(runnerId: string, now: number): Promise<void>;
|
|
@@ -384,4 +452,4 @@ declare class MemoryStore implements ByollmStore {
|
|
|
384
452
|
/** The capability that would serve a kind, if any. */
|
|
385
453
|
declare function capabilityFor(capabilities: readonly Capability[], kind: string): Capability | undefined;
|
|
386
454
|
|
|
387
|
-
export { AdoptArgs, ApproveArgs, type AvailabilityQuery, ByollmApp, type ByollmAppOptions, ByollmStore, ClaimArgs, CloudLane, type CloudLaneOptions, CompleteArgs, CompleteResult, EnqueueInput, HandlerConfig, type JobHandle, JobRecord, MemoryStore, type MemoryStoreOptions, type NoRunnerReason, PairingRecord, PollingDeliveryDeps, type PumpReport, ReleaseArgs, RenewArgs, RenewResult, ResultDelivery, type RunnerAvailability, RunnerRecord, TouchArgs, WaitOptions, capabilityFor, createFetchHandler, formatSiteKeys, generateDeviceCode, generateJobId, generateRunnerId,
|
|
455
|
+
export { AdoptArgs, ApproveArgs, type AvailabilityQuery, ByollmApp, type ByollmAppOptions, ByollmStore, ClaimArgs, CloudLane, type CloudLaneOptions, CompleteArgs, CompleteResult, EnqueueInput, EnqueueRefused, HandlerConfig, type JobHandle, JobRecord, MemoryStore, type MemoryStoreOptions, type NoRunnerReason, PairingRecord, PollingDeliveryDeps, type PumpReport, RelayUnavailable, ReleaseArgs, RenewArgs, RenewResult, ResultDelivery, type RunnerAvailability, RunnerRecord, TouchArgs, WaitOptions, capabilityFor, createFetchHandler, formatSiteKeys, generateDeviceCode, generateJobId, generateRunnerId, generateSiteKeys, generateUserCode, hashSecret, normalizeUserCode, routeEndpoint, secretsMatch, signatureFrom, siteKeysFromEnv };
|