@fungi.computer/braid 0.2.4 → 0.4.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/runtime/arming-authority.d.ts +1 -1
- package/dist/runtime/arming-authority.js +1 -1
- package/dist/runtime/git-pass-contract.d.ts +1 -1
- package/dist/runtime/git-pass-contract.js +1 -1
- package/dist/runtime/git-pass-store.js +1 -1
- package/dist/runtime/graph-start.d.ts +1 -1
- package/dist/runtime/graph-start.js +1 -1
- package/dist/runtime/run-owner.d.ts +4 -4
- package/dist/runtime/run-owner.js +1 -1
- package/dist/runtime/schedules.d.ts +40 -9
- package/dist/runtime/schedules.js +64 -17
- package/dist/runtime/stored-trigger-rows.d.ts +3 -1
- package/dist/runtime/stored-trigger-rows.js +8 -1
- package/dist/runtime/trigger-owner.d.ts +1 -1
- package/dist/runtime/trigger-owner.js +1 -1
- package/dist/runtime/trigger-plans.js +1 -1
- package/dist/runtime/trigger-router.js +1 -1
- package/dist/runtime/trigger-run-reservations.d.ts +1 -1
- package/dist/runtime/trigger-run-reservations.js +1 -1
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import type { WorkflowRunMember } from "./run-authority.js";
|
|
3
3
|
/** Host-captured provenance from the actual connector or approved-policy owner. */
|
|
4
4
|
export declare const workflowArmedViaSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { repositoryRefsEqual, } from "@fungi.computer/agar/git";
|
|
2
|
-
import
|
|
2
|
+
import * as z from "zod";
|
|
3
3
|
import { canonicalJson } from "../index.js";
|
|
4
4
|
import { formatWorkflowGitPassToken, workflowGitPassTokenSchema, workflowGitPassGrantSchema, workflowGitPassRevocationSchema, } from "./git-pass-contract.js";
|
|
5
5
|
const invocationIdSchema = z.string().min(1).max(256);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import { bindingsSelectionSchema, workflowBindingSchema, } from "../git-access.js";
|
|
3
3
|
import { gitSha1Schema } from "@fungi.computer/puffball/git-representation";
|
|
4
4
|
import { sha256DigestSchema } from "@fungi.computer/puffball/effect";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as S from "effect/Schema";
|
|
2
2
|
import { type NodeEvent, type NodeAdmissionResult } from "../execution.js";
|
|
3
|
-
import
|
|
3
|
+
import * as z from "zod";
|
|
4
4
|
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
5
5
|
import { type WorkflowRunAuthority } from "./run-authority.js";
|
|
6
6
|
import type { ArmedWorkflowTrigger } from "./trigger-owner.js";
|
|
@@ -1584,7 +1584,7 @@ export declare function createTeamWorkflowRuns(options: {
|
|
|
1584
1584
|
revokeReleaseInMutation(appIds: readonly string[]): void;
|
|
1585
1585
|
/** The kernel's lifecycle caller owns the transaction. Suspension ends every run. */
|
|
1586
1586
|
onLifecycle(status: "active" | "suspended"): void;
|
|
1587
|
-
reserveWebhookDelivery(raw: z.input<z.ZodObject<{
|
|
1587
|
+
reserveWebhookDelivery(raw: z.core.input<z.ZodObject<{
|
|
1588
1588
|
scope: z.ZodLazy<z.ZodObject<{
|
|
1589
1589
|
installationId: z.ZodString;
|
|
1590
1590
|
workflowId: z.ZodString;
|
|
@@ -1610,7 +1610,7 @@ export declare function createTeamWorkflowRuns(options: {
|
|
|
1610
1610
|
kind: "refused";
|
|
1611
1611
|
reason: "limit";
|
|
1612
1612
|
};
|
|
1613
|
-
pendingWebhookDeliveries(rawOwner: z.input<z.ZodObject<{
|
|
1613
|
+
pendingWebhookDeliveries(rawOwner: z.core.input<z.ZodObject<{
|
|
1614
1614
|
host: z.ZodString;
|
|
1615
1615
|
tenant: z.ZodString;
|
|
1616
1616
|
}, z.core.$strict>>): ({
|
|
@@ -1639,7 +1639,7 @@ export declare function createTeamWorkflowRuns(options: {
|
|
|
1639
1639
|
reservationId: string;
|
|
1640
1640
|
runId?: never;
|
|
1641
1641
|
})[];
|
|
1642
|
-
nextWebhookWake(rawOwner: z.input<z.ZodObject<{
|
|
1642
|
+
nextWebhookWake(rawOwner: z.core.input<z.ZodObject<{
|
|
1643
1643
|
host: z.ZodString;
|
|
1644
1644
|
tenant: z.ZodString;
|
|
1645
1645
|
}, z.core.$strict>>): number | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as S from "effect/Schema";
|
|
2
2
|
import * as Struct from "effect/Struct";
|
|
3
3
|
import { nodeEventSchema, } from "../execution.js";
|
|
4
|
-
import
|
|
4
|
+
import * as z from "zod";
|
|
5
5
|
import { bindingsSelectionSchema, workflowBindingSchema, } from "../git-access.js";
|
|
6
6
|
import { canonicalJson, contentDigest } from "../index.js";
|
|
7
7
|
import { createTriggerPlans } from "./trigger-plans.js";
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
1
2
|
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
2
3
|
import { type WorkflowArmer, type WorkflowInstallationAuthority, type WorkflowTriggerPrincipal, type WorkflowTriggerTarget, type createTeamWorkflowTriggers } from "./trigger-owner.js";
|
|
4
|
+
/** Installation-scoped keyset read; the default and ceiling bound one reply. */
|
|
5
|
+
export declare const workflowScheduleListInputSchema: z.ZodObject<{
|
|
6
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
7
|
+
after: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, z.core.$strict>;
|
|
9
|
+
export type WorkflowScheduleListInput = z.input<typeof workflowScheduleListInputSchema>;
|
|
3
10
|
/** Team-owned schedule records; Almanac alone decides occurrences and transitions. */
|
|
4
11
|
export declare function createWorkflowSchedules(options: {
|
|
5
12
|
sql: WatchdogSqliteOwner["sql"];
|
|
@@ -22,15 +29,39 @@ export declare function createWorkflowSchedules(options: {
|
|
|
22
29
|
kind: "armed";
|
|
23
30
|
scheduleId: string;
|
|
24
31
|
}>;
|
|
25
|
-
list(principal: WorkflowTriggerPrincipal, installationId: string): Promise<{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
list(principal: WorkflowTriggerPrincipal, installationId: string, input: WorkflowScheduleListInput): Promise<{
|
|
33
|
+
schedules: {
|
|
34
|
+
scheduleId: string;
|
|
35
|
+
installationId: string;
|
|
36
|
+
workflowId: string;
|
|
37
|
+
triggerId: string;
|
|
38
|
+
cron: {
|
|
39
|
+
expression: string;
|
|
40
|
+
timeZone: string;
|
|
41
|
+
};
|
|
42
|
+
nextDueAt: number;
|
|
43
|
+
state: "active" | "finished" | "paused" | "cancelled";
|
|
44
|
+
lastError: string | null;
|
|
45
|
+
}[];
|
|
46
|
+
nextCursor: string | null;
|
|
47
|
+
}>;
|
|
48
|
+
/** Pause future admission only; accepted runs and the compiled plan remain owned. */
|
|
49
|
+
pause(principal: WorkflowTriggerPrincipal, installationId: string, scheduleId: string): Promise<{
|
|
50
|
+
kind: "not_found";
|
|
51
|
+
} | {
|
|
52
|
+
kind: "paused";
|
|
53
|
+
}>;
|
|
54
|
+
/** Resume from the next occurrence; Almanac owns missed-run and retry semantics. */
|
|
55
|
+
resume(principal: WorkflowTriggerPrincipal, installationId: string, scheduleId: string): Promise<{
|
|
56
|
+
kind: "not_found";
|
|
57
|
+
reason?: never;
|
|
58
|
+
} | {
|
|
59
|
+
kind: "refused";
|
|
60
|
+
reason: string;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "resumed";
|
|
63
|
+
reason?: never;
|
|
64
|
+
}>;
|
|
34
65
|
disarm(principal: WorkflowTriggerPrincipal, installationId: string, scheduleId: string): Promise<{
|
|
35
66
|
kind: "not_found";
|
|
36
67
|
} | {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import * as Result from "effect/Result";
|
|
3
|
-
import { afterAdmitted, afterRefused, isDue, nextOccurrence, occurrenceKey, paused, cancelled, } from "@fungi.computer/almanac";
|
|
3
|
+
import { afterAdmitted, afterRefused, isDue, nextOccurrence, occurrenceKey, paused, resumed, cancelled, } from "@fungi.computer/almanac";
|
|
4
4
|
import { canonicalJson, contentDigest } from "../index.js";
|
|
5
5
|
import { armedWorkflowTriggerSchema, workflowArmerKey, workflowTriggerControlledBy, workflowTriggerPrincipalMember, } from "./trigger-owner.js";
|
|
6
6
|
import { storedTriggerRows } from "./stored-trigger-rows.js";
|
|
7
|
+
/** Installation-scoped keyset read; the default and ceiling bound one reply. */
|
|
8
|
+
export const workflowScheduleListInputSchema = z.strictObject({
|
|
9
|
+
limit: z.number().int().min(1).max(100).default(50),
|
|
10
|
+
after: z.string().min(1).max(256).optional(),
|
|
11
|
+
});
|
|
7
12
|
const recordSchema = armedWorkflowTriggerSchema.extend({
|
|
8
13
|
id: z.string().min(1).max(256),
|
|
9
14
|
cron: z.strictObject({
|
|
@@ -26,7 +31,7 @@ export function createWorkflowSchedules(options) {
|
|
|
26
31
|
key: "scheduleId",
|
|
27
32
|
record: recordSchema,
|
|
28
33
|
});
|
|
29
|
-
const
|
|
34
|
+
const refresh = (record) => {
|
|
30
35
|
if (record.state !== "active")
|
|
31
36
|
return record;
|
|
32
37
|
const reason = options.triggers.refusal(bindingOf(record));
|
|
@@ -35,7 +40,8 @@ export function createWorkflowSchedules(options) {
|
|
|
35
40
|
const next = { ...paused(record), lastError: reason, retryAt: null };
|
|
36
41
|
store(next);
|
|
37
42
|
return next;
|
|
38
|
-
}
|
|
43
|
+
};
|
|
44
|
+
const records = () => rows.all().map(refresh);
|
|
39
45
|
const nextWake = () => {
|
|
40
46
|
const due = records()
|
|
41
47
|
.filter((record) => record.state === "active")
|
|
@@ -89,19 +95,56 @@ export function createWorkflowSchedules(options) {
|
|
|
89
95
|
await options.wake(nextWake());
|
|
90
96
|
return { kind: "armed", scheduleId: id };
|
|
91
97
|
},
|
|
92
|
-
async list(principal, installationId) {
|
|
98
|
+
async list(principal, installationId, input) {
|
|
99
|
+
await options.authorize(workflowTriggerPrincipalMember(principal), installationId);
|
|
100
|
+
const page = workflowScheduleListInputSchema.parse(input);
|
|
101
|
+
const selected = rows.installationPage(installationId, page.after ?? "", page.limit + 1);
|
|
102
|
+
const visible = selected.slice(0, page.limit).map(refresh);
|
|
103
|
+
return {
|
|
104
|
+
schedules: visible.map((record) => ({
|
|
105
|
+
scheduleId: record.id,
|
|
106
|
+
installationId: record.installationId,
|
|
107
|
+
workflowId: record.workflowId,
|
|
108
|
+
triggerId: record.triggerId,
|
|
109
|
+
cron: record.cron,
|
|
110
|
+
nextDueAt: record.nextDueAt,
|
|
111
|
+
state: record.state,
|
|
112
|
+
lastError: record.lastError,
|
|
113
|
+
})),
|
|
114
|
+
nextCursor: selected.length > page.limit ? (visible.at(-1)?.id ?? null) : null,
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
/** Pause future admission only; accepted runs and the compiled plan remain owned. */
|
|
118
|
+
async pause(principal, installationId, scheduleId) {
|
|
93
119
|
await options.authorize(workflowTriggerPrincipalMember(principal), installationId);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
120
|
+
const record = rows.read(scheduleId);
|
|
121
|
+
if (record?.installationId !== installationId ||
|
|
122
|
+
!workflowTriggerControlledBy(principal, record) ||
|
|
123
|
+
record.state === "cancelled" ||
|
|
124
|
+
record.state === "finished")
|
|
125
|
+
return { kind: "not_found" };
|
|
126
|
+
await options.commit(() => {
|
|
127
|
+
store({ ...paused(record), retryAt: null });
|
|
128
|
+
});
|
|
129
|
+
await options.wake(nextWake());
|
|
130
|
+
return { kind: "paused" };
|
|
131
|
+
},
|
|
132
|
+
/** Resume from the next occurrence; Almanac owns missed-run and retry semantics. */
|
|
133
|
+
async resume(principal, installationId, scheduleId) {
|
|
134
|
+
await options.authorize(workflowTriggerPrincipalMember(principal), installationId);
|
|
135
|
+
const record = rows.read(scheduleId);
|
|
136
|
+
if (record?.installationId !== installationId ||
|
|
137
|
+
!workflowTriggerControlledBy(principal, record) ||
|
|
138
|
+
record.state !== "paused")
|
|
139
|
+
return { kind: "not_found" };
|
|
140
|
+
const next = resumed(record, options.now());
|
|
141
|
+
if (Result.isFailure(next))
|
|
142
|
+
return { kind: "refused", reason: next.failure.message };
|
|
143
|
+
await options.commit(() => {
|
|
144
|
+
store({ ...next.success, retryAt: null });
|
|
145
|
+
});
|
|
146
|
+
await options.wake(nextWake());
|
|
147
|
+
return { kind: "resumed" };
|
|
105
148
|
},
|
|
106
149
|
async disarm(principal, installationId, scheduleId) {
|
|
107
150
|
const memberId = workflowTriggerPrincipalMember(principal);
|
|
@@ -130,6 +173,10 @@ export function createWorkflowSchedules(options) {
|
|
|
130
173
|
const result = await options.triggers.fire(binding, `${options.teamId()}/schedule/${record.id}`, {
|
|
131
174
|
scheduledAt: new Date(record.nextDueAt).toISOString(),
|
|
132
175
|
eventId: occurrenceKey(record),
|
|
176
|
+
}, {
|
|
177
|
+
// The existing run owner checks this in its durable-start
|
|
178
|
+
// transaction, after authority I/O and before accepting work.
|
|
179
|
+
admitBinding: () => JSON.stringify(rows.read(record.id)) === JSON.stringify(record),
|
|
133
180
|
});
|
|
134
181
|
next =
|
|
135
182
|
result.kind === "refused"
|
|
@@ -141,7 +188,7 @@ export function createWorkflowSchedules(options) {
|
|
|
141
188
|
const refused = afterRefused(record, options.now(), "Workflow execution temporarily unavailable");
|
|
142
189
|
next = { ...refused.schedule, retryAt: refused.retryAt };
|
|
143
190
|
}
|
|
144
|
-
// A disarm or re-arm
|
|
191
|
+
// A pause, disarm or re-arm during upstream I/O wins over this firing.
|
|
145
192
|
sql.exec("UPDATE hub_workflow_schedules SET record=? WHERE scheduleId=? AND record=?", JSON.stringify(recordSchema.parse(next)), record.id, JSON.stringify(record));
|
|
146
193
|
}
|
|
147
194
|
await options.wake(nextWake());
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
3
3
|
/** Stored trigger rows decode strictly; an undecodable row is corruption. */
|
|
4
4
|
export declare function storedTriggerRows<Record extends z.ZodType>(sql: WatchdogSqliteOwner["sql"], table: Readonly<{
|
|
@@ -7,6 +7,8 @@ export declare function storedTriggerRows<Record extends z.ZodType>(sql: Watchdo
|
|
|
7
7
|
record: Record;
|
|
8
8
|
}>): {
|
|
9
9
|
all: () => z.core.output<Record>[];
|
|
10
|
+
/** Filter and bound in SQLite, before decoding records into the owner. */
|
|
11
|
+
installationPage(installationId: string, after: string, limit: number): z.core.output<Record>[];
|
|
10
12
|
read(id: string): z.core.output<Record> | null;
|
|
11
13
|
remove(id: string): void;
|
|
12
14
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
const rowSchema = z.strictObject({ id: z.string(), record: z.string() });
|
|
3
3
|
/** Stored trigger rows decode strictly; an undecodable row is corruption. */
|
|
4
4
|
export function storedTriggerRows(sql, table) {
|
|
@@ -6,6 +6,13 @@ export function storedTriggerRows(sql, table) {
|
|
|
6
6
|
const decode = (raw) => table.record.parse(JSON.parse(rowSchema.parse(raw).record));
|
|
7
7
|
return {
|
|
8
8
|
all: () => sql.exec(`${select} ORDER BY ${table.key}`).toArray().map(decode),
|
|
9
|
+
/** Filter and bound in SQLite, before decoding records into the owner. */
|
|
10
|
+
installationPage(installationId, after, limit) {
|
|
11
|
+
return sql
|
|
12
|
+
.exec(`${select} WHERE json_extract(record, '$.installationId')=? AND ${table.key}>? ORDER BY ${table.key} LIMIT ?`, installationId, after, limit)
|
|
13
|
+
.toArray()
|
|
14
|
+
.map(decode);
|
|
15
|
+
},
|
|
9
16
|
read(id) {
|
|
10
17
|
const row = sql.exec(`${select} WHERE ${table.key}=?`, id).toArray()[0];
|
|
11
18
|
return row === undefined ? null : decode(row);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import { bindingsSelectionSchema, gitAccessScope, resolveGitAccess, } from "../git-access.js";
|
|
3
3
|
import * as S from "effect/Schema";
|
|
4
4
|
import { appIdSchema, releaseIdSchema, sha256DigestSchema, } from "@fungi.computer/puffball/effect";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import { canonicalJson, contentDigest } from "../index.js";
|
|
3
3
|
import { appEventFactSchema, } from "@fungi.computer/puffball/app-event-facts";
|
|
4
4
|
import { armedWorkflowTriggerSchema, workflowArmerKey, workflowTriggerControlledBy, workflowTriggerPrincipalMember, } from "./trigger-owner.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineIdFamily } from "@fungi.computer/cairn/id";
|
|
2
|
-
import
|
|
2
|
+
import * as z from "zod";
|
|
3
3
|
import { gitSha1Schema } from "@fungi.computer/puffball/git-representation";
|
|
4
4
|
import { workflowIdSchema } from "../index.js";
|
|
5
5
|
const reservationIds = defineIdFamily({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/braid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Pure versioned Workflow graph validation and content-addressed compilation",
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@cfworker/json-schema": "4.1.1",
|
|
32
32
|
"@fungi.computer/almanac": "0.2.0",
|
|
33
|
-
"@fungi.computer/mycelium": "0.
|
|
33
|
+
"@fungi.computer/mycelium": "0.3.1",
|
|
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.
|
|
38
|
+
"@fungi.computer/agar": "0.6.1",
|
|
39
39
|
"@fungi.computer/gills": "0.2.0",
|
|
40
40
|
"@fungi.computer/watchdog": "0.1.0",
|
|
41
|
-
"@fungi.computer/puffball": "0.
|
|
41
|
+
"@fungi.computer/puffball": "0.4.0",
|
|
42
42
|
"@fungi.computer/cairn": "0.1.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|