@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
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { JobKind, JobPayload, Audience, SealedEnvelope, SizeClass, JobState, Lease, JobOutcome, ResultProvenance, Capability, PublicIdentity } 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
|
+
/**
|
|
14
|
+
* The work, sealed to this site's own encryption key (byollm_009 §10).
|
|
15
|
+
*
|
|
16
|
+
* The store never holds plaintext. The app sees plaintext at enqueue and at
|
|
17
|
+
* result because the app *is* the endpoint; everything in between —
|
|
18
|
+
* database, backups, log aggregators, a support engineer with read access —
|
|
19
|
+
* sees ciphertext.
|
|
20
|
+
*
|
|
21
|
+
* This is not protection from the application the user deliberately sent
|
|
22
|
+
* their work to. It is protection from everything the application's storage
|
|
23
|
+
* touches, which is a longer list than most people picture.
|
|
24
|
+
*/
|
|
25
|
+
readonly envelope: SealedEnvelope;
|
|
26
|
+
/** Fixed at enqueue, where the plaintext is. */
|
|
27
|
+
readonly sizeClass: SizeClass;
|
|
28
|
+
readonly audience: Audience;
|
|
29
|
+
/** The app's id for the user who enqueued it. */
|
|
30
|
+
readonly owner: string;
|
|
31
|
+
/** Server-side restriction on which runner owners may take a `named` job. */
|
|
32
|
+
readonly audienceAllow: readonly string[] | undefined;
|
|
33
|
+
/** Job ids that must all be `ok` before this becomes claimable. */
|
|
34
|
+
readonly dependsOn: readonly string[];
|
|
35
|
+
readonly state: JobState;
|
|
36
|
+
readonly lease: Lease | null;
|
|
37
|
+
/**
|
|
38
|
+
* The grant that recorded this job's result — cloud_008 §3.6.
|
|
39
|
+
*
|
|
40
|
+
* Kept after `lease` is nulled, because "who finished this" outlives "who
|
|
41
|
+
* holds this" and the two are asked for different reasons. It is what lets
|
|
42
|
+
* a replay from the device that finished the job be answered *as a
|
|
43
|
+
* duplicate* rather than as a stale lease — and lets a replay from any
|
|
44
|
+
* other device be refused exactly as it would be for a job that is not
|
|
45
|
+
* terminal, so a job id is not a terminality probe.
|
|
46
|
+
*/
|
|
47
|
+
readonly completedByLeaseId: string | null;
|
|
48
|
+
readonly createdAt: number;
|
|
49
|
+
/**
|
|
50
|
+
* When the job became claimable — enqueue time for a job with no
|
|
51
|
+
* dependencies, or the moment its last dependency reached `ok`.
|
|
52
|
+
*
|
|
53
|
+
* **The TTL clock starts here, not at `createdAt`.** Starting it at enqueue
|
|
54
|
+
* would expire a dependent job for the crime of waiting on a slow
|
|
55
|
+
* dependency (byollm_001 Rev 1 §D, TTL clock resolved in build review).
|
|
56
|
+
* `null` means still blocked.
|
|
57
|
+
*/
|
|
58
|
+
readonly claimableAt: number | null;
|
|
59
|
+
/** How long an unclaimed job may wait once claimable. */
|
|
60
|
+
readonly ttlMs: number;
|
|
61
|
+
/** Optional absolute deadline, independent of the TTL. */
|
|
62
|
+
readonly deadlineAt: number | null;
|
|
63
|
+
/**
|
|
64
|
+
* Runners that released this job with reason `refused` — their local
|
|
65
|
+
* allowlist declined it. Never offered to them again
|
|
66
|
+
* ({@link MUSTS.REFUSAL_NOT_REOFFERED}).
|
|
67
|
+
*/
|
|
68
|
+
readonly refusedBy: readonly string[];
|
|
69
|
+
/** How many times this job has been claimed, including lease-expiry retries. */
|
|
70
|
+
readonly attempts: number;
|
|
71
|
+
readonly outcome: JobOutcome | null;
|
|
72
|
+
readonly provenance: ResultProvenance | null;
|
|
73
|
+
readonly updatedAt: number;
|
|
74
|
+
}
|
|
75
|
+
/** A paired daemon as the server stores it. */
|
|
76
|
+
interface RunnerRecord {
|
|
77
|
+
readonly id: string;
|
|
78
|
+
/** The app's id for the user this runner is bound to — exactly one. */
|
|
79
|
+
readonly owner: string;
|
|
80
|
+
readonly label: string;
|
|
81
|
+
readonly platform: "darwin" | "linux" | "win32";
|
|
82
|
+
readonly daemonVersion: string;
|
|
83
|
+
readonly capabilities: readonly Capability[];
|
|
84
|
+
readonly paused: boolean;
|
|
85
|
+
/** Set once; a revoked runner never un-revokes. */
|
|
86
|
+
readonly revokedAt: number | null;
|
|
87
|
+
readonly lastHeartbeatAt: number;
|
|
88
|
+
readonly createdAt: number;
|
|
89
|
+
/**
|
|
90
|
+
* The device's pinned public keys. What later signatures verify against —
|
|
91
|
+
* a runner id names a machine, this proves it.
|
|
92
|
+
*/
|
|
93
|
+
readonly device: PublicIdentity;
|
|
94
|
+
}
|
|
95
|
+
/** An in-flight device-code pairing. */
|
|
96
|
+
interface PairingRecord {
|
|
97
|
+
/** SHA-256 of the device code. The code itself is never stored. */
|
|
98
|
+
readonly deviceCodeHash: string;
|
|
99
|
+
/** The short code the user reads. Unique among live pairings. */
|
|
100
|
+
readonly userCode: string;
|
|
101
|
+
readonly state: "pending" | "approved" | "denied";
|
|
102
|
+
/** Set when approved — learned from the approving user's own session. */
|
|
103
|
+
readonly owner: string | null;
|
|
104
|
+
readonly runnerId: string | null;
|
|
105
|
+
/**
|
|
106
|
+
* Whether this approval has already been collected — cloud_008 §2.4.
|
|
107
|
+
*
|
|
108
|
+
* This was `runnerTokenOnce`, a bearer token held until the daemon's next
|
|
109
|
+
* poll and then nulled. The token is gone (finding 37: minted, hashed,
|
|
110
|
+
* written to two disks, never sent or compared), but the *deliver-once*
|
|
111
|
+
* property it carried is real and separate: a replayed device code must get
|
|
112
|
+
* nothing, or a code seen in a shell history is a second pairing.
|
|
113
|
+
*
|
|
114
|
+
* So the flag stays and the secret does not. Nulling a token to mean
|
|
115
|
+
* "collected" was one field doing two jobs, and only one of them was load
|
|
116
|
+
* bearing.
|
|
117
|
+
*/
|
|
118
|
+
readonly collected: boolean;
|
|
119
|
+
readonly label: string;
|
|
120
|
+
readonly platform: "darwin" | "linux" | "win32";
|
|
121
|
+
readonly daemonVersion: string;
|
|
122
|
+
readonly capabilities: readonly Capability[];
|
|
123
|
+
/**
|
|
124
|
+
* The device's public keys, presented at pair start (byollm_009 §5).
|
|
125
|
+
*
|
|
126
|
+
* Kept on the pairing so the approving user is approving a *specific
|
|
127
|
+
* machine*, not a code that any machine could later redeem. It is copied
|
|
128
|
+
* onto the runner at approval.
|
|
129
|
+
*/
|
|
130
|
+
readonly device: PublicIdentity;
|
|
131
|
+
readonly expiresAt: number;
|
|
132
|
+
readonly createdAt: number;
|
|
133
|
+
}
|
|
134
|
+
/** What the app supplies to enqueue a job. */
|
|
135
|
+
interface EnqueueInput {
|
|
136
|
+
readonly kind: JobKind;
|
|
137
|
+
/** The work, in plaintext. The server seals it before it is stored. */
|
|
138
|
+
readonly payload: JobPayload;
|
|
139
|
+
readonly owner: string;
|
|
140
|
+
/** Defaults to `self` — the safe direction. */
|
|
141
|
+
readonly audience?: Audience;
|
|
142
|
+
readonly audienceAllow?: readonly string[];
|
|
143
|
+
readonly dependsOn?: readonly string[];
|
|
144
|
+
/** Defaults to the server config's `defaultTtlMs`. */
|
|
145
|
+
readonly ttlMs?: number;
|
|
146
|
+
readonly deadlineAt?: number;
|
|
147
|
+
/** Caller-supplied id, for idempotent enqueue. */
|
|
148
|
+
readonly id?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* What the *store* is given — the sealed form.
|
|
152
|
+
*
|
|
153
|
+
* Distinct from {@link EnqueueInput} because the two are genuinely different
|
|
154
|
+
* things: an app hands over work in plaintext, and what gets written down is
|
|
155
|
+
* sealed. Collapsing them into one type would mean a field that is sometimes
|
|
156
|
+
* readable and sometimes not, which is the kind of ambiguity that ends with
|
|
157
|
+
* plaintext in a database.
|
|
158
|
+
*/
|
|
159
|
+
interface StoredJobInput extends Omit<EnqueueInput, "payload" | "id"> {
|
|
160
|
+
readonly id: string;
|
|
161
|
+
readonly envelope: SealedEnvelope;
|
|
162
|
+
readonly sizeClass: SizeClass;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The adapter seam.
|
|
167
|
+
*
|
|
168
|
+
* Everything in `@byollm/server` above this interface is storage-agnostic;
|
|
169
|
+
* everything below it is one adapter. `MemoryJobStore` is the reference
|
|
170
|
+
* implementation and the thing the conformance kit certifies first — an
|
|
171
|
+
* adapter is correct when the same kit passes against it.
|
|
172
|
+
*/
|
|
173
|
+
interface JobStore {
|
|
174
|
+
/** Create a job. Idempotent when `input.id` is supplied and already exists. */
|
|
175
|
+
create(input: StoredJobInput, now: number): Promise<JobRecord>;
|
|
176
|
+
get(jobId: string): Promise<JobRecord | null>;
|
|
177
|
+
/**
|
|
178
|
+
* Atomically claim up to `max` jobs for a runner
|
|
179
|
+
* ({@link MUSTS.CLAIM_ATOMIC}).
|
|
180
|
+
*
|
|
181
|
+
* An implementation MUST apply, inside the same atomic step:
|
|
182
|
+
* - state is `queued`;
|
|
183
|
+
* - `claimableAt` is non-null and `<= now` (dependencies satisfied,
|
|
184
|
+
* {@link MUSTS.DEPENDS_ON_GATING});
|
|
185
|
+
* - the job's kind appears in `capabilities`
|
|
186
|
+
* ({@link MUSTS.CLAIM_REQUIRES_CAPABILITY});
|
|
187
|
+
* - the audience rules admit this runner
|
|
188
|
+
* ({@link MUSTS.AUDIENCE_BOTH_SIDES});
|
|
189
|
+
* - `runnerId` is not in the job's `refusedBy`
|
|
190
|
+
* ({@link MUSTS.REFUSAL_NOT_REOFFERED}).
|
|
191
|
+
*
|
|
192
|
+
* SQL-backed adapters SHOULD use `FOR UPDATE SKIP LOCKED`.
|
|
193
|
+
*/
|
|
194
|
+
claim(args: ClaimArgs): Promise<JobRecord[]>;
|
|
195
|
+
/**
|
|
196
|
+
* Renew leases for the jobs a runner believes it holds, and report which it
|
|
197
|
+
* has lost. A job whose lease expired un-renewed returns to `queued`
|
|
198
|
+
* ({@link MUSTS.LEASE_RECLAIMABLE}).
|
|
199
|
+
*/
|
|
200
|
+
/**
|
|
201
|
+
* Record a lease granted by an upstream this store does not own.
|
|
202
|
+
*
|
|
203
|
+
* The cloud lane's one addition to the store contract, and it exists
|
|
204
|
+
* because of a question the direct plane never has to answer: **who grants
|
|
205
|
+
* the lease?** On the direct plane the site is the upstream, so `claim`
|
|
206
|
+
* both selects the job and grants the lease in one atomic step. Through a
|
|
207
|
+
* relay the relay selects and grants, and the site finds out afterwards.
|
|
208
|
+
*
|
|
209
|
+
* Without this the site's own row stays `queued` while a device is
|
|
210
|
+
* actively running the work, which breaks two things that are not
|
|
211
|
+
* cosmetic: `complete` refuses the result because no lease matches
|
|
212
|
+
* ({@link MUSTS.LEASE_HONORED} enforced against a lease that was never
|
|
213
|
+
* recorded), and `expireDue` expires a job someone is in the middle of.
|
|
214
|
+
*
|
|
215
|
+
* Not a second grant: it records one, and returns `null` if the job is not
|
|
216
|
+
* in a state that can accept it. The authority over who holds what remains
|
|
217
|
+
* the upstream that granted it — a store adopting a lease is bookkeeping,
|
|
218
|
+
* not a decision.
|
|
219
|
+
*/
|
|
220
|
+
adopt(args: AdoptArgs): Promise<JobRecord | null>;
|
|
221
|
+
renewLeases(args: RenewArgs): Promise<RenewResult>;
|
|
222
|
+
/**
|
|
223
|
+
* Record a terminal outcome. Idempotent by job id: the first terminal
|
|
224
|
+
* outcome wins ({@link MUSTS.RESULT_IDEMPOTENT}).
|
|
225
|
+
*
|
|
226
|
+
* Recording `ok` MUST also unblock dependents whose remaining dependencies
|
|
227
|
+
* are all `ok`, setting their `claimableAt` — which is when their TTL clock
|
|
228
|
+
* starts ({@link MUSTS.TTL_EXPIRY}).
|
|
229
|
+
*/
|
|
230
|
+
complete(args: CompleteArgs): Promise<CompleteResult>;
|
|
231
|
+
/**
|
|
232
|
+
* Return jobs to `queued`. When `reason` is `refused`, the runner MUST be
|
|
233
|
+
* added to each job's `refusedBy`.
|
|
234
|
+
*/
|
|
235
|
+
release(args: ReleaseArgs): Promise<string[]>;
|
|
236
|
+
/**
|
|
237
|
+
* Move every claimable-but-unclaimed job past its TTL, and every job past
|
|
238
|
+
* its absolute deadline, to `expired`. Returns what changed.
|
|
239
|
+
*
|
|
240
|
+
* Called opportunistically by the handlers; an adapter MAY also run it on a
|
|
241
|
+
* schedule. It MUST be idempotent — firing twice is always safe.
|
|
242
|
+
*/
|
|
243
|
+
expireDue(now: number): Promise<JobRecord[]>;
|
|
244
|
+
/** Cancel a job by app request. Returns the job, or null if unknown. */
|
|
245
|
+
cancel(jobId: string, now: number): Promise<JobRecord | null>;
|
|
246
|
+
/** Jobs a runner currently holds — used to build the heartbeat cancel list. */
|
|
247
|
+
listClaimedBy(runnerId: string): Promise<JobRecord[]>;
|
|
248
|
+
/** Jobs awaiting cancellation that a given runner holds. */
|
|
249
|
+
listCancelRequests(runnerId: string): Promise<LeaseRef[]>;
|
|
250
|
+
}
|
|
251
|
+
interface ClaimArgs {
|
|
252
|
+
readonly runnerId: string;
|
|
253
|
+
/** The runner's owner, for audience matching. */
|
|
254
|
+
readonly runnerOwner: string;
|
|
255
|
+
readonly capabilities: readonly Capability[];
|
|
256
|
+
readonly max: number;
|
|
257
|
+
readonly leaseMs: number;
|
|
258
|
+
readonly now: number;
|
|
259
|
+
}
|
|
260
|
+
/** What an upstream tells a store it has granted. */
|
|
261
|
+
interface AdoptArgs {
|
|
262
|
+
readonly jobId: string;
|
|
263
|
+
/**
|
|
264
|
+
* The lease, by its own id — and deliberately **not** a runner id.
|
|
265
|
+
*
|
|
266
|
+
* A relayed device is not this site's runner. It never paired here, the
|
|
267
|
+
* site holds no token for it and cannot revoke it, and `byollm_runners` is
|
|
268
|
+
* a table of machines this site has a relationship with. Fabricating a row
|
|
269
|
+
* to satisfy a foreign key would manufacture a record the site cannot act
|
|
270
|
+
* on, which is worse than not having one.
|
|
271
|
+
*
|
|
272
|
+
* What the site legitimately knows is that the job is out on a lease
|
|
273
|
+
* granted by an upstream, and when that lease ends. Identity of the machine
|
|
274
|
+
* that ran it arrives with the result, proved by a signature — which is a
|
|
275
|
+
* stronger claim than a row anyway.
|
|
276
|
+
*/
|
|
277
|
+
readonly leaseId: string;
|
|
278
|
+
readonly expiresAt: number;
|
|
279
|
+
readonly now: number;
|
|
280
|
+
}
|
|
281
|
+
/** A lease named by its grant, not only by the job it covers. */
|
|
282
|
+
interface LeaseRef {
|
|
283
|
+
readonly jobId: string;
|
|
284
|
+
readonly leaseId: string;
|
|
285
|
+
}
|
|
286
|
+
interface RenewArgs {
|
|
287
|
+
readonly runnerId: string;
|
|
288
|
+
readonly leases: readonly LeaseRef[];
|
|
289
|
+
readonly leaseMs: number;
|
|
290
|
+
readonly now: number;
|
|
291
|
+
}
|
|
292
|
+
interface RenewResult {
|
|
293
|
+
readonly renewed: readonly {
|
|
294
|
+
jobId: string;
|
|
295
|
+
expiresAt: number;
|
|
296
|
+
}[];
|
|
297
|
+
/**
|
|
298
|
+
* Grants the runner claimed to hold but no longer does — V1-3.
|
|
299
|
+
*
|
|
300
|
+
* Both halves, because a job id is chosen per site: a daemon serving two
|
|
301
|
+
* sites that picked the same id cannot act on a bare one without guessing.
|
|
302
|
+
*/
|
|
303
|
+
readonly lost: readonly LeaseRef[];
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Which grant is being completed — the `LEASE_HONORED` guard, as a shape.
|
|
307
|
+
*
|
|
308
|
+
* A discriminated union rather than two optional fields, because the earlier
|
|
309
|
+
* version was safe only by data: it read "match the runner, unless a lease id
|
|
310
|
+
* was supplied", and a caller supplying neither would have matched
|
|
311
|
+
* `undefined === undefined` and written a result into a job it never held.
|
|
312
|
+
* Nothing did that, and nothing was going to — but the type permitted it, and
|
|
313
|
+
* this codebase has spent a week learning that a permitted mistake is a
|
|
314
|
+
* scheduled one.
|
|
315
|
+
*
|
|
316
|
+
* Two ways to name a holder because there are two planes. A direct runner
|
|
317
|
+
* paired with this site and is known by id. A relayed device never did, and
|
|
318
|
+
* is known only by the grant it holds — which is the more exact check anyway:
|
|
319
|
+
* `LEASE_HONORED` is a statement about a lease instance, the lesson the
|
|
320
|
+
* release endpoint learned when a replayed release yanked a later grant.
|
|
321
|
+
*/
|
|
322
|
+
type CompleteHolder = {
|
|
323
|
+
readonly by: "runner";
|
|
324
|
+
readonly runnerId: string;
|
|
325
|
+
} | {
|
|
326
|
+
readonly by: "lease";
|
|
327
|
+
readonly leaseId: string;
|
|
328
|
+
};
|
|
329
|
+
interface CompleteArgs {
|
|
330
|
+
readonly jobId: string;
|
|
331
|
+
/**
|
|
332
|
+
* The authenticated caller — cloud_008 §3.6, and for the duplicate answer
|
|
333
|
+
* only.
|
|
334
|
+
*
|
|
335
|
+
* Never for authorisation: {@link CompleteHolder} decides whether this
|
|
336
|
+
* write may happen, and that has not changed. This decides which of two
|
|
337
|
+
* refusals a caller is owed once the write is refused.
|
|
338
|
+
*
|
|
339
|
+
* It exists because a *grant* is not a device. Scoping the duplicate answer
|
|
340
|
+
* to the lease id alone let a second daemon replay under a lease id it had
|
|
341
|
+
* learned and be told "already recorded" — which is both untrue and a
|
|
342
|
+
* terminality probe. Found by writing C010's third case.
|
|
343
|
+
*/
|
|
344
|
+
readonly runnerId: string;
|
|
345
|
+
/** Who claims to hold this job. Both variants are checked, never trusted. */
|
|
346
|
+
readonly holder: CompleteHolder;
|
|
347
|
+
readonly outcome: JobOutcome;
|
|
348
|
+
readonly provenance: ResultProvenance;
|
|
349
|
+
readonly now: number;
|
|
350
|
+
}
|
|
351
|
+
interface CompleteResult {
|
|
352
|
+
/** False when this submission wrote nothing. */
|
|
353
|
+
readonly accepted: boolean;
|
|
354
|
+
/**
|
|
355
|
+
* True when the caller is the device that already recorded this result.
|
|
356
|
+
*
|
|
357
|
+
* cloud_008 §3.6. `RESULT_IDEMPOTENT` used to hold as a *side effect* of
|
|
358
|
+
* `LEASE_HONORED`: `complete` nulls the lease on success, so a replay was
|
|
359
|
+
* refused for not holding the job and the idempotency branch was never
|
|
360
|
+
* reached. Deleting that branch failed no check.
|
|
361
|
+
*
|
|
362
|
+
* byollm_009 §4's case for signing requests rather than issuing nonces
|
|
363
|
+
* rests on every write being idempotent per the instance it names. A MUST
|
|
364
|
+
* that another MUST's security argument leans on cannot be one that holds
|
|
365
|
+
* only because a null happened to trip first.
|
|
366
|
+
*/
|
|
367
|
+
readonly duplicate?: boolean;
|
|
368
|
+
readonly job: JobRecord | null;
|
|
369
|
+
}
|
|
370
|
+
interface ReleaseArgs {
|
|
371
|
+
readonly runnerId: string;
|
|
372
|
+
readonly leases: readonly LeaseRef[];
|
|
373
|
+
readonly reason: "shutdown" | "pause" | "revoked" | "backend-down" | "refused";
|
|
374
|
+
readonly now: number;
|
|
375
|
+
}
|
|
376
|
+
/** Runner registry and pairing state. */
|
|
377
|
+
interface RunnerStore {
|
|
378
|
+
/** Begin a device-code pairing. */
|
|
379
|
+
createPairing(record: PairingRecord): Promise<void>;
|
|
380
|
+
getPairingByDeviceCodeHash(hash: string): Promise<PairingRecord | null>;
|
|
381
|
+
getPairingByUserCode(userCode: string): Promise<PairingRecord | null>;
|
|
382
|
+
/**
|
|
383
|
+
* Approve a pairing on behalf of an authenticated user, creating the
|
|
384
|
+
* runner. The `owner` MUST come from the approving user's own session — a
|
|
385
|
+
* daemon can never assert who it is ({@link MUSTS.PAIR_ONE_USER}).
|
|
386
|
+
*/
|
|
387
|
+
approvePairing(args: ApproveArgs): Promise<RunnerRecord>;
|
|
388
|
+
denyPairing(userCode: string, now: number): Promise<void>;
|
|
389
|
+
/** Clear the one-shot token after the daemon collects it. */
|
|
390
|
+
consumePairingToken(deviceCodeHash: string): Promise<void>;
|
|
391
|
+
getRunner(runnerId: string): Promise<RunnerRecord | null>;
|
|
392
|
+
/** Record a heartbeat: capabilities, version, pause state, liveness. */
|
|
393
|
+
touchRunner(args: TouchArgs): Promise<RunnerRecord | null>;
|
|
394
|
+
/** Revoke a runner. Once revoked, never un-revoked. */
|
|
395
|
+
revokeRunner(runnerId: string, now: number): Promise<void>;
|
|
396
|
+
/** Live runners for an owner — used by the no-runner signal. */
|
|
397
|
+
listRunners(owner?: string): Promise<RunnerRecord[]>;
|
|
398
|
+
/**
|
|
399
|
+
* Watch one job for state changes — the push seam (byollm_009 §8.3).
|
|
400
|
+
*
|
|
401
|
+
* **Required of every adapter, from day one, even though v1 uses it only
|
|
402
|
+
* for result readiness.** byollm_006 located streaming's real difficulty at
|
|
403
|
+
* the server→app leg: polling cannot carry deltas by construction. If the
|
|
404
|
+
* store contract were request/response only, adding streaming later would
|
|
405
|
+
* force a *second* adapter-breaking reshape — so the channel exists now and
|
|
406
|
+
* gets one more use later, rather than the interface changing twice.
|
|
407
|
+
*
|
|
408
|
+
* The handler is called after a change lands; it is a signal, not a
|
|
409
|
+
* payload, so a missed or duplicated call is survivable and the caller
|
|
410
|
+
* re-reads. That looseness is deliberate: it is the weakest contract every
|
|
411
|
+
* plausible backend can honour, and a stronger one would exclude adapters
|
|
412
|
+
* for no gain.
|
|
413
|
+
*
|
|
414
|
+
* Returns an unsubscribe function. Calling it twice MUST be safe.
|
|
415
|
+
*/
|
|
416
|
+
subscribe(jobId: string, onChange: () => void): () => void;
|
|
417
|
+
}
|
|
418
|
+
interface ApproveArgs {
|
|
419
|
+
readonly userCode: string;
|
|
420
|
+
/** From the approving user's session, never from the daemon. */
|
|
421
|
+
readonly owner: string;
|
|
422
|
+
readonly runnerId: string;
|
|
423
|
+
readonly now: number;
|
|
424
|
+
}
|
|
425
|
+
interface TouchArgs {
|
|
426
|
+
readonly runnerId: string;
|
|
427
|
+
readonly capabilities: readonly Capability[];
|
|
428
|
+
readonly daemonVersion: string;
|
|
429
|
+
readonly paused: boolean;
|
|
430
|
+
readonly now: number;
|
|
431
|
+
}
|
|
432
|
+
/** A store providing both halves. Most adapters implement one object. */
|
|
433
|
+
interface ByollmStore extends JobStore, RunnerStore {
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export type { AdoptArgs as A, ByollmStore as B, ClaimArgs as C, EnqueueInput as E, JobRecord as J, PairingRecord as P, RunnerRecord as R, StoredJobInput as S, TouchArgs as T, RenewArgs as a, RenewResult as b, CompleteArgs as c, CompleteResult as d, ReleaseArgs as e, ApproveArgs as f, CompleteHolder as g, JobStore as h, RunnerStore as i };
|
package/dist/supabase/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
import { B as ByollmStore } from '../store-
|
|
2
|
+
import { B as ByollmStore } from '../store-Dno2fnHH.js';
|
|
3
3
|
import { P as PollingDeliveryDeps, R as ResultDelivery } from '../delivery-36nIe-b3.js';
|
|
4
4
|
import '@byollm/protocol';
|
|
5
5
|
|