@ref0/sdk 0.0.0-stage → 0.6.0

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/dist/index.mjs ADDED
@@ -0,0 +1,1552 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { z } from "zod";
3
+ //#region src/ref0-steps.ts
4
+ /**
5
+ * What ref0's own steps in a run share, whichever handle or leaf takes them: how each retries,
6
+ * and the digest a step's name carries as its identity.
7
+ */
8
+ /**
9
+ * How ref0's own steps retry: what fails in one is ref0 unavailable, so five tries a second
10
+ * apart and doubling, about half a minute, then the run ends `unavailable`, never as a failure.
11
+ * ref0 gives up on an attempt after 40 seconds, inside its minute, so a stall fails as ref0's
12
+ * outage and never as Workflows' own timeout.
13
+ */
14
+ const ref0Steps = {
15
+ retries: {
16
+ limit: 5,
17
+ delay: "1 second",
18
+ backoff: "exponential"
19
+ },
20
+ timeout: "1 minute"
21
+ };
22
+ /** The first 12 hex of the SHA-256 of some text: a step's identity in its name. */
23
+ const digestOf = async (text) => {
24
+ return [...new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text))).slice(0, 6)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
25
+ };
26
+ //#endregion
27
+ //#region src/rpc.ts
28
+ /**
29
+ * What the SDK does with what reaches a run's code over RPC: Cloudflare's `step`, and ref0's own
30
+ * calls. Each result is copied and released, so nothing holds the call open and the invocation
31
+ * can end.
32
+ */
33
+ /** A result that came over RPC, copied and released. */
34
+ const settle = async (call) => {
35
+ const result = await call;
36
+ try {
37
+ return structuredClone(result);
38
+ } finally {
39
+ result?.[Symbol.dispose]?.();
40
+ }
41
+ };
42
+ /**
43
+ * Cloudflare's `step`, with every result settled, and each `do` callback given to `scoped`
44
+ * first, so a run can know which step its code is in.
45
+ */
46
+ const settledStep = (step, scoped = (callback) => callback) => ({
47
+ do: (name, ...args) => settle(args.length === 1 ? step.do(name, scoped(args[0])) : step.do(name, args[0], scoped(args[1]))),
48
+ sleep: (name, duration) => settle(step.sleep(name, duration)),
49
+ sleepUntil: (name, timestamp) => settle(step.sleepUntil(name, timestamp)),
50
+ waitForEvent: (name, options) => settle(step.waitForEvent(name, options))
51
+ });
52
+ //#endregion
53
+ //#region src/run-scope.ts
54
+ const inStep = new AsyncLocalStorage();
55
+ const inRun = new AsyncLocalStorage();
56
+ /**
57
+ * Where a sandbox opened now goes, for `sandbox.run`: the run's sink, its place counted among the
58
+ * step's actions, and the step's attempt. Undefined outside a workflow's step.
59
+ */
60
+ const sandboxPlace = () => {
61
+ const at = inStep.getStore();
62
+ if (at === void 0) return void 0;
63
+ const place = {
64
+ step: at.step,
65
+ count: at.count,
66
+ call: at.calls
67
+ };
68
+ at.calls += 1;
69
+ return {
70
+ sink: at.sink,
71
+ place,
72
+ attempt: at.attempt
73
+ };
74
+ };
75
+ //#endregion
76
+ //#region src/ask.ts
77
+ /**
78
+ * ref0 refused an ask, as it refuses an action, and never opened it; `duplicate` when the same
79
+ * words went to the same decider within the hour; or `withdrawn`, opened, then closed with no
80
+ * answer when its thread closed. A refusal is kept as its step's result, so a replay throws it
81
+ * again and asks nothing.
82
+ */
83
+ var AskRefused = class extends Error {
84
+ why;
85
+ constructor(key, why) {
86
+ super(why === "withdrawn" ? `ref0 withdrew the ask ${key}: its thread closed before anyone answered` : `ref0 refused the ask ${key}: ${why}`);
87
+ this.name = "AskRefused";
88
+ this.why = why;
89
+ }
90
+ };
91
+ /** What an ask's key may be, as `AnswerKey` in `@ref0/contracts` says. */
92
+ const askKey = /^[a-z0-9][a-z0-9-]{0,31}$/;
93
+ /**
94
+ * How long the run waits for an ask's close: its deadline is a day at most, and ref0 sends its
95
+ * lean at the deadline, so past a day and an hour ref0 has failed to.
96
+ */
97
+ const answerTimeout = "25 hours";
98
+ /**
99
+ * The run's sink: refused, in words, inside a step's callback, since one step can't hold another,
100
+ * and outside a workflow run.
101
+ */
102
+ const sinkFor$1 = () => {
103
+ if (inStep.getStore() !== void 0) throw new Error("ask.run is a step of its own: call it outside step.do's callback");
104
+ const run = inRun.getStore();
105
+ if (run === void 0) throw new Error("ask.run asks from a workflow's run only");
106
+ return run.sink;
107
+ };
108
+ /**
109
+ * Asks once, in a step of its own, then waits in the run for the ask's close, `ask-<key>`. The
110
+ * ask's step name carries its key and a digest of what it asks, since Workflows tells steps of
111
+ * one name apart only by the order they're reached in: a replay finds the same ask, and two asks
112
+ * with one key never take each other's answers, as ref0 opens only the first.
113
+ */
114
+ const run$1 = async (step, options) => {
115
+ const sink = sinkFor$1();
116
+ const key = options.key;
117
+ if (!askKey.test(key)) throw new AskRefused(JSON.stringify(key).slice(0, 80), "invalid");
118
+ const request = {
119
+ verb: "ask",
120
+ to: options.to,
121
+ key,
122
+ question: options.question,
123
+ answers: options.answers.map((answer) => ({
124
+ key: answer.key,
125
+ title: answer.title,
126
+ detail: answer.detail
127
+ })),
128
+ lean: options.lean,
129
+ deadline: options.deadline
130
+ };
131
+ const asked = await step.do(`ref0: ask ${key} ${await digestOf(JSON.stringify(request))}`, ref0Steps, async (context) => await settle(sink.act({
132
+ place: {
133
+ step: context.step.name,
134
+ count: context.step.count,
135
+ call: 0
136
+ },
137
+ request
138
+ })));
139
+ if (asked.kind === "duplicate") throw new AskRefused(key, "duplicate");
140
+ if (asked.kind === "refused") throw new AskRefused(key, asked.why);
141
+ const got = (await step.waitForEvent(`ref0: answer ${key}`, {
142
+ type: `ask-${key}`,
143
+ timeout: answerTimeout
144
+ }).catch(() => {
145
+ throw new Error(`ref0 sent no answer to the ask ${key} within a day and an hour`);
146
+ })).payload;
147
+ if (got.kind === "withdrawn") throw new AskRefused(key, "withdrawn");
148
+ const answered = got.key;
149
+ return got.via === "decider" ? {
150
+ key: answered,
151
+ by: got.by,
152
+ via: got.via,
153
+ at: got.at
154
+ } : {
155
+ key: answered,
156
+ via: got.via,
157
+ at: got.at
158
+ };
159
+ };
160
+ /**
161
+ * ref0's ask: `ask.run(step, { to, key, question, answers, lean, deadline })` asks, then waits
162
+ * in the run for `{ key, by, via, at }`, the answer, or the lean at the deadline.
163
+ */
164
+ const ask = { run: run$1 };
165
+ //#endregion
166
+ //#region src/check-params.ts
167
+ /**
168
+ * How deep an object may sit in params: an expansion's answer holds them two deeper, and the run's
169
+ * main module takes an answer whose objects sit at most 64 deep.
170
+ */
171
+ const maxDepth$1 = 62;
172
+ /** JSON with each object's keys in order, so one value always reads as the same text. */
173
+ const sortedJson = (value) => JSON.stringify(value, (_key, each) => each instanceof Object && !Array.isArray(each) ? Object.fromEntries(Object.entries(each).toSorted(([a], [b]) => a < b ? -1 : 1)) : each);
174
+ /**
175
+ * Params as `CheckRunParams` holds them, in a copy of their own, or the path to the first value
176
+ * in them that JSON can't hold as it is, `''` for the params themselves: a function, a number
177
+ * that isn't finite, `undefined` in a list, or an object that isn't plain, such as a `Date` or a
178
+ * `Map`. An object's `undefined` field is left out, and `-0` is `0`, as JSON writes them, so
179
+ * what's sent is what's digested.
180
+ */
181
+ const paramsOf = (value) => {
182
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return { notJson: "" };
183
+ const copy = objectOf(value, "", 0);
184
+ return "notJson" in copy ? copy : { params: copy.json };
185
+ };
186
+ /** A value as JSON holds it, at `at`, `depth` deep: see `paramsOf`. */
187
+ const copyOf = (value, at, depth) => {
188
+ if (value === null || typeof value === "string" || typeof value === "boolean") return { json: value };
189
+ if (typeof value === "number") return Number.isFinite(value) ? { json: value === 0 ? 0 : value } : { notJson: at };
190
+ if (typeof value !== "object" || depth > maxDepth$1) return { notJson: at };
191
+ return Array.isArray(value) ? listOf(value, at, depth) : objectOf(value, at, depth);
192
+ };
193
+ /** A list as JSON holds it, each item read by its index, as JSON reads one. */
194
+ const listOf = (value, at, depth) => {
195
+ const items = [];
196
+ for (let index = 0; index < value.length; index += 1) {
197
+ const copy = copyOf(value[index], `${at}[${index}]`, depth + 1);
198
+ if ("notJson" in copy) return copy;
199
+ items.push(copy.json);
200
+ }
201
+ return { json: items };
202
+ };
203
+ /**
204
+ * A called workflow's output as JSON holds it, in a copy of its own, or the path to the first value
205
+ * in it that JSON can't hold as it is, `''` for the output itself, as `paramsOf` reads params.
206
+ */
207
+ const outputOf = (value) => copyOf(value, "", 0);
208
+ /** A plain object as JSON holds it, each `undefined` field left out. */
209
+ const objectOf = (value, at, depth) => {
210
+ const prototype = Object.getPrototypeOf(value);
211
+ if (prototype !== Object.prototype && prototype !== null) return { notJson: at };
212
+ const fields = [];
213
+ for (const [key, field] of Object.entries(value)) {
214
+ if (field === void 0) continue;
215
+ const copy = copyOf(field, at === "" ? key : `${at}.${key}`, depth + 1);
216
+ if ("notJson" in copy) return copy;
217
+ fields.push([key, copy.json]);
218
+ }
219
+ return { json: Object.fromEntries(fields) };
220
+ };
221
+ /** The most a refusal's why may say, as `CheckExpansion` and `CalledWorkflowAnswer` keep it. */
222
+ const whyLength$1 = 1e3;
223
+ /**
224
+ * The first of a zod error's issues, as one line, at its path, for a check's params or a called
225
+ * workflow's input: `none` when it has no issue to say.
226
+ */
227
+ const issueOf = (error, none) => {
228
+ const [issue] = error.issues;
229
+ if (issue === void 0) return none;
230
+ const at = issue.path.map(String).join(".");
231
+ return (at === "" ? issue.message : `${at}: ${issue.message}`).slice(0, whyLength$1);
232
+ };
233
+ //#endregion
234
+ //#region src/called.ts
235
+ /**
236
+ * Why a call of a called workflow failed in its caller: ref0 refused it, by `why`, with what to do
237
+ * next, such as a chain of calls past four workflows; or the child run ended without returning,
238
+ * `failed`, in words that say how, such as its code throwing or its time running out. A refusal
239
+ * is kept as its step's result, so a replay throws it again and calls nothing.
240
+ */
241
+ var CalledRunFailed = class extends Error {
242
+ workflow;
243
+ why;
244
+ /** The child run, once there is one. */
245
+ run;
246
+ constructor(workflow, failed, run) {
247
+ super(failed.kind === "refused" ? `ref0 refused to call ${workflow}: ${failed.nextStep}` : `${workflow} failed: ${failed.why}`);
248
+ this.name = "CalledRunFailed";
249
+ this.workflow = workflow;
250
+ this.why = failed.kind === "refused" ? failed.why : "failed";
251
+ this.run = run;
252
+ }
253
+ };
254
+ /** The most a call's input may be, as JSON in UTF-8, as `@ref0/contracts` says. */
255
+ const maxInputBytes = 16384;
256
+ /** How long one wait for a child's end lasts before ref0's record is read again. */
257
+ const waitTimeout$1 = "1 hour";
258
+ const encoder = new TextEncoder();
259
+ /**
260
+ * A call's input as JSON holds it, as a check's params are, an `undefined` field left out, so
261
+ * what's sent is what's digested: refused, before anything is sent, when JSON can't hold it as it
262
+ * is, or past its bound.
263
+ */
264
+ const inputOf = (workflow, given) => {
265
+ const copy = paramsOf(given);
266
+ if ("notJson" in copy) throw new CalledRunFailed(workflow, {
267
+ kind: "refused",
268
+ why: "invalid input",
269
+ nextStep: copy.notJson === "" ? "Give a called workflow's input as a plain object of JSON values" : `Give ${copy.notJson} as JSON: a string, a finite number, true, false, null, or a list or plain object of them`
270
+ });
271
+ if (encoder.encode(JSON.stringify(copy.params)).byteLength > maxInputBytes) throw new CalledRunFailed(workflow, {
272
+ kind: "refused",
273
+ why: "invalid input",
274
+ nextStep: `Give it at most ${maxInputBytes} bytes of input, as JSON: pass a name or a key, not the data`
275
+ });
276
+ return copy.params;
277
+ };
278
+ /**
279
+ * The run's sink: refused, in words, inside a step's callback, since one step can't hold another,
280
+ * and outside a workflow run.
281
+ */
282
+ const sinkFor = (workflow) => {
283
+ if (inStep.getStore() !== void 0) throw new Error(`${workflow}.run is a step of its own: call it outside step.do's callback`);
284
+ const run = inRun.getStore();
285
+ if (run === void 0) throw new Error(`ref0 runs a called workflow from a workflow's run only: ${workflow}`);
286
+ return run.sink;
287
+ };
288
+ /**
289
+ * Calls a called workflow in a step of its own, `ref0: call <name> <digest>`. Its name carries a
290
+ * digest of the input, since Workflows tells steps of one name apart only by the order they're
291
+ * reached in, and concurrent calls reach theirs in another order on a replay. ref0 keys the child
292
+ * by this run, the step's name and count, and the workflow, so a replay finds the same child, a
293
+ * replay with other input calls another, and two calls with the same input are a child each.
294
+ * Calls alike in input may swap their children on a replay, as check starts alike do. Then each
295
+ * round reads how the child is going, in a step, and waits for its end, an hour at a time: ref0
296
+ * sends the end, and a wait that times out reads again, for as long as the child is going.
297
+ */
298
+ const callOf = async (step, workflow, given) => {
299
+ const sink = sinkFor(workflow);
300
+ const input = inputOf(workflow, given);
301
+ const answer = await step.do(`ref0: call ${workflow} ${await digestOf(JSON.stringify(input))}`, ref0Steps, async (context) => await settle(sink.callWorkflow({
302
+ place: {
303
+ step: context.step.name,
304
+ count: context.step.count
305
+ },
306
+ workflow,
307
+ input
308
+ })));
309
+ if (answer.kind === "refused") throw new CalledRunFailed(workflow, answer);
310
+ const { run } = answer;
311
+ for (let round = 1;; round += 1) {
312
+ const state = await step.do(`ref0: called ${run} ${round}`, ref0Steps, async () => await settle(sink.calledState({ run })));
313
+ if (state.kind === "refused") throw new CalledRunFailed(workflow, state, run);
314
+ if (state.kind === "failed") throw new CalledRunFailed(workflow, state, run);
315
+ if (state.kind === "done") return state.output;
316
+ await step.waitForEvent(`ref0: wait ${run} ${round}`, {
317
+ type: `workflow-${run}`,
318
+ timeout: waitTimeout$1
319
+ }).catch(() => void 0);
320
+ }
321
+ };
322
+ /**
323
+ * What an import of `.ref0/workflows/<name>.ts` is, as ref0's bundler writes it: a called
324
+ * workflow's handle. Its bare call takes input JSON holds, as a call does.
325
+ */
326
+ const workflowHandle = (workflow) => Object.assign((input) => ({
327
+ kind: "workflow",
328
+ workflow,
329
+ input: inputOf(workflow, input)
330
+ }), { run: async (step, input) => await callOf(step, workflow, input) });
331
+ //#endregion
332
+ //#region src/attempts.ts
333
+ const inAttempt = new AsyncLocalStorage();
334
+ /** A check's step callback, run with its attempt's opener in scope, and given the context alone. */
335
+ const attemptScoped = (callback) => (context, opener) => opener === void 0 ? callback(context) : inAttempt.run(opener, () => callback(context));
336
+ /** The opener of the attempt this code runs in, or `undefined` outside a step's callback. */
337
+ const attemptOpener = () => inAttempt.getStore();
338
+ //#endregion
339
+ //#region src/needs.ts
340
+ const made = /* @__PURE__ */ new WeakMap();
341
+ /** The most of a need's stack kept, as `maxNeedStackLength` in `@ref0/contracts` says. */
342
+ const maxStack = 320;
343
+ /** The frames kept: the SDK's own few, then the line of `gate.ts` that made the need. */
344
+ const maxFrames = 6;
345
+ /**
346
+ * A need, made where it's called from, or on `stack` when a helper of the SDK's took its caller's
347
+ * stack itself, so the line of `gate.ts` stays in the frames kept.
348
+ */
349
+ const needFrom = (need, stack = (/* @__PURE__ */ new Error("a need")).stack ?? "") => {
350
+ const value = Object.freeze({});
351
+ made.set(value, {
352
+ need,
353
+ stack: stack.split("\n").slice(0, maxFrames).join("\n").slice(0, maxStack)
354
+ });
355
+ return value;
356
+ };
357
+ /**
358
+ * What the SDK made a need as, or `undefined` for anything else the gate returned in its place:
359
+ * what isn't an object is never a key of `made`.
360
+ */
361
+ const madeOf = (value) => made.get(value);
362
+ /**
363
+ * Whether what the gate or a check's run returned in place of a list of needs is one: a list the
364
+ * value's own type says holds needs keeps that type, where `Array.isArray` would make it `any[]`.
365
+ */
366
+ const isList = (value) => Array.isArray(value);
367
+ //#endregion
368
+ //#region src/start-answers.ts
369
+ /**
370
+ * A start rule's answer, as `check()` keeps it for ref0 to call in the check's isolate: the rule
371
+ * run for each new run a start would make, and what its lists say, checked, since a rule may
372
+ * return anything.
373
+ */
374
+ /** The most refusals an answer keeps, and called runs it waits on, as `@ref0/contracts` says. */
375
+ const maxRefusals = 16;
376
+ const maxWaits = 4;
377
+ /** The starter, as a run their start would make shows them: their actor, without its chain. */
378
+ const actorOf = (actor) => {
379
+ switch (actor.kind) {
380
+ case "person": return {
381
+ kind: "person",
382
+ id: actor.id
383
+ };
384
+ case "agent": return {
385
+ kind: "agent",
386
+ id: actor.id,
387
+ owner: actor.owner
388
+ };
389
+ case "ref0": return {
390
+ kind: "ref0",
391
+ id: actor.id
392
+ };
393
+ }
394
+ };
395
+ const isStrings = (value) => isList(value) && value.every((item) => typeof item === "string");
396
+ const isAllow = (item) => typeof item === "object" && item !== null && "kind" in item && item.kind === "allow";
397
+ /** Whether a value is `text`'s, a start rule's refusal's words or a called workflow's title. */
398
+ const isText = (value) => typeof value === "object" && value !== null && "strings" in value && "values" in value && isStrings(value.strings) && isStrings(value.values);
399
+ const isRefusal = (item) => typeof item === "object" && item !== null && "kind" in item && item.kind === "refuse" && "text" in item && isText(item.text);
400
+ const isCalledRun = (item) => typeof item === "object" && item !== null && "kind" in item && item.kind === "workflow" && "workflow" in item && typeof item.workflow === "string" && "input" in item && typeof item.input === "object" && item.input !== null && !isList(item.input);
401
+ /**
402
+ * A start rule's answer for each new run a start would make, each judged with its own params, the
403
+ * check's runs with the start's new runs before it, newest first, as just started, so a grid
404
+ * can't pass a limit its rule counts, and when it's judged: refused if any list refuses, with
405
+ * each refusal once; otherwise waiting on each called workflow's run the lists name, each
406
+ * workflow and input once, at most `maxWaits`; otherwise allowed. Invalid, with why, when the
407
+ * rule returns anything else, or names more runs.
408
+ */
409
+ const startAnswerOf = async (rule, context) => {
410
+ if (rule === void 0) return { kind: "allowed" };
411
+ const refusals = /* @__PURE__ */ new Map();
412
+ const waits = /* @__PURE__ */ new Map();
413
+ const starting = [];
414
+ for (const cell of context.cells) {
415
+ const listed = await rule({
416
+ actor: context.actor,
417
+ params: cell,
418
+ where: context.where,
419
+ runs: [...starting, ...context.runs],
420
+ now: context.now
421
+ });
422
+ if (!isList(listed)) return {
423
+ kind: "invalid",
424
+ why: "It didn't return a list"
425
+ };
426
+ for (const item of listed) {
427
+ if (!item) continue;
428
+ if (isRefusal(item)) {
429
+ const text = {
430
+ strings: [...item.text.strings],
431
+ values: [...item.text.values]
432
+ };
433
+ refusals.set(JSON.stringify(text), text);
434
+ } else if (isCalledRun(item)) {
435
+ const run = {
436
+ workflow: item.workflow,
437
+ input: item.input
438
+ };
439
+ waits.set(sortedJson(run), run);
440
+ } else if (!isAllow(item)) return {
441
+ kind: "invalid",
442
+ why: "Its list holds something that isn't allow(), refuse(text), a called workflow's run or a falsy value"
443
+ };
444
+ }
445
+ starting.unshift({
446
+ startedAt: context.now,
447
+ actor: actorOf(context.actor),
448
+ where: context.where,
449
+ params: cell
450
+ });
451
+ }
452
+ if (refusals.size > 0) return {
453
+ kind: "refused",
454
+ refusals: [...refusals.values()].slice(0, maxRefusals)
455
+ };
456
+ if (waits.size > maxWaits) return {
457
+ kind: "invalid",
458
+ why: `It waits on more than ${maxWaits} called workflows' runs: name at most ${maxWaits}, each workflow and input once`
459
+ };
460
+ return waits.size === 0 ? { kind: "allowed" } : {
461
+ kind: "waits",
462
+ runs: [...waits.values()]
463
+ };
464
+ };
465
+ //#endregion
466
+ //#region src/tree.ts
467
+ const decoder = new TextDecoder();
468
+ /** A tree's reads, each one of ref0's over RPC through `host`. */
469
+ const treeOf = (host) => {
470
+ const bytesAt = ({ path }) => settle(host.readFile(path));
471
+ const textAt = async (read) => {
472
+ const bytes = await bytesAt(read);
473
+ return bytes === null ? null : decoder.decode(bytes);
474
+ };
475
+ return {
476
+ readFile: async (read) => {
477
+ const bytes = await bytesAt(read);
478
+ return bytes === null ? null : new Blob([bytes]);
479
+ },
480
+ readText: textAt,
481
+ readJSON: async (read) => {
482
+ const text = await textAt(read);
483
+ return text === null ? null : JSON.parse(text);
484
+ },
485
+ readTree: async ({ path }) => await settle(host.readTree(path))
486
+ };
487
+ };
488
+ //#endregion
489
+ //#region src/check.ts
490
+ /**
491
+ * ref0 refused a workflow's start of a check, or its read of one's runs, as `ref0 run` refuses
492
+ * one: `why`, what to do next, and for `busy`, when it may go. A refusal is kept as its step's
493
+ * result, so a replay throws it again and starts nothing. The SDK refuses a start's key or
494
+ * params itself, before any step, and a gate's need's params when the need is made.
495
+ */
496
+ var CheckRunRefused = class extends Error {
497
+ why;
498
+ nextStep;
499
+ /** When a `busy` start may go, in milliseconds since the epoch. */
500
+ retryAt;
501
+ constructor(check, refused) {
502
+ super(`ref0 refused ${check}: ${refused.nextStep}`);
503
+ this.name = "CheckRunRefused";
504
+ this.why = refused.why;
505
+ this.nextStep = refused.nextStep;
506
+ this.retryAt = refused.retryAt;
507
+ }
508
+ };
509
+ /** How long one wait for a run's end lasts before ref0's record is read again. */
510
+ const waitTimeout = "1 hour";
511
+ /**
512
+ * The run whose code calls a handle, to start and read its runs through: refused, in words,
513
+ * inside a step's callback, since one step can't hold another, and outside a workflow run.
514
+ */
515
+ const runFor = (name) => {
516
+ if (inStep.getStore() !== void 0) throw new Error(`${name}.run, start and wait are steps of their own: call them outside step.do's callback`);
517
+ const run = inRun.getStore();
518
+ if (run === void 0) throw new Error(`ref0 runs a check from a workflow's code only, not yet from a check: ${name}`);
519
+ return run;
520
+ };
521
+ /** A reference to a run a start got, as a step keeps it. */
522
+ const referenceOf = (check, run) => ({
523
+ check,
524
+ run
525
+ });
526
+ /** How a run ended, as a result, from what ref0's record says of it. */
527
+ const resultOf = (reference, read) => {
528
+ const { params } = read;
529
+ if (read.state !== "ended") throw new Error(`ref0 read ${reference.run} as ${read.state} once it had ended`);
530
+ if (read.error !== void 0) return read.why === void 0 ? {
531
+ run: reference,
532
+ params,
533
+ error: read.error
534
+ } : {
535
+ run: reference,
536
+ params,
537
+ error: read.error,
538
+ why: read.why
539
+ };
540
+ const output = read.output;
541
+ return output === void 0 ? {
542
+ run: reference,
543
+ params,
544
+ conclusion: read.conclusion
545
+ } : {
546
+ run: reference,
547
+ params,
548
+ conclusion: read.conclusion,
549
+ output
550
+ };
551
+ };
552
+ /** What a start asks at its step's place, with `where` and `params` only when given. */
553
+ const startAt = (place, check, where, params) => {
554
+ if (where === void 0) return params === void 0 ? {
555
+ place,
556
+ check
557
+ } : {
558
+ place,
559
+ check,
560
+ params
561
+ };
562
+ return params === void 0 ? {
563
+ place,
564
+ check,
565
+ where
566
+ } : {
567
+ place,
568
+ check,
569
+ where,
570
+ params
571
+ };
572
+ };
573
+ /** What a start's key may be: see `CheckRunOptions['key']`. */
574
+ const startKey = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
575
+ /** What to give in place of params JSON can't hold, by the path `paramsOf` answers. */
576
+ const jsonStep = (at) => (at === "" ? "Give a check's params as a plain object of JSON values" : `Give ${at} as JSON: a string, a finite number, true, false, null, or a list or plain object of them`).slice(0, whyLength);
577
+ /**
578
+ * A start's or a need's params as JSON holds them, an `undefined` field left out, so what's sent
579
+ * is what's digested, and a field left out runs each of its `oneOf` values. Params JSON can't hold
580
+ * as they are are refused before anything is digested or started.
581
+ */
582
+ const jsonOf = (name, params) => {
583
+ if (params === void 0) return void 0;
584
+ const copy = paramsOf(params);
585
+ if ("notJson" in copy) throw new CheckRunRefused(name, {
586
+ kind: "refused",
587
+ why: "refused",
588
+ nextStep: jsonStep(copy.notJson)
589
+ });
590
+ return copy.params;
591
+ };
592
+ /**
593
+ * Waits for a start that waits on called workflows to go on or be refused: each round reads how
594
+ * it stands, in a step, then waits for its end, an hour at a time. ref0 sends its end, and a wait
595
+ * that times out reads again, for as long as ref0's record says it waits. Each read's name carries
596
+ * the waiting start's ID and its round, so a replay reads the same. Its runs, or its refusal,
597
+ * thrown.
598
+ */
599
+ const waitedOf = async (step, name, start) => {
600
+ const { sink } = runFor(name);
601
+ for (let round = 1;; round += 1) {
602
+ const state = await step.do(`ref0: waited ${name} ${start} ${round}`, ref0Steps, async () => await settle(sink.startState({
603
+ check: name,
604
+ start
605
+ })));
606
+ if (state.kind === "refused") throw new CheckRunRefused(name, state);
607
+ if (state.kind === "started") return state.runs;
608
+ await step.waitForEvent(`ref0: wait ${start} ${round}`, {
609
+ type: `start-${start}`,
610
+ timeout: waitTimeout
611
+ }).catch(() => void 0);
612
+ }
613
+ };
614
+ /**
615
+ * Starts a check's runs in one step of its own. Its name carries the start's identity: its key,
616
+ * if any, and a digest of its `where`, params and key, since Workflows tells steps of one name
617
+ * apart only by the order they're reached in, and concurrent starts reach theirs in another order
618
+ * on a replay. Starts that ask the same without a key are alike to Workflows, so on a replay each
619
+ * may get the other's runs, which differ if the head moved between them: see
620
+ * `CheckRunOptions['key']`. A start whose check's start rule makes it wait waits here, in the
621
+ * run, until it goes on or is refused.
622
+ */
623
+ const startOf = async (step, name, options = {}) => {
624
+ const { sink } = runFor(name);
625
+ const { key } = options;
626
+ if (key !== void 0 && !startKey.test(key)) throw new CheckRunRefused(name, {
627
+ kind: "refused",
628
+ why: "refused",
629
+ nextStep: `A start's key is up to 64 letters, digits, ., _, : or -, from a letter or digit, not ${JSON.stringify(key).slice(0, 80)}.`
630
+ });
631
+ const params = jsonOf(name, options.params);
632
+ const asked = await digestOf(sortedJson({
633
+ where: options.where ?? null,
634
+ params: params ?? {},
635
+ key: key ?? null
636
+ }));
637
+ const answer = await step.do(key === void 0 ? `ref0: start ${name} ${asked}` : `ref0: start ${name} ${key} ${asked}`, ref0Steps, async (context) => await settle(sink.startChecks(startAt({
638
+ step: context.step.name,
639
+ count: context.step.count
640
+ }, name, options.where, params))));
641
+ if (answer.kind === "refused") throw new CheckRunRefused(name, answer);
642
+ return (answer.kind === "waiting" ? await waitedOf(step, name, answer.start) : answer.runs).map((run) => referenceOf(name, run));
643
+ };
644
+ /** A wait's identity: the digest of its runs, sorted, one to a line. */
645
+ const waitIdOf = async (runs) => await digestOf(runs.toSorted().join("\n"));
646
+ /**
647
+ * Waits until a run's end wakes this invocation, or an hour passes. Every wait on the run shares
648
+ * one `step.waitForEvent`, since ref0 sends the end once and it wakes one waiter: one that's
649
+ * going is joined, and one that woke stays woken. Only one that ran out its hour makes the next,
650
+ * its count one more, so each one's name is the same on a replay, whichever wait makes it.
651
+ */
652
+ const wokenBy = (step, waiters, run) => {
653
+ const held = waiters.get(run);
654
+ if (held !== void 0 && !held.timedOut) return held.woken;
655
+ const waiter = {
656
+ count: (held?.count ?? 0) + 1,
657
+ woken: Promise.resolve(),
658
+ timedOut: false
659
+ };
660
+ waiter.woken = step.waitForEvent(`ref0: wait ${run} ${waiter.count}`, {
661
+ type: `check-${run}`,
662
+ timeout: waitTimeout
663
+ }).then(() => void 0, () => {
664
+ waiter.timedOut = true;
665
+ });
666
+ waiters.set(run, waiter);
667
+ return waiter.woken;
668
+ };
669
+ /**
670
+ * Waits for runs to end, then reads how each ended. Each round reads whether they've ended, in a
671
+ * step, then waits for each still going, an hour at a time, as `wokenBy` says; an end ref0 sends
672
+ * wakes the wait, and a wait that times out reads again, for as long as ref0's record says a run
673
+ * is going. Then the results are read a page at a time, each page a step well inside Workflows'
674
+ * 1 MiB. Each read's name carries the wait's own identity, a digest of its runs, and its round or
675
+ * page, since Workflows tells steps of one name apart only by the order they're reached in, and
676
+ * concurrent waits reach theirs in another order on a replay.
677
+ */
678
+ const waitOf = async (step, name, pending) => {
679
+ const { sink, waiters } = runFor(name);
680
+ const foreign = pending.find((reference) => reference.check !== name);
681
+ if (foreign !== void 0) throw new Error(`${foreign.run} is a run of ${foreign.check}, not ${name}`);
682
+ const runs = [...new Set(pending.map((reference) => reference.run))].toSorted();
683
+ if (runs.length === 0) return [];
684
+ const wait = await waitIdOf(runs);
685
+ const missing = (found) => {
686
+ const unknown = runs.find((run) => !found.some((each) => each.run === run));
687
+ if (unknown !== void 0) throw new Error(`${unknown} isn't a run of ${name} that this workflow run started`);
688
+ };
689
+ for (let round = 1;; round += 1) {
690
+ const states = await step.do(`ref0: states ${name} ${wait} ${round}`, ref0Steps, async () => await settle(sink.checkStates({
691
+ check: name,
692
+ runs
693
+ })));
694
+ if (states.kind === "refused") throw new CheckRunRefused(name, states);
695
+ missing(states.runs);
696
+ const going = states.runs.filter((run) => !run.ended);
697
+ if (going.length === 0) break;
698
+ await Promise.all(going.map(async ({ run }) => await wokenBy(step, waiters, run)));
699
+ }
700
+ const read = [];
701
+ for (let from = 0, page = 1; from !== void 0; page += 1) {
702
+ const at = from;
703
+ const got = await step.do(`ref0: results ${name} ${wait} ${page}`, ref0Steps, async () => await settle(sink.checkResults({
704
+ check: name,
705
+ runs,
706
+ from: at
707
+ })));
708
+ if (got.kind === "refused") throw new CheckRunRefused(name, got);
709
+ read.push(...got.runs);
710
+ from = got.next;
711
+ }
712
+ missing(read);
713
+ return pending.map((reference) => {
714
+ const found = read.find((run) => run.run === reference.run);
715
+ if (found === void 0) throw new Error(`${reference.run} wasn't read`);
716
+ return resultOf(reference, found);
717
+ });
718
+ };
719
+ /**
720
+ * What an import of `.ref0/checks/<name>.ts` is, as ref0's bundler writes it; with `project`,
721
+ * an import of `ref0:project/checks/<name>`, one of the project repo's checks, which a gate needs
722
+ * and nothing runs from here yet.
723
+ */
724
+ const checkHandle = (name, source) => {
725
+ const notYet = () => {
726
+ throw new Error(`ref0 can't run the project's check ${name} from here yet`);
727
+ };
728
+ const runs = source === "project" ? {
729
+ run: notYet,
730
+ start: notYet,
731
+ wait: notYet
732
+ } : {
733
+ run: async (step, options) => await waitOf(step, name, await startOf(step, name, options)),
734
+ start: async (step, options) => ({ pending: await startOf(step, name, options) }),
735
+ wait: async (step, pending) => await waitOf(step, name, pending)
736
+ };
737
+ const from = source === "project" ? { source } : {};
738
+ return Object.assign((given) => {
739
+ const params = jsonOf(name, given);
740
+ return needFrom(params === void 0 ? {
741
+ kind: "check",
742
+ name,
743
+ ...from
744
+ } : {
745
+ kind: "check",
746
+ name,
747
+ params,
748
+ ...from
749
+ });
750
+ }, runs);
751
+ };
752
+ /** Each `oneOf` field's values, by its schema: the SDK's own, so no zod registry is needed. */
753
+ const marks = /* @__PURE__ */ new WeakMap();
754
+ /**
755
+ * A field's `oneOf` mark: its own, or that of the schema it was cloned from, as `.min()` clones
756
+ * one, or of the schema it wraps, as `.optional()` and `.transform()` wrap one. It reads zod's
757
+ * own links, so a check reaches no zod it doesn't import.
758
+ */
759
+ const markOf = (field) => {
760
+ const mark = marks.get(field);
761
+ if (mark !== void 0) return mark;
762
+ const { def, parent } = field._zod;
763
+ const inner = "innerType" in def ? def.innerType : "in" in def ? def.in : parent;
764
+ return inner === void 0 ? void 0 : markOf(inner);
765
+ };
766
+ /** The most runs one start may ask for, as `maxCheckRunsPerStart` in `@ref0/contracts` says. */
767
+ const maxRuns = 256;
768
+ /** What a refusal may say, as `CheckExpansion` in `@ref0/contracts` keeps it. */
769
+ const whyLength = 1e3;
770
+ /** A start's runs, by its params' `oneOf` fields: see `CheckOwn['expand']`. */
771
+ const expansionOf = async (params, asked) => {
772
+ const fields = params?.shape ?? {};
773
+ const unknown = Object.keys(asked).filter((key) => !Object.hasOwn(fields, key));
774
+ if (unknown.length > 0) return { refused: `It has no param ${unknown.join(", ")}`.slice(0, whyLength) };
775
+ let cells = [asked];
776
+ for (const [key, field] of Object.entries(fields)) {
777
+ const mark = Object.hasOwn(asked, key) ? void 0 : markOf(field);
778
+ if (mark === void 0) continue;
779
+ if ("computed" in mark) return { refused: `${key}'s values are read from the tree, which ref0 doesn't do yet: give ${key}` };
780
+ const distinct = [...new Set(mark.listed)];
781
+ if (distinct.length === 0) return { refused: `${key} has no values` };
782
+ if (cells.length * distinct.length > maxRuns) return { refused: `It asks for more than ${maxRuns} runs: give ${key}, or another param` };
783
+ cells = cells.flatMap((cell) => distinct.map((value) => ({
784
+ ...cell,
785
+ [key]: value
786
+ })));
787
+ }
788
+ if (params === void 0) return { runs: cells };
789
+ const runs = [];
790
+ for (const cell of cells) {
791
+ const parsed = await params.safeParseAsync(cell);
792
+ if (!parsed.success) return { refused: issueOf(parsed.error, "Its params are invalid") };
793
+ const copy = paramsOf(parsed.data);
794
+ if ("notJson" in copy) return { refused: (copy.notJson === "" ? "Its params aren't JSON once parsed" : `Its params aren't JSON once parsed: ${copy.notJson} isn't`).slice(0, whyLength) };
795
+ runs.push(copy.params);
796
+ }
797
+ return { runs };
798
+ };
799
+ /** The most needs one run's result may add, as `maxResultNeeds` in `@ref0/contracts` says. */
800
+ const maxResultNeeds = 16;
801
+ /**
802
+ * A run's result needs as ref0 keeps them, or why they can't be: each one the SDK made, and none a
803
+ * check's, since a result can't ask for runs mid-version.
804
+ */
805
+ const resultNeedsOf = (needs) => {
806
+ if (needs === void 0) return { needs: [] };
807
+ if (!isList(needs)) return { why: "Its needs aren't a list" };
808
+ if (needs.length > maxResultNeeds) return { why: `Its result adds past ${maxResultNeeds} needs` };
809
+ const kept = [];
810
+ for (const need of needs) {
811
+ const made = madeOf(need)?.need;
812
+ if (made === void 0) return { why: "Its needs hold something that isn't one: return approval() and review()" };
813
+ if (made.kind === "check" || made.kind === "checks") return { why: `Its result can't need ${made.kind === "check" ? `the check ${made.name}` : "checks"}: return approval() and review()` };
814
+ kept.push(made);
815
+ }
816
+ return { needs: kept };
817
+ };
818
+ /** One run of a check: see `CheckOwn['run']`. */
819
+ const runOf$1 = (spec) => async (input, step, host) => {
820
+ const params = input.params;
821
+ const result = await spec.run({
822
+ params,
823
+ step: settledStep(step, attemptScoped),
824
+ repo: treeOf(host)
825
+ });
826
+ const checked = spec.output === void 0 ? {
827
+ value: result.output,
828
+ issues: void 0
829
+ } : await spec.output["~standard"].validate(result.output);
830
+ if (checked.issues !== void 0) return {
831
+ error: "invalid output",
832
+ why: checked.issues.map((issue) => issue.message).join("; ").slice(0, whyLength)
833
+ };
834
+ const read = resultNeedsOf(result.needs);
835
+ if ("why" in read) return {
836
+ error: "invalid output",
837
+ why: read.why
838
+ };
839
+ const { needs } = read;
840
+ const judged = needs.length === 0 ? { conclusion: result.conclusion } : {
841
+ conclusion: result.conclusion,
842
+ needs
843
+ };
844
+ if (checked.value === void 0) return judged;
845
+ return {
846
+ ...judged,
847
+ output: checked.value
848
+ };
849
+ };
850
+ /**
851
+ * A check: what it is, its params, when it runs, and the work of one run. Export it as its
852
+ * file's default, `.ref0/checks/<name>.ts`, named by its file.
853
+ */
854
+ const check = (spec) => Object.assign(checkHandle("this check"), { "~ref0": {
855
+ version: 1,
856
+ expand: async (asked) => await expansionOf(spec.params, asked),
857
+ start: async (context) => await startAnswerOf(spec.start, context),
858
+ run: runOf$1(spec)
859
+ } });
860
+ /**
861
+ * A param that's one check run for each of its values, and a grid with several. It's a zod 4
862
+ * string schema, so `.ref0/package.json` needs zod 4, the one its `z.object` comes from.
863
+ */
864
+ const oneOf = (values) => {
865
+ const schema = z.string();
866
+ marks.set(schema, Array.isArray(values) ? { listed: values } : { computed: true });
867
+ return schema;
868
+ };
869
+ //#endregion
870
+ //#region src/path-patterns.ts
871
+ const segmentOf = (text) => {
872
+ if (text === "**") return { kind: "any" };
873
+ if (text.includes("?")) return {
874
+ kind: "wild",
875
+ chars: Array.from(text),
876
+ parts: void 0
877
+ };
878
+ return text.includes("*") ? {
879
+ kind: "wild",
880
+ chars: Array.from(text),
881
+ parts: text.split("*")
882
+ } : {
883
+ kind: "literal",
884
+ text
885
+ };
886
+ };
887
+ /**
888
+ * Whether a segment matches the literals between a pattern's stars: the first at its start, the
889
+ * last at its end, and each between them in order, the earliest place each fits leaving the
890
+ * most room for the rest.
891
+ */
892
+ const partsMatch = (parts, segment) => {
893
+ const head = parts[0] ?? "";
894
+ const tail = parts.at(-1) ?? "";
895
+ const end = segment.length - tail.length;
896
+ if (end < head.length || !segment.startsWith(head) || !segment.endsWith(tail)) return false;
897
+ let at = head.length;
898
+ for (const part of parts.slice(1, -1)) {
899
+ const found = segment.indexOf(part, at);
900
+ if (found === -1 || found + part.length > end) return false;
901
+ at = found + part.length;
902
+ }
903
+ return true;
904
+ };
905
+ /**
906
+ * Whether one segment matches a wildcard segment's characters, `*` and `?` within it: in time
907
+ * linear in each, since a `*` reaches everything from the first place it can start.
908
+ */
909
+ const wildMatches = (pattern, segment) => {
910
+ let reach = new Uint8Array(segment.length + 1);
911
+ let next = new Uint8Array(segment.length + 1);
912
+ reach[0] = 1;
913
+ for (const character of pattern) {
914
+ next.fill(0);
915
+ if (character === "*") {
916
+ const first = reach.indexOf(1);
917
+ if (first !== -1) next.fill(1, first);
918
+ } else for (let j = 0; j < segment.length; j++) if (reach[j] === 1 && (character === "?" || character === segment[j])) next[j + 1] = 1;
919
+ [reach, next] = [next, reach];
920
+ if (reach.indexOf(1) === -1) return false;
921
+ }
922
+ return reach[segment.length] === 1;
923
+ };
924
+ /** Whether one segment matches a wildcard segment. */
925
+ const wildSegmentMatches = (wild, segment) => wild.parts === void 0 ? wildMatches(wild.chars, Array.from(segment)) : partsMatch(wild.parts, segment);
926
+ /** Whether a path's segments match a compiled pattern's. */
927
+ const segmentsMatch = (pattern, path) => {
928
+ const width = path.length + 1;
929
+ const tried = new Uint8Array((pattern.length + 1) * width);
930
+ const from = (p, s) => {
931
+ const kept = tried[p * width + s];
932
+ if (kept !== 0) return kept === 1;
933
+ const segment = pattern[p];
934
+ const here = path[s];
935
+ let matched;
936
+ if (segment === void 0) matched = s === path.length;
937
+ else if (segment.kind === "any") matched = from(p + 1, s) || s < path.length && from(p, s + 1);
938
+ else if (here === void 0) matched = false;
939
+ else if (segment.kind === "literal") matched = segment.text === here && from(p + 1, s + 1);
940
+ else matched = wildSegmentMatches(segment, here) && from(p + 1, s + 1);
941
+ tried[p * width + s] = matched ? 1 : 2;
942
+ return matched;
943
+ };
944
+ return from(0, 0);
945
+ };
946
+ /**
947
+ * Whether a path matches any of the patterns, each compiled once. The shapes gates use most for
948
+ * a stack's files are read straight from the path's segments, as the general match would read
949
+ * them: `**\/name` is its last segment, `**\/name/**` any segment, and `**\/name/**\/*` any
950
+ * folder it's in.
951
+ */
952
+ const pathMatcher = (patterns) => {
953
+ const names = /* @__PURE__ */ new Set();
954
+ const segments = /* @__PURE__ */ new Set();
955
+ const folders = /* @__PURE__ */ new Set();
956
+ const lastWild = [];
957
+ const general = [];
958
+ for (const pattern of patterns) {
959
+ const compiled = pattern.split("/").map(segmentOf);
960
+ const [first, second, third, fourth] = compiled;
961
+ const anyFirst = first?.kind === "any";
962
+ if (anyFirst && compiled.length === 2 && second?.kind === "literal") names.add(second.text);
963
+ else if (anyFirst && compiled.length === 2 && second?.kind === "wild") lastWild.push(second);
964
+ else if (anyFirst && compiled.length === 3 && second?.kind === "literal" && third?.kind === "any") segments.add(second.text);
965
+ else if (anyFirst && compiled.length === 4 && second?.kind === "literal" && third?.kind === "any" && fourth?.kind === "wild" && fourth.chars.length === 1 && fourth.chars[0] === "*") folders.add(second.text);
966
+ else general.push(compiled);
967
+ }
968
+ return (path) => {
969
+ const parts = path.split("/");
970
+ const last = parts.length - 1;
971
+ const name = parts[last] ?? "";
972
+ return names.has(name) || parts.some((part, index) => segments.has(part) || index < last && folders.has(part)) || lastWild.some((wild) => wildSegmentMatches(wild, name)) || general.some((pattern) => segmentsMatch(pattern, parts));
973
+ };
974
+ };
975
+ //#endregion
976
+ //#region src/gate.ts
977
+ /** What the gate is given, from the version's summary and the hosts of the change's trees. */
978
+ const contextOf = (summary, trees) => {
979
+ const paths = summary.files.flatMap((file) => file.from === null ? [file.path] : [file.path, file.from]);
980
+ return {
981
+ change: {
982
+ files: summary.files,
983
+ complete: summary.complete,
984
+ permissions: [...new Set(summary.files.flatMap((file) => file.permissions))].sort(),
985
+ lines: {
986
+ added: summary.size.added,
987
+ removed: summary.size.deleted
988
+ },
989
+ touches: (...patterns) => paths.some(pathMatcher(patterns)),
990
+ only: (...patterns) => paths.length > 0 && paths.every(pathMatcher(patterns)),
991
+ author: summary.author,
992
+ before: treeOf(trees.before),
993
+ after: treeOf(trees.after)
994
+ },
995
+ plan: summary.plan
996
+ };
997
+ };
998
+ /** The most needs the gate may return, as `maxGateNeeds` in `@ref0/contracts` says. */
999
+ const maxNeeds = 128;
1000
+ /** How deep the gate's lists may nest. */
1001
+ const maxDepth = 16;
1002
+ /** What the gate returned, flattened: each need the SDK made, or a throw for anything else. */
1003
+ const flatten = (returned) => {
1004
+ const needs = [];
1005
+ const walk = (list, depth) => {
1006
+ if (!isList(list)) throw new TypeError("The gate returns a list of needs");
1007
+ if (depth > maxDepth) throw new TypeError(`The gate's lists nest past ${maxDepth} deep`);
1008
+ for (const item of list) {
1009
+ if (item === false || item === null || item === void 0) continue;
1010
+ if (isList(item)) {
1011
+ walk(item, depth + 1);
1012
+ continue;
1013
+ }
1014
+ const made = madeOf(item);
1015
+ if (made === void 0) throw new TypeError("The gate returned something that isn't a need: return checks, approval() and review()");
1016
+ if (needs.length >= maxNeeds) throw new TypeError(`The gate returned past ${maxNeeds} needs`);
1017
+ needs.push({
1018
+ need: made.need,
1019
+ stack: made.stack
1020
+ });
1021
+ }
1022
+ };
1023
+ walk(returned, 0);
1024
+ return needs;
1025
+ };
1026
+ /** The gate: given a change, what it needs. Export it as `.ref0/gate.ts`'s default. */
1027
+ const gate = (decide) => ({
1028
+ decide,
1029
+ "~ref0": {
1030
+ version: 1,
1031
+ needs: async (input, trees) => ({ needs: flatten(await decide(contextOf(input.summary, trees))) })
1032
+ }
1033
+ });
1034
+ /**
1035
+ * An approval from someone holding that repo permission: `approval('billing')`. Admins hold every
1036
+ * permission.
1037
+ */
1038
+ const approval = (permission) => needFrom({
1039
+ kind: "permission",
1040
+ name: permission
1041
+ });
1042
+ /** A whole review covering the change, with no blocking finding open. ref0's floor has one. */
1043
+ const review = () => needFrom({ kind: "review" });
1044
+ /**
1045
+ * A check's need that a local run meets too: `local(unit())`. A run of the same check-run
1046
+ * identity, the check's code, its params and the version's expected tree, run with `ref0 run
1047
+ * --local` on an attached machine of the thread's and reported, meets each of its parts, and ref0
1048
+ * runs none of them on the change, unless another need for the part doesn't take local. ref0's
1049
+ * failure of a part still stands, the merge queue's tip still runs the check as ref0's own, and
1050
+ * when ref0's runs of a local success's identity fail twice, its machine stops answering the
1051
+ * check until an admin restores it. It takes a check's need, never a check or another need.
1052
+ */
1053
+ const local = (need) => {
1054
+ const made = madeOf(need)?.need;
1055
+ if (made?.kind !== "check") throw new TypeError("local() wraps a check's need, as local(unit()) does: never approval(), review() or a check");
1056
+ return needFrom({
1057
+ ...made,
1058
+ local: true
1059
+ });
1060
+ };
1061
+ //#endregion
1062
+ //#region src/passkey.ts
1063
+ /**
1064
+ * ref0 asked nobody, and why, as `PasskeyRefusal` in `@ref0/contracts` says. A refusal is
1065
+ * kept as its step's result, so a replay throws it again and asks nobody; `agent` is refused
1066
+ * before any step.
1067
+ */
1068
+ var PasskeyRequestRefused = class extends Error {
1069
+ why;
1070
+ constructor(why) {
1071
+ super(`ref0 asked nobody for a passkey: ${why}`);
1072
+ this.name = "PasskeyRequestRefused";
1073
+ this.why = why;
1074
+ }
1075
+ };
1076
+ /** The longest one wait for a request's end lasts before ref0's record is read again. */
1077
+ const longestWait = 36e5;
1078
+ /** What a step's code asks, as a digest: who, the statement and the deadline. */
1079
+ const askedDigest = async (options) => await digestOf(JSON.stringify([
1080
+ options.from,
1081
+ options.statement.strings,
1082
+ options.statement.values,
1083
+ options.deadline
1084
+ ]));
1085
+ /**
1086
+ * The run whose code asks, refused in words inside a step's callback, since one step can't hold
1087
+ * another, and outside a workflow run.
1088
+ */
1089
+ const runOf = () => {
1090
+ if (inStep.getStore() !== void 0) throw new Error("passkey.run is a step of its own: call it outside step.do's callback");
1091
+ const run = inRun.getStore();
1092
+ if (run === void 0) throw new Error("ref0 asks for a passkey from a workflow's code only");
1093
+ return run;
1094
+ };
1095
+ /** What `passkey.run` answers, from ref0's read of a request that ended. */
1096
+ const answerOf$1 = (answered) => {
1097
+ const answer = {
1098
+ approved: answered.approved,
1099
+ at: answered.at
1100
+ };
1101
+ if (answered.by !== void 0) answer.by = answered.by;
1102
+ return answer;
1103
+ };
1104
+ /**
1105
+ * Asks for a passkey's yes to `statement`, then waits for the answer. The request is one step,
1106
+ * named `ref0: passkey` and told from the run's others by its count, so it's reached in the order
1107
+ * the code asks: a replay waits on the same request, and one that asks something else there
1108
+ * throws `changed`. Then each round reads the request in a step and waits for its end, until its
1109
+ * deadline or an hour, whichever is first, and a wait that times out reads again.
1110
+ */
1111
+ const run = async (step, options) => {
1112
+ const { sink } = runOf();
1113
+ const { from } = options;
1114
+ if (from !== "accountable" && "agent" in from) throw new PasskeyRequestRefused("agent");
1115
+ const asking = step.do("ref0: passkey", ref0Steps, async (context) => ({
1116
+ ...await settle(sink.askPasskey({
1117
+ place: {
1118
+ step: context.step.name,
1119
+ count: context.step.count
1120
+ },
1121
+ from,
1122
+ statement: options.statement,
1123
+ deadline: options.deadline
1124
+ })),
1125
+ asked: await askedDigest(options)
1126
+ }));
1127
+ const [answer, asked] = await Promise.all([asking, askedDigest(options)]);
1128
+ if (answer.asked !== asked) throw new PasskeyRequestRefused("changed");
1129
+ if (answer.kind === "refused") throw new PasskeyRequestRefused(answer.why);
1130
+ for (let round = 1;; round += 1) {
1131
+ const state = await step.do(`ref0: passkey ${answer.request} ${round}`, ref0Steps, async () => await settle(sink.passkeyState({ request: answer.request })));
1132
+ if (state.state === "refused") throw new Error(`ref0 read ${answer.request} as ${state.why}`);
1133
+ if (state.state === "answered") return answerOf$1(state);
1134
+ const timeout = Math.min(Math.max(state.expiresAt - state.readAt + 1e3, 1e3), longestWait);
1135
+ await step.waitForEvent(`ref0: passkey wait ${answer.request} ${round}`, {
1136
+ type: `passkey-${answer.request}`,
1137
+ timeout
1138
+ }).catch(() => {});
1139
+ }
1140
+ };
1141
+ /**
1142
+ * A check's need whose result counts only once a person said yes to it with their passkey:
1143
+ * `passkey.check(local(e2e()))`, or `passkey.check(e2e(), { from: { permission: 'qa' } })` to ask
1144
+ * someone else. For each part, ref0 asks for a yes to a statement that names the run that meets it,
1145
+ * its check-run identity, its conclusion and its outputs' hash, and the part waits for that yes. A
1146
+ * newer run that counts asks again. A no or the request's day ending waits for a newer local run;
1147
+ * on a run of ref0's, or from a person who can't be asked, the check needs an admin's override.
1148
+ * When ref0's runs of an identity someone said yes over fail twice, their yes stops answering the
1149
+ * check until an admin restores them. It takes a check's need, never a check or another need, and wraps it
1150
+ * once, before or after `local`.
1151
+ */
1152
+ const check$1 = (need, options = {}) => {
1153
+ const made = madeOf(need)?.need;
1154
+ if (made?.kind !== "check") throw new TypeError("passkey.check() wraps a check's need, as passkey.check(unit()) does: never approval(), review() or a check");
1155
+ if (made.passkey !== void 0) throw new TypeError("passkey.check() wraps a need once: return another need to ask someone else too");
1156
+ return needFrom({
1157
+ ...made,
1158
+ passkey: { from: options.from ?? "accountable" }
1159
+ });
1160
+ };
1161
+ const passkey = {
1162
+ run,
1163
+ check: check$1
1164
+ };
1165
+ //#endregion
1166
+ //#region src/browse.ts
1167
+ /**
1168
+ * Where ref0 loads its browser module, beside the prelude, for a check that imports it: the path
1169
+ * contracts names, written here since the SDK's root takes only types from contracts.
1170
+ */
1171
+ const browserModule = "/ref0-browser.js";
1172
+ const loaded = async () => {
1173
+ try {
1174
+ return await import(
1175
+ /* @vite-ignore */
1176
+ browserModule
1177
+ );
1178
+ } catch {
1179
+ throw new Error("This check doesn't import @ref0/sdk/browser: import `expect` from @ref0/sdk/browser");
1180
+ }
1181
+ };
1182
+ /** The most the reports' flush and the disconnect may each take, which only help the close. */
1183
+ const browseCleanupMs = 5e3;
1184
+ /**
1185
+ * Runs a cleanup step, ignoring its failure, and, given `ms`, giving up on it after that long, so
1186
+ * a stalled step holds up none after it.
1187
+ */
1188
+ const quietly = async (step, ms) => {
1189
+ let timer;
1190
+ try {
1191
+ const stalled = ms === void 0 ? [] : [new Promise((given) => {
1192
+ timer = setTimeout(given, ms);
1193
+ })];
1194
+ await Promise.race([step(), ...stalled]);
1195
+ } catch {} finally {
1196
+ clearTimeout(timer);
1197
+ }
1198
+ };
1199
+ /**
1200
+ * Lends `fn` a browser for `session`, and closes it however `fn` ends: the reports flushed and the
1201
+ * browser disconnected, each within `cleanupMs`, then the lease's close, durable in ref0, always
1202
+ * asked, and the lease disposed after it whatever happens.
1203
+ */
1204
+ const browseWith = async (opener, session, fn, load = loaded, cleanupMs = browseCleanupMs) => {
1205
+ const browser = await load();
1206
+ const lease = await opener.browse(session);
1207
+ try {
1208
+ const opened = await browser.__ref0Open(lease);
1209
+ try {
1210
+ return await fn(opened.browser);
1211
+ } finally {
1212
+ await quietly(opened.flush, cleanupMs);
1213
+ await quietly(opened.disconnect, cleanupMs);
1214
+ }
1215
+ } finally {
1216
+ try {
1217
+ await quietly(async () => await lease.close());
1218
+ } finally {
1219
+ lease[Symbol.dispose]?.();
1220
+ }
1221
+ }
1222
+ };
1223
+ //#endregion
1224
+ //#region src/sandbox.ts
1225
+ /** A sandbox step's timeout and retries unless its options say. */
1226
+ const defaultTimeout = "10 minutes";
1227
+ const defaultRetries = 2;
1228
+ const durationUnits = /* @__PURE__ */ new Map([
1229
+ ["second", 1e3],
1230
+ ["minute", 6e4],
1231
+ ["hour", 36e5]
1232
+ ]);
1233
+ /** A duration's milliseconds: `'10 minutes'` is 600,000. */
1234
+ const millisOf = (text) => {
1235
+ const match = /^(\d+(?:\.\d+)?) (second|minute|hour)s?$/.exec(text);
1236
+ const unit = match?.[2] === void 0 ? void 0 : durationUnits.get(match[2]);
1237
+ if (match === null || unit === void 0) throw new Error(`Not a duration: ${text}`);
1238
+ return Math.ceil(Number(match[1]) * unit);
1239
+ };
1240
+ /**
1241
+ * A command's answer in the sandbox, or the failure of one that gave none, and its push. It
1242
+ * serves nothing.
1243
+ */
1244
+ const execIn = (at, sandbox) => ({
1245
+ push: async ({ message }) => {
1246
+ const place = sandboxPlace()?.place;
1247
+ if (place === void 0) throw new Error("A sandbox pushes only inside its step");
1248
+ return await settle(at.sink.pushSandbox({
1249
+ place,
1250
+ attempt: at.attempt,
1251
+ sandbox,
1252
+ message
1253
+ }));
1254
+ },
1255
+ exec: async (command, options = {}) => {
1256
+ const answer = await settle(at.sink.execSandbox({
1257
+ sandbox,
1258
+ command,
1259
+ ...options.cwd !== void 0 && { cwd: options.cwd },
1260
+ ...options.env !== void 0 && { env: { ...options.env } },
1261
+ ...options.timeout !== void 0 && { timeoutMs: millisOf(options.timeout) }
1262
+ }));
1263
+ if (answer.kind === "failed") throw new Error(`The command gave no answer, ${answer.why}: ${answer.message}`);
1264
+ return {
1265
+ exitCode: answer.exitCode,
1266
+ stdout: answer.stdout,
1267
+ stderr: answer.stderr,
1268
+ startedAt: answer.startedAt,
1269
+ durationMs: answer.durationMs
1270
+ };
1271
+ },
1272
+ serve: async () => await Promise.reject(/* @__PURE__ */ new Error("A workflow's sandbox serves nothing: `serve` is for checks")),
1273
+ browse: async () => await Promise.reject(/* @__PURE__ */ new Error("A workflow's sandbox browses nothing: `browse` is for checks"))
1274
+ });
1275
+ /**
1276
+ * Opens a workflow's sandbox for this attempt of its step, gives it to `work`, and closes it when
1277
+ * `work` ends or throws. A step's retry opens a new one. A refusal throws, since `work` has
1278
+ * nothing to run in.
1279
+ */
1280
+ const runInWorkflow = async (at, sandbox, options, work) => {
1281
+ if (options.paths !== void 0) throw new Error("A workflow's sandbox holds the whole tree: `paths` is for checks");
1282
+ if (options.tree === void 0) throw new Error("A workflow's sandbox needs a tree: `'main'` or a fork's answer");
1283
+ const opened = await settle(at.sink.openSandbox({
1284
+ place: at.place,
1285
+ attempt: at.attempt,
1286
+ sandbox,
1287
+ tree: options.tree,
1288
+ ...options.size !== void 0 && { size: options.size },
1289
+ timeoutMs: millisOf(options.timeout ?? defaultTimeout)
1290
+ }));
1291
+ if (opened.kind === "refused") throw new Error(`ref0 refused the sandbox ${sandbox}: ${opened.why}`);
1292
+ try {
1293
+ return await work(execIn(at, opened.sandbox));
1294
+ } finally {
1295
+ await settle(at.sink.closeSandbox(opened.sandbox));
1296
+ }
1297
+ };
1298
+ /**
1299
+ * What an import of `.ref0/sandboxes/<name>.ts` is, as ref0's bundler writes it. Its `run` is
1300
+ * `step.do(name, { timeout, retries })` around opening the sandbox once for each attempt, `work`,
1301
+ * and closing it in `finally`: a replay returns the step's kept output and opens nothing. In a
1302
+ * workflow's step, ref0's Worker opens it, through the run's sink.
1303
+ */
1304
+ const sandboxHandle = (name) => {
1305
+ const run = async (step, stepName, options, work) => await step.do(stepName, {
1306
+ timeout: options.timeout ?? defaultTimeout,
1307
+ retries: {
1308
+ limit: options.retries ?? defaultRetries,
1309
+ delay: "10 seconds",
1310
+ backoff: "exponential"
1311
+ }
1312
+ }, async () => {
1313
+ const at = sandboxPlace();
1314
+ if (at !== void 0) return await runInWorkflow(at, name, options, work);
1315
+ const opener = attemptOpener();
1316
+ if (opener === void 0) throw new Error(`${name} opens only in a step of a check run`);
1317
+ if (options.tree !== void 0) throw new Error(`${name} opens in its check run's own tree: \`tree\` is for workflows`);
1318
+ const asked = { sandbox: name };
1319
+ if (options.paths !== void 0) asked.paths = [...options.paths];
1320
+ if (options.size !== void 0) asked.size = options.size;
1321
+ const { session } = await settle(opener.open(asked));
1322
+ try {
1323
+ return await work({
1324
+ exec: async (command, given = {}) => await settle(opener.exec(session, command, given)),
1325
+ push: async () => {
1326
+ throw new Error("A check's sandbox pushes nothing: `push` is a workflow's");
1327
+ },
1328
+ serve: async (command, given) => await settle(opener.serve(session, command, given)),
1329
+ browse: async (fn) => await browseWith(opener, session, fn)
1330
+ });
1331
+ } finally {
1332
+ await settle(opener.close(session)).catch(() => void 0);
1333
+ }
1334
+ });
1335
+ return { run };
1336
+ };
1337
+ /**
1338
+ * A sandbox: its image, size, cached setup, and the hosts setup may reach. Export it as its
1339
+ * file's default, `.ref0/sandboxes/<name>.ts`, named by its file.
1340
+ */
1341
+ const sandbox = (_spec) => sandboxHandle("this sandbox");
1342
+ //#endregion
1343
+ //#region src/start-rules.ts
1344
+ /** Lets a start make its run, unless another item of the rule's list refuses it. */
1345
+ const allow = () => ({ kind: "allow" });
1346
+ /** Refuses a start, in words: `` refuse(text`Ten e2e runs a day each. Reuse a result instead`) ``. */
1347
+ const refuse = (text) => ({
1348
+ kind: "refuse",
1349
+ text
1350
+ });
1351
+ //#endregion
1352
+ //#region src/workflow.ts
1353
+ /**
1354
+ * Cloudflare's `step`, with each `do` callback run in a scope that knows its step and attempt,
1355
+ * so an action inside it gets its place.
1356
+ */
1357
+ const durableStep = (step, sink) => settledStep(step, (callback) => (context) => inStep.run({
1358
+ step: context.step.name,
1359
+ count: context.step.count,
1360
+ attempt: context.attempt,
1361
+ sink,
1362
+ calls: 0,
1363
+ connectionCalls: 0
1364
+ }, () => callback(context)));
1365
+ /**
1366
+ * ref0's actions, each sent with its place, or refused at once outside a step. A fork's place is
1367
+ * counted among the step's actions, in the order they're asked.
1368
+ */
1369
+ const actionsOf = (sink) => {
1370
+ const placeOf = () => {
1371
+ const at = inStep.getStore();
1372
+ if (at === void 0) throw new Error("ref0's actions run inside a step, in step.do's callback, since a run's code outside one runs again on each replay");
1373
+ const place = {
1374
+ step: at.step,
1375
+ count: at.count,
1376
+ call: at.calls
1377
+ };
1378
+ at.calls += 1;
1379
+ return place;
1380
+ };
1381
+ const send = (request) => settle(sink.act({
1382
+ place: placeOf(),
1383
+ request
1384
+ }));
1385
+ return {
1386
+ comments: { create: (comment) => send({
1387
+ verb: "comment",
1388
+ ...comment
1389
+ }) },
1390
+ threads: { start: (start) => send({
1391
+ verb: "thread.start",
1392
+ ...start
1393
+ }) },
1394
+ forks: { create: (options = {}) => settle(sink.createFork({
1395
+ place: placeOf(),
1396
+ ...options
1397
+ })) },
1398
+ pr: {
1399
+ needs: { add: (pullRequest, need, options = {}) => {
1400
+ const made = madeOf(need);
1401
+ if (made === void 0) throw new TypeError("pr.needs.add takes a need: a check's handle called, approval() or review()");
1402
+ return send({
1403
+ verb: "pr.needs.add",
1404
+ pullRequest,
1405
+ need: made.need,
1406
+ ...options
1407
+ });
1408
+ } },
1409
+ open: (options) => {
1410
+ const attempt = inStep.getStore()?.attempt ?? 1;
1411
+ return settle(sink.openPullRequest({
1412
+ place: placeOf(),
1413
+ attempt,
1414
+ ...options
1415
+ }));
1416
+ }
1417
+ }
1418
+ };
1419
+ };
1420
+ /** What a call came to, in a few words, for the run's trace. */
1421
+ const callWords = (answer) => answer.kind === "answered" ? "answered" : `refused, ${answer.why}`;
1422
+ /**
1423
+ * The connections the workflow declared, each operation sent with its place, inside a step, and
1424
+ * written to the run's trace once answered: which connection and operation, and what it came
1425
+ * to. Never what it answered.
1426
+ */
1427
+ const connectionsOf = (sink, declared) => {
1428
+ const send = async (connection, input) => {
1429
+ const at = inStep.getStore();
1430
+ if (at === void 0) throw new Error("ref0's calls run inside a step, in step.do's callback, since a run's code outside one runs again on each replay");
1431
+ const place = {
1432
+ step: at.step,
1433
+ count: at.count,
1434
+ call: at.connectionCalls
1435
+ };
1436
+ at.connectionCalls += 1;
1437
+ const answer = await settle(sink.callConnection({
1438
+ place,
1439
+ connection,
1440
+ method: "getIssue",
1441
+ input
1442
+ }));
1443
+ console.log(`ref0: ${connection}.getIssue ${callWords(answer)}`);
1444
+ return answer;
1445
+ };
1446
+ const entries = Object.entries(declared ?? {}).map(([connection, methods]) => [connection, methods.includes("getIssue") ? { getIssue: async (input) => await send(connection, input) } : {}]);
1447
+ return Object.fromEntries(entries);
1448
+ };
1449
+ /**
1450
+ * A call's input, as a called workflow's `input` parses it, and its title from that: see
1451
+ * `WorkflowOwn['called']`.
1452
+ */
1453
+ const calledOf = async (spec, given) => {
1454
+ const parsed = await spec.input.safeParseAsync(given);
1455
+ if (!parsed.success) return {
1456
+ kind: "invalid",
1457
+ why: issueOf(parsed.error, "Its input is invalid")
1458
+ };
1459
+ const copy = paramsOf(parsed.data);
1460
+ if ("notJson" in copy) return {
1461
+ kind: "invalid",
1462
+ why: (copy.notJson === "" ? "Its input isn't JSON once parsed" : `Its input isn't JSON once parsed: ${copy.notJson} isn't`).slice(0, 1e3)
1463
+ };
1464
+ const title = spec.title({ input: parsed.data });
1465
+ if (!isText(title)) return {
1466
+ kind: "invalid",
1467
+ why: "Its title isn't text: give title a function that returns text`…`"
1468
+ };
1469
+ return {
1470
+ kind: "called",
1471
+ input: copy.params,
1472
+ title: {
1473
+ strings: [...title.strings],
1474
+ values: [...title.values]
1475
+ }
1476
+ };
1477
+ };
1478
+ /** The most a called run's output may be, as JSON in UTF-8, as `@ref0/contracts` says. */
1479
+ const maxOutputBytes = 65536;
1480
+ /**
1481
+ * What a called run's code came to: its output as JSON holds it, or why JSON can't hold it as it
1482
+ * is, or past its bound, which fails the run.
1483
+ */
1484
+ const answerOf = (output) => {
1485
+ if (output === void 0) return { kind: "returned" };
1486
+ const copy = outputOf(output);
1487
+ if ("notJson" in copy) return {
1488
+ kind: "invalid output",
1489
+ why: copy.notJson === "" ? "Its output isn't JSON" : `Its output isn't JSON: ${copy.notJson} isn't`.slice(0, 1e3)
1490
+ };
1491
+ const bytes = new TextEncoder().encode(JSON.stringify(copy.json)).byteLength;
1492
+ if (bytes > maxOutputBytes) return {
1493
+ kind: "invalid output",
1494
+ why: `Its output is ${bytes} bytes of JSON, past ${maxOutputBytes}: return a name or a key, not the data`
1495
+ };
1496
+ return {
1497
+ kind: "returned",
1498
+ output: copy.json
1499
+ };
1500
+ };
1501
+ /** The run's scope, for any check or called workflow its code runs through a handle. */
1502
+ const inRunOf = (sink, run) => inRun.run({
1503
+ sink,
1504
+ waiters: /* @__PURE__ */ new Map()
1505
+ }, run);
1506
+ const eventWorkflowOf = (spec) => ({ "~ref0": {
1507
+ version: 1,
1508
+ run: async (input, step, sink) => {
1509
+ if ("input" in input) throw new Error("ref0 ran an event workflow on a call's input");
1510
+ const event = input.event;
1511
+ await inRunOf(sink, () => spec.run({
1512
+ event,
1513
+ sessions: input.sessions,
1514
+ step: durableStep(step, sink),
1515
+ ref0: actionsOf(sink),
1516
+ connections: connectionsOf(sink, spec.permissions.connections)
1517
+ }));
1518
+ return { kind: "returned" };
1519
+ }
1520
+ } });
1521
+ const calledWorkflowOf = (spec) => Object.assign(workflowHandle("this workflow"), { "~ref0": {
1522
+ version: 1,
1523
+ run: async (input, step, sink) => {
1524
+ if (!("input" in input)) throw new Error("ref0 ran a called workflow on an event");
1525
+ const parsed = input.input;
1526
+ const output = await inRunOf(sink, () => spec.run({
1527
+ input: parsed,
1528
+ step: durableStep(step, sink),
1529
+ ref0: actionsOf(sink),
1530
+ connections: connectionsOf(sink, spec.permissions.connections)
1531
+ }));
1532
+ return answerOf(output);
1533
+ },
1534
+ called: async (input) => await calledOf(spec, input)
1535
+ } });
1536
+ function workflow(spec) {
1537
+ return spec.input === void 0 ? eventWorkflowOf(spec) : calledWorkflowOf(spec);
1538
+ }
1539
+ /**
1540
+ * Words to send, as a tagged template: `` text`Started a docs thread for ${event.pullRequest}` ``.
1541
+ * The template's own words are kept as written, and ref0 passes only templates written in the
1542
+ * workflow's code; each value is quoted when it's shown, so nothing from an event reads as an
1543
+ * instruction.
1544
+ */
1545
+ const text = (strings, ...values) => ({
1546
+ strings: [...strings],
1547
+ values: values.map(String)
1548
+ });
1549
+ //#endregion
1550
+ export { AskRefused, CalledRunFailed, CheckRunRefused, PasskeyRequestRefused, allow, approval, ask, check, gate, local, oneOf, passkey, refuse, review, sandbox, text, workflow };
1551
+
1552
+ //# sourceMappingURL=index.mjs.map