@fungi.computer/braid 0.0.1 → 0.2.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/cloudflare/graph-program.js +29 -7
- package/dist/code-execution.js +2 -0
- package/dist/compile.d.ts +2 -0
- package/dist/compile.js +9 -1
- package/dist/execution.d.ts +81 -0
- package/dist/execution.js +22 -0
- package/dist/git-access.d.ts +15 -15
- package/dist/git-access.js +8 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/review-text.d.ts +15 -0
- package/dist/review-text.js +40 -0
- package/dist/runtime/arming-authority.d.ts +5 -5
- package/dist/runtime/arming-authority.js +5 -5
- package/dist/runtime/git-pass-contract.d.ts +30 -9
- package/dist/runtime/git-pass-contract.js +26 -2
- package/dist/runtime/git-pass-store.d.ts +16 -13
- package/dist/runtime/git-pass-store.js +126 -50
- package/dist/runtime/git-pass.d.ts +3 -4
- package/dist/runtime/git-pass.js +3 -17
- package/dist/runtime/graph-contract.d.ts +5 -1
- package/dist/runtime/graph-run-store.d.ts +6 -0
- package/dist/runtime/ports.d.ts +11 -1
- package/dist/runtime/run-ceiling.d.ts +1 -1
- package/dist/runtime/run-owner.d.ts +89 -4
- package/dist/runtime/run-owner.js +27 -12
- package/dist/runtime/schedules.d.ts +1 -0
- package/dist/runtime/schedules.js +1 -0
- package/dist/runtime/trigger-owner.d.ts +10 -6
- package/dist/runtime/trigger-owner.js +2 -2
- package/dist/runtime/trigger-plans.d.ts +8 -0
- package/dist/runtime/trigger-run-reservations.d.ts +67 -3
- package/dist/runtime/trigger-run-reservations.js +126 -7
- package/dist/schema.d.ts +80 -8
- package/dist/schema.js +14 -2
- package/dist/validate.js +7 -0
- package/dist/workflow-id.d.ts +1 -1
- package/dist/workflow-id.js +1 -1
- package/package.json +8 -7
|
@@ -8,22 +8,22 @@ const reservationIds = defineIdFamily({
|
|
|
8
8
|
kinds: { reservation: { prefix: "wf_trigger_reservation" } },
|
|
9
9
|
});
|
|
10
10
|
const identity = z.string().min(1).max(256);
|
|
11
|
-
const triggerRunScopeSchema = z.strictObject({
|
|
11
|
+
const triggerRunScopeSchema = z.lazy(() => z.strictObject({
|
|
12
12
|
installationId: identity,
|
|
13
13
|
workflowId: workflowIdSchema,
|
|
14
14
|
commit: gitSha1Schema,
|
|
15
15
|
triggerId: z.string().min(1).max(80),
|
|
16
|
-
});
|
|
17
|
-
const reservationInputSchema = z.strictObject({
|
|
16
|
+
}));
|
|
17
|
+
const reservationInputSchema = z.lazy(() => z.strictObject({
|
|
18
18
|
scope: triggerRunScopeSchema,
|
|
19
19
|
requestId: z.string().min(1).max(1024),
|
|
20
|
-
});
|
|
21
|
-
const rowSchema = z.object({
|
|
20
|
+
}));
|
|
21
|
+
const rowSchema = z.lazy(() => z.object({
|
|
22
22
|
reservation_id: identity,
|
|
23
23
|
run_id: identity.nullable(),
|
|
24
24
|
status: z.enum(["active", "released", "terminal"]),
|
|
25
|
-
});
|
|
26
|
-
const countSchema = z.object({ count: z.number().int().nonnegative() });
|
|
25
|
+
}));
|
|
26
|
+
const countSchema = z.lazy(() => z.object({ count: z.number().int().nonnegative() }));
|
|
27
27
|
const scopeKey = (scope) => {
|
|
28
28
|
const parsed = triggerRunScopeSchema.parse(scope);
|
|
29
29
|
return JSON.stringify([
|
|
@@ -33,15 +33,134 @@ const scopeKey = (scope) => {
|
|
|
33
33
|
parsed.triggerId,
|
|
34
34
|
]);
|
|
35
35
|
};
|
|
36
|
+
const deliveryIdentitySchema = z.strictObject({
|
|
37
|
+
host: identity,
|
|
38
|
+
tenant: identity,
|
|
39
|
+
bindingId: identity,
|
|
40
|
+
eventId: identity,
|
|
41
|
+
});
|
|
42
|
+
const deliveryInputSchema = z.strictObject({
|
|
43
|
+
scope: triggerRunScopeSchema,
|
|
44
|
+
delivery: deliveryIdentitySchema,
|
|
45
|
+
cargo: z.json(),
|
|
46
|
+
limits: z.strictObject({
|
|
47
|
+
hostConcurrency: z.number().int().positive().max(20),
|
|
48
|
+
bindingConcurrency: z.number().int().positive().max(20),
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
const deliveryOwnerSchema = deliveryIdentitySchema.pick({
|
|
52
|
+
host: true,
|
|
53
|
+
tenant: true,
|
|
54
|
+
});
|
|
55
|
+
const pendingDeliverySchema = rowSchema.and(z.object({
|
|
56
|
+
scope: z.string(),
|
|
57
|
+
delivery_record: z.string().nullable(),
|
|
58
|
+
}));
|
|
59
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
36
60
|
/** The enclosing Team owner supplies SQLite; callers never select the Team. */
|
|
37
61
|
export function createTriggerRunReservations(options) {
|
|
38
62
|
const { sql } = options;
|
|
63
|
+
const now = options.now ?? Date.now;
|
|
39
64
|
sql.exec(`CREATE TABLE IF NOT EXISTS hub_trigger_run_reservations (
|
|
40
65
|
reservation_id TEXT PRIMARY KEY, scope TEXT NOT NULL, request_id TEXT NOT NULL,
|
|
41
66
|
run_id TEXT UNIQUE, status TEXT NOT NULL CHECK(status IN ('active','released','terminal')),
|
|
67
|
+
delivery_key TEXT, delivery_record TEXT, delivery_expires_at INTEGER,
|
|
68
|
+
delivery_host TEXT, delivery_tenant TEXT, delivery_binding TEXT,
|
|
69
|
+
delivery_pending INTEGER NOT NULL DEFAULT 0 CHECK(delivery_pending IN (0,1)),
|
|
42
70
|
UNIQUE(scope,request_id)
|
|
43
71
|
) STRICT`);
|
|
72
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS hub_trigger_delivery_identity
|
|
73
|
+
ON hub_trigger_run_reservations(delivery_key,delivery_expires_at)`);
|
|
74
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS hub_trigger_delivery_expiry
|
|
75
|
+
ON hub_trigger_run_reservations(delivery_expires_at)`);
|
|
76
|
+
const expireDeliveries = () => {
|
|
77
|
+
const at = now();
|
|
78
|
+
// An expired unstarted intent cannot wake forever. A recorded active run
|
|
79
|
+
// still owns its concurrency slot even after delivery deduplication expires.
|
|
80
|
+
sql.exec("UPDATE hub_trigger_run_reservations SET status='terminal',delivery_pending=0 WHERE reservation_id IN (SELECT reservation_id FROM hub_trigger_run_reservations WHERE delivery_expires_at<=? AND run_id IS NULL LIMIT 100)", at);
|
|
81
|
+
sql.exec("DELETE FROM hub_trigger_run_reservations WHERE reservation_id IN (SELECT reservation_id FROM hub_trigger_run_reservations WHERE delivery_expires_at<=? AND status!='active' AND delivery_pending=0 LIMIT 100)", at);
|
|
82
|
+
sql.exec("UPDATE hub_trigger_run_reservations SET delivery_key=NULL,delivery_record=NULL,delivery_expires_at=NULL WHERE reservation_id IN (SELECT reservation_id FROM hub_trigger_run_reservations WHERE delivery_expires_at<=? AND run_id IS NOT NULL LIMIT 100)", at);
|
|
83
|
+
sql.exec("DELETE FROM hub_trigger_run_reservations WHERE reservation_id IN (SELECT reservation_id FROM hub_trigger_run_reservations WHERE delivery_host IS NOT NULL AND delivery_expires_at IS NULL AND status!='active' AND delivery_pending=0 LIMIT 100)");
|
|
84
|
+
};
|
|
44
85
|
return {
|
|
86
|
+
/** Opaque verified cargo and delivery identity live with the run reservation. */
|
|
87
|
+
reserveWebhookDelivery(raw) {
|
|
88
|
+
const input = deliveryInputSchema.parse(raw);
|
|
89
|
+
const key = JSON.stringify(input.delivery);
|
|
90
|
+
return options.transactionSync(() => {
|
|
91
|
+
expireDeliveries();
|
|
92
|
+
const prior = sql
|
|
93
|
+
.exec("SELECT reservation_id,run_id,status FROM hub_trigger_run_reservations WHERE delivery_key=? AND delivery_expires_at>? ORDER BY delivery_expires_at DESC LIMIT 1", key, now())
|
|
94
|
+
.toArray()[0];
|
|
95
|
+
if (prior !== undefined) {
|
|
96
|
+
const row = rowSchema.parse(prior);
|
|
97
|
+
return {
|
|
98
|
+
kind: "replay",
|
|
99
|
+
reservationId: row.reservation_id,
|
|
100
|
+
runId: row.run_id,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const active = z
|
|
104
|
+
.object({
|
|
105
|
+
count: z.number(),
|
|
106
|
+
binding_count: z.number(),
|
|
107
|
+
})
|
|
108
|
+
.parse(sql
|
|
109
|
+
.exec("SELECT count(*) AS count,coalesce(sum(delivery_host=? AND delivery_tenant=? AND delivery_binding=?),0) AS binding_count FROM hub_trigger_run_reservations WHERE status='active'", input.delivery.host, input.delivery.tenant, input.delivery.bindingId)
|
|
110
|
+
.toArray()[0]);
|
|
111
|
+
if (active.count >= input.limits.hostConcurrency ||
|
|
112
|
+
active.binding_count >= input.limits.bindingConcurrency)
|
|
113
|
+
return { kind: "refused", reason: "limit" };
|
|
114
|
+
const reservationId = reservationIds.reservation.generate();
|
|
115
|
+
sql.exec("INSERT INTO hub_trigger_run_reservations(reservation_id,scope,request_id,run_id,status,delivery_key,delivery_record,delivery_expires_at,delivery_pending,delivery_host,delivery_tenant,delivery_binding) VALUES(?,?,?,NULL,'active',?,?,?,1,?,?,?)", reservationId, scopeKey(input.scope), reservationId, key, JSON.stringify(input), now() + DAY, input.delivery.host, input.delivery.tenant, input.delivery.bindingId);
|
|
116
|
+
return { kind: "reserved", reservationId, runId: null };
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
/** Bounded restart/lost-launch-reply recovery over the SAME reservations. */
|
|
120
|
+
pendingWebhookDeliveries(rawOwner) {
|
|
121
|
+
const owner = deliveryOwnerSchema.parse(rawOwner);
|
|
122
|
+
return options.transactionSync(() => {
|
|
123
|
+
expireDeliveries();
|
|
124
|
+
return sql
|
|
125
|
+
.exec("SELECT reservation_id,run_id,status,scope,delivery_record FROM hub_trigger_run_reservations WHERE delivery_host=? AND delivery_tenant=? AND delivery_pending=1 ORDER BY delivery_expires_at LIMIT 32", owner.host, owner.tenant)
|
|
126
|
+
.toArray()
|
|
127
|
+
.map((raw) => {
|
|
128
|
+
const row = pendingDeliverySchema.parse(raw);
|
|
129
|
+
if (row.run_id !== null)
|
|
130
|
+
return {
|
|
131
|
+
kind: "launch",
|
|
132
|
+
reservationId: row.reservation_id,
|
|
133
|
+
runId: row.run_id,
|
|
134
|
+
};
|
|
135
|
+
const record = deliveryInputSchema.parse(JSON.parse(z.string().parse(row.delivery_record)));
|
|
136
|
+
return {
|
|
137
|
+
kind: "start",
|
|
138
|
+
reservationId: row.reservation_id,
|
|
139
|
+
...record,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
/** Native alarms share this deadline with schedules, including idle TTL cleanup. */
|
|
145
|
+
nextWebhookWake(rawOwner) {
|
|
146
|
+
const owner = deliveryOwnerSchema.parse(rawOwner);
|
|
147
|
+
return options.transactionSync(() => {
|
|
148
|
+
expireDeliveries();
|
|
149
|
+
const row = z
|
|
150
|
+
.object({
|
|
151
|
+
pending: z.number(),
|
|
152
|
+
expiry: z.number().nullable(),
|
|
153
|
+
})
|
|
154
|
+
.parse(sql
|
|
155
|
+
.exec("SELECT coalesce(max(delivery_pending),0) AS pending,min(delivery_expires_at) AS expiry FROM hub_trigger_run_reservations WHERE delivery_host=? AND delivery_tenant=?", owner.host, owner.tenant)
|
|
156
|
+
.toArray()[0]);
|
|
157
|
+
return row.pending === 1 ? now() + 1000 : row.expiry;
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
completeWebhookDelivery(rawReservationId) {
|
|
161
|
+
const reservationId = identity.parse(rawReservationId);
|
|
162
|
+
sql.exec("UPDATE hub_trigger_run_reservations SET delivery_pending=0,delivery_record=NULL,status=CASE WHEN run_id IS NULL THEN 'terminal' ELSE status END WHERE reservation_id=? AND delivery_pending=1", reservationId);
|
|
163
|
+
},
|
|
45
164
|
reserveTriggerRun(raw) {
|
|
46
165
|
const input = reservationInputSchema.parse(raw);
|
|
47
166
|
const scope = scopeKey(input.scope);
|
package/dist/schema.d.ts
CHANGED
|
@@ -94,6 +94,12 @@ export declare const nodeSchema: S.Union<readonly [S.Struct<{
|
|
|
94
94
|
readonly id: S.String;
|
|
95
95
|
readonly kind: S.Literal<"review">;
|
|
96
96
|
readonly title: S.String;
|
|
97
|
+
/**
|
|
98
|
+
* One plain line under the title saying where the run is, such as
|
|
99
|
+
* `Beat {item} of {items} · try {try} of {tries}`. The placeholders are
|
|
100
|
+
* filled from the loops around the review (see `fillReviewText`).
|
|
101
|
+
*/
|
|
102
|
+
readonly detail: S.optionalKey<S.String>;
|
|
97
103
|
readonly show: S.$Array<S.Struct<{
|
|
98
104
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
99
105
|
readonly port: S.String;
|
|
@@ -102,11 +108,14 @@ export declare const nodeSchema: S.Union<readonly [S.Struct<{
|
|
|
102
108
|
readonly timeout: S.Number;
|
|
103
109
|
/**
|
|
104
110
|
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
105
|
-
* the shown item. Without choices the review approves or rejects.
|
|
111
|
+
* the shown item. Without choices the review approves or rejects. A
|
|
112
|
+
* choice's `description` says in plain words what picking it does, with
|
|
113
|
+
* the same placeholders as `detail`.
|
|
106
114
|
*/
|
|
107
115
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
108
116
|
readonly id: S.String;
|
|
109
117
|
readonly label: S.String;
|
|
118
|
+
readonly description: S.optionalKey<S.String>;
|
|
110
119
|
}>>>;
|
|
111
120
|
}>, S.Struct<{
|
|
112
121
|
readonly id: S.String;
|
|
@@ -199,6 +208,12 @@ export declare const graphSchema: S.Struct<{
|
|
|
199
208
|
readonly id: S.String;
|
|
200
209
|
readonly kind: S.Literal<"review">;
|
|
201
210
|
readonly title: S.String;
|
|
211
|
+
/**
|
|
212
|
+
* One plain line under the title saying where the run is, such as
|
|
213
|
+
* `Beat {item} of {items} · try {try} of {tries}`. The placeholders are
|
|
214
|
+
* filled from the loops around the review (see `fillReviewText`).
|
|
215
|
+
*/
|
|
216
|
+
readonly detail: S.optionalKey<S.String>;
|
|
202
217
|
readonly show: S.$Array<S.Struct<{
|
|
203
218
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
204
219
|
readonly port: S.String;
|
|
@@ -207,11 +222,14 @@ export declare const graphSchema: S.Struct<{
|
|
|
207
222
|
readonly timeout: S.Number;
|
|
208
223
|
/**
|
|
209
224
|
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
210
|
-
* the shown item. Without choices the review approves or rejects.
|
|
225
|
+
* the shown item. Without choices the review approves or rejects. A
|
|
226
|
+
* choice's `description` says in plain words what picking it does, with
|
|
227
|
+
* the same placeholders as `detail`.
|
|
211
228
|
*/
|
|
212
229
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
213
230
|
readonly id: S.String;
|
|
214
231
|
readonly label: S.String;
|
|
232
|
+
readonly description: S.optionalKey<S.String>;
|
|
215
233
|
}>>>;
|
|
216
234
|
}>, S.Struct<{
|
|
217
235
|
readonly id: S.String;
|
|
@@ -306,6 +324,12 @@ export declare const workflowDefinitionSchema: S.Struct<{
|
|
|
306
324
|
readonly id: S.String;
|
|
307
325
|
readonly kind: S.Literal<"review">;
|
|
308
326
|
readonly title: S.String;
|
|
327
|
+
/**
|
|
328
|
+
* One plain line under the title saying where the run is, such as
|
|
329
|
+
* `Beat {item} of {items} · try {try} of {tries}`. The placeholders are
|
|
330
|
+
* filled from the loops around the review (see `fillReviewText`).
|
|
331
|
+
*/
|
|
332
|
+
readonly detail: S.optionalKey<S.String>;
|
|
309
333
|
readonly show: S.$Array<S.Struct<{
|
|
310
334
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
311
335
|
readonly port: S.String;
|
|
@@ -314,11 +338,14 @@ export declare const workflowDefinitionSchema: S.Struct<{
|
|
|
314
338
|
readonly timeout: S.Number;
|
|
315
339
|
/**
|
|
316
340
|
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
317
|
-
* the shown item. Without choices the review approves or rejects.
|
|
341
|
+
* the shown item. Without choices the review approves or rejects. A
|
|
342
|
+
* choice's `description` says in plain words what picking it does, with
|
|
343
|
+
* the same placeholders as `detail`.
|
|
318
344
|
*/
|
|
319
345
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
320
346
|
readonly id: S.String;
|
|
321
347
|
readonly label: S.String;
|
|
348
|
+
readonly description: S.optionalKey<S.String>;
|
|
322
349
|
}>>>;
|
|
323
350
|
}>, S.Struct<{
|
|
324
351
|
readonly id: S.String;
|
|
@@ -434,6 +461,12 @@ export declare const workflowDefinitionSchema: S.Struct<{
|
|
|
434
461
|
readonly id: S.String;
|
|
435
462
|
readonly kind: S.Literal<"review">;
|
|
436
463
|
readonly title: S.String;
|
|
464
|
+
/**
|
|
465
|
+
* One plain line under the title saying where the run is, such as
|
|
466
|
+
* `Beat {item} of {items} · try {try} of {tries}`. The placeholders are
|
|
467
|
+
* filled from the loops around the review (see `fillReviewText`).
|
|
468
|
+
*/
|
|
469
|
+
readonly detail: S.optionalKey<S.String>;
|
|
437
470
|
readonly show: S.$Array<S.Struct<{
|
|
438
471
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
439
472
|
readonly port: S.String;
|
|
@@ -442,11 +475,14 @@ export declare const workflowDefinitionSchema: S.Struct<{
|
|
|
442
475
|
readonly timeout: S.Number;
|
|
443
476
|
/**
|
|
444
477
|
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
445
|
-
* the shown item. Without choices the review approves or rejects.
|
|
478
|
+
* the shown item. Without choices the review approves or rejects. A
|
|
479
|
+
* choice's `description` says in plain words what picking it does, with
|
|
480
|
+
* the same placeholders as `detail`.
|
|
446
481
|
*/
|
|
447
482
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
448
483
|
readonly id: S.String;
|
|
449
484
|
readonly label: S.String;
|
|
485
|
+
readonly description: S.optionalKey<S.String>;
|
|
450
486
|
}>>>;
|
|
451
487
|
}>, S.Struct<{
|
|
452
488
|
readonly id: S.String;
|
|
@@ -595,6 +631,12 @@ export declare const planContentSchema: S.Struct<{
|
|
|
595
631
|
readonly id: S.String;
|
|
596
632
|
readonly kind: S.Literal<"review">;
|
|
597
633
|
readonly title: S.String;
|
|
634
|
+
/**
|
|
635
|
+
* One plain line under the title saying where the run is, such as
|
|
636
|
+
* `Beat {item} of {items} · try {try} of {tries}`. The placeholders are
|
|
637
|
+
* filled from the loops around the review (see `fillReviewText`).
|
|
638
|
+
*/
|
|
639
|
+
readonly detail: S.optionalKey<S.String>;
|
|
598
640
|
readonly show: S.$Array<S.Struct<{
|
|
599
641
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
600
642
|
readonly port: S.String;
|
|
@@ -603,11 +645,14 @@ export declare const planContentSchema: S.Struct<{
|
|
|
603
645
|
readonly timeout: S.Number;
|
|
604
646
|
/**
|
|
605
647
|
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
606
|
-
* the shown item. Without choices the review approves or rejects.
|
|
648
|
+
* the shown item. Without choices the review approves or rejects. A
|
|
649
|
+
* choice's `description` says in plain words what picking it does, with
|
|
650
|
+
* the same placeholders as `detail`.
|
|
607
651
|
*/
|
|
608
652
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
609
653
|
readonly id: S.String;
|
|
610
654
|
readonly label: S.String;
|
|
655
|
+
readonly description: S.optionalKey<S.String>;
|
|
611
656
|
}>>>;
|
|
612
657
|
}>, S.Struct<{
|
|
613
658
|
readonly id: S.String;
|
|
@@ -723,6 +768,12 @@ export declare const planContentSchema: S.Struct<{
|
|
|
723
768
|
readonly id: S.String;
|
|
724
769
|
readonly kind: S.Literal<"review">;
|
|
725
770
|
readonly title: S.String;
|
|
771
|
+
/**
|
|
772
|
+
* One plain line under the title saying where the run is, such as
|
|
773
|
+
* `Beat {item} of {items} · try {try} of {tries}`. The placeholders are
|
|
774
|
+
* filled from the loops around the review (see `fillReviewText`).
|
|
775
|
+
*/
|
|
776
|
+
readonly detail: S.optionalKey<S.String>;
|
|
726
777
|
readonly show: S.$Array<S.Struct<{
|
|
727
778
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
728
779
|
readonly port: S.String;
|
|
@@ -731,11 +782,14 @@ export declare const planContentSchema: S.Struct<{
|
|
|
731
782
|
readonly timeout: S.Number;
|
|
732
783
|
/**
|
|
733
784
|
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
734
|
-
* the shown item. Without choices the review approves or rejects.
|
|
785
|
+
* the shown item. Without choices the review approves or rejects. A
|
|
786
|
+
* choice's `description` says in plain words what picking it does, with
|
|
787
|
+
* the same placeholders as `detail`.
|
|
735
788
|
*/
|
|
736
789
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
737
790
|
readonly id: S.String;
|
|
738
791
|
readonly label: S.String;
|
|
792
|
+
readonly description: S.optionalKey<S.String>;
|
|
739
793
|
}>>>;
|
|
740
794
|
}>, S.Struct<{
|
|
741
795
|
readonly id: S.String;
|
|
@@ -876,6 +930,12 @@ export declare const executionPlanSchema: S.Struct<{
|
|
|
876
930
|
readonly id: S.String;
|
|
877
931
|
readonly kind: S.Literal<"review">;
|
|
878
932
|
readonly title: S.String;
|
|
933
|
+
/**
|
|
934
|
+
* One plain line under the title saying where the run is, such as
|
|
935
|
+
* `Beat {item} of {items} · try {try} of {tries}`. The placeholders are
|
|
936
|
+
* filled from the loops around the review (see `fillReviewText`).
|
|
937
|
+
*/
|
|
938
|
+
readonly detail: S.optionalKey<S.String>;
|
|
879
939
|
readonly show: S.$Array<S.Struct<{
|
|
880
940
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
881
941
|
readonly port: S.String;
|
|
@@ -884,11 +944,14 @@ export declare const executionPlanSchema: S.Struct<{
|
|
|
884
944
|
readonly timeout: S.Number;
|
|
885
945
|
/**
|
|
886
946
|
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
887
|
-
* the shown item. Without choices the review approves or rejects.
|
|
947
|
+
* the shown item. Without choices the review approves or rejects. A
|
|
948
|
+
* choice's `description` says in plain words what picking it does, with
|
|
949
|
+
* the same placeholders as `detail`.
|
|
888
950
|
*/
|
|
889
951
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
890
952
|
readonly id: S.String;
|
|
891
953
|
readonly label: S.String;
|
|
954
|
+
readonly description: S.optionalKey<S.String>;
|
|
892
955
|
}>>>;
|
|
893
956
|
}>, S.Struct<{
|
|
894
957
|
readonly id: S.String;
|
|
@@ -1004,6 +1067,12 @@ export declare const executionPlanSchema: S.Struct<{
|
|
|
1004
1067
|
readonly id: S.String;
|
|
1005
1068
|
readonly kind: S.Literal<"review">;
|
|
1006
1069
|
readonly title: S.String;
|
|
1070
|
+
/**
|
|
1071
|
+
* One plain line under the title saying where the run is, such as
|
|
1072
|
+
* `Beat {item} of {items} · try {try} of {tries}`. The placeholders are
|
|
1073
|
+
* filled from the loops around the review (see `fillReviewText`).
|
|
1074
|
+
*/
|
|
1075
|
+
readonly detail: S.optionalKey<S.String>;
|
|
1007
1076
|
readonly show: S.$Array<S.Struct<{
|
|
1008
1077
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
1009
1078
|
readonly port: S.String;
|
|
@@ -1012,11 +1081,14 @@ export declare const executionPlanSchema: S.Struct<{
|
|
|
1012
1081
|
readonly timeout: S.Number;
|
|
1013
1082
|
/**
|
|
1014
1083
|
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
1015
|
-
* the shown item. Without choices the review approves or rejects.
|
|
1084
|
+
* the shown item. Without choices the review approves or rejects. A
|
|
1085
|
+
* choice's `description` says in plain words what picking it does, with
|
|
1086
|
+
* the same placeholders as `detail`.
|
|
1016
1087
|
*/
|
|
1017
1088
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
1018
1089
|
readonly id: S.String;
|
|
1019
1090
|
readonly label: S.String;
|
|
1091
|
+
readonly description: S.optionalKey<S.String>;
|
|
1020
1092
|
}>>>;
|
|
1021
1093
|
}>, S.Struct<{
|
|
1022
1094
|
readonly id: S.String;
|
package/dist/schema.js
CHANGED
|
@@ -84,14 +84,26 @@ export const nodeSchema = S.Union([
|
|
|
84
84
|
id: name,
|
|
85
85
|
kind: S.Literal("review"),
|
|
86
86
|
title: boundedString(120),
|
|
87
|
+
/**
|
|
88
|
+
* One plain line under the title saying where the run is, such as
|
|
89
|
+
* `Beat {item} of {items} · try {try} of {tries}`. The placeholders are
|
|
90
|
+
* filled from the loops around the review (see `fillReviewText`).
|
|
91
|
+
*/
|
|
92
|
+
detail: S.optionalKey(boundedString(160)),
|
|
87
93
|
show: array(portRefSchema, 64),
|
|
88
94
|
reviewers: S.Literals(["runner", "owners"]),
|
|
89
95
|
timeout: duration,
|
|
90
96
|
/**
|
|
91
97
|
* Named outcomes a reviewer picks from; each is an output port carrying
|
|
92
|
-
* the shown item. Without choices the review approves or rejects.
|
|
98
|
+
* the shown item. Without choices the review approves or rejects. A
|
|
99
|
+
* choice's `description` says in plain words what picking it does, with
|
|
100
|
+
* the same placeholders as `detail`.
|
|
93
101
|
*/
|
|
94
|
-
choices: S.optionalKey(array(S.Struct({
|
|
102
|
+
choices: S.optionalKey(array(S.Struct({
|
|
103
|
+
id: name,
|
|
104
|
+
label: boundedString(80),
|
|
105
|
+
description: S.optionalKey(boundedString(160)),
|
|
106
|
+
}), 8)),
|
|
95
107
|
}),
|
|
96
108
|
S.Struct({ id: name, kind: S.Literal("condition"), test: predicateSchema }),
|
|
97
109
|
S.Struct({
|
package/dist/validate.js
CHANGED
|
@@ -2,6 +2,7 @@ import { escapePointer } from "@cfworker/json-schema";
|
|
|
2
2
|
import * as S from "effect/Schema";
|
|
3
3
|
import * as Result from "effect/Result";
|
|
4
4
|
import { nextOccurrence } from "@fungi.computer/almanac";
|
|
5
|
+
import { reviewTextPlaceholders, unknownReviewPlaceholders, } from "./review-text.js";
|
|
5
6
|
import { workflowDefinitionSchema, catalogSchema, TRIGGER_EVENT_PORTS, } from "./schema.js";
|
|
6
7
|
import { parseProjection, methodInputPorts, methodOutputPorts, portProjection, ports, accepts, assignable, } from "./ports.js";
|
|
7
8
|
import { nodeRefs, mappingRefs, mappingProjection, predicateRefs, ExpressionSyntaxError, } from "./expressions.js";
|
|
@@ -363,6 +364,12 @@ export function analyze(raw, rawCatalog) {
|
|
|
363
364
|
case "review": {
|
|
364
365
|
const shown = node.show[0];
|
|
365
366
|
const item = shown ? (source(shown) ?? {}) : {};
|
|
367
|
+
for (const text of [
|
|
368
|
+
node.detail ?? "",
|
|
369
|
+
...(node.choices ?? []).map((choice) => choice.description ?? ""),
|
|
370
|
+
])
|
|
371
|
+
for (const name of unknownReviewPlaceholders(text))
|
|
372
|
+
issue("schema", `Unknown review placeholder {${name}}; use ${reviewTextPlaceholders.map((known) => `{${known}}`).join(", ")}`, { node: node.id });
|
|
366
373
|
if (node.choices === undefined) {
|
|
367
374
|
outs.set(node.id, {
|
|
368
375
|
approved: item,
|
package/dist/workflow-id.d.ts
CHANGED
package/dist/workflow-id.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/braid",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Pure versioned Workflow graph validation and content-addressed compilation",
|
|
@@ -29,16 +29,17 @@
|
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@cfworker/json-schema": "4.1.1",
|
|
32
|
-
"@fungi.computer/almanac": "0.0
|
|
33
|
-
"@fungi.computer/mycelium": "0.0
|
|
32
|
+
"@fungi.computer/almanac": "0.2.0",
|
|
33
|
+
"@fungi.computer/mycelium": "0.2.0",
|
|
34
34
|
"effect": "4.0.2",
|
|
35
35
|
"jsonpointer": "5.0.1",
|
|
36
36
|
"mustache": "4.2.0",
|
|
37
37
|
"zod": "4.4.3",
|
|
38
|
-
"@fungi.computer/agar": "0.
|
|
39
|
-
"@fungi.computer/
|
|
40
|
-
"@fungi.computer/
|
|
41
|
-
"@fungi.computer/
|
|
38
|
+
"@fungi.computer/agar": "0.3.0",
|
|
39
|
+
"@fungi.computer/gills": "0.2.0",
|
|
40
|
+
"@fungi.computer/watchdog": "0.1.0",
|
|
41
|
+
"@fungi.computer/puffball": "0.2.0",
|
|
42
|
+
"@fungi.computer/cairn": "0.1.0"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@cloudflare/workers-types": "5.20260828.1",
|