@fungi.computer/braid 0.0.0-stage → 0.0.1

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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -2
  3. package/dist/cloudflare/graph-engine.d.ts +25 -0
  4. package/dist/cloudflare/graph-engine.js +46 -0
  5. package/dist/cloudflare/graph-program.d.ts +6 -0
  6. package/dist/cloudflare/graph-program.js +528 -0
  7. package/dist/cloudflare/index.d.ts +1 -0
  8. package/dist/cloudflare/index.js +1 -0
  9. package/dist/code-execution.d.ts +16 -0
  10. package/dist/code-execution.js +50 -0
  11. package/dist/code-result.d.ts +11 -0
  12. package/dist/code-result.js +19 -0
  13. package/dist/code-sandbox.d.ts +9 -0
  14. package/dist/code-sandbox.js +13 -0
  15. package/dist/compile.d.ts +41 -0
  16. package/dist/compile.js +240 -0
  17. package/dist/execution.d.ts +113 -0
  18. package/dist/execution.js +108 -0
  19. package/dist/expressions.d.ts +16 -0
  20. package/dist/expressions.js +322 -0
  21. package/dist/git-access.d.ts +59 -0
  22. package/dist/git-access.js +106 -0
  23. package/dist/index.d.ts +8 -0
  24. package/dist/index.js +8 -0
  25. package/dist/ports.d.ts +58 -0
  26. package/dist/ports.js +256 -0
  27. package/dist/runtime/arming-authority.d.ts +83 -0
  28. package/dist/runtime/arming-authority.js +90 -0
  29. package/dist/runtime/bindings.d.ts +25 -0
  30. package/dist/runtime/bindings.js +41 -0
  31. package/dist/runtime/capture.d.ts +18 -0
  32. package/dist/runtime/capture.js +99 -0
  33. package/dist/runtime/git-pass-contract.d.ts +63 -0
  34. package/dist/runtime/git-pass-contract.js +36 -0
  35. package/dist/runtime/git-pass-store.d.ts +32 -0
  36. package/dist/runtime/git-pass-store.js +82 -0
  37. package/dist/runtime/git-pass.d.ts +41 -0
  38. package/dist/runtime/git-pass.js +72 -0
  39. package/dist/runtime/graph-contract.d.ts +784 -0
  40. package/dist/runtime/graph-contract.js +122 -0
  41. package/dist/runtime/graph-dispatch.d.ts +31 -0
  42. package/dist/runtime/graph-dispatch.js +111 -0
  43. package/dist/runtime/graph-run-store.d.ts +73 -0
  44. package/dist/runtime/graph-run-store.js +267 -0
  45. package/dist/runtime/graph-start.d.ts +90 -0
  46. package/dist/runtime/graph-start.js +124 -0
  47. package/dist/runtime/index.d.ts +20 -0
  48. package/dist/runtime/index.js +20 -0
  49. package/dist/runtime/ports.d.ts +153 -0
  50. package/dist/runtime/ports.js +1 -0
  51. package/dist/runtime/run-authority.d.ts +60 -0
  52. package/dist/runtime/run-authority.js +145 -0
  53. package/dist/runtime/run-ceiling.d.ts +106 -0
  54. package/dist/runtime/run-ceiling.js +23 -0
  55. package/dist/runtime/run-member.d.ts +3 -0
  56. package/dist/runtime/run-member.js +12 -0
  57. package/dist/runtime/run-owner.d.ts +1628 -0
  58. package/dist/runtime/run-owner.js +513 -0
  59. package/dist/runtime/run-report.d.ts +14 -0
  60. package/dist/runtime/run-report.js +23 -0
  61. package/dist/runtime/schedules.d.ts +39 -0
  62. package/dist/runtime/schedules.js +149 -0
  63. package/dist/runtime/stored-trigger-rows.d.ts +12 -0
  64. package/dist/runtime/stored-trigger-rows.js +17 -0
  65. package/dist/runtime/trigger-owner.d.ts +590 -0
  66. package/dist/runtime/trigger-owner.js +280 -0
  67. package/dist/runtime/trigger-plans.d.ts +766 -0
  68. package/dist/runtime/trigger-plans.js +62 -0
  69. package/dist/runtime/trigger-router.d.ts +34 -0
  70. package/dist/runtime/trigger-router.js +143 -0
  71. package/dist/runtime/trigger-run-reservations.d.ts +28 -0
  72. package/dist/runtime/trigger-run-reservations.js +114 -0
  73. package/dist/schema.d.ts +1111 -0
  74. package/dist/schema.js +227 -0
  75. package/dist/triggers.d.ts +46 -0
  76. package/dist/triggers.js +75 -0
  77. package/dist/validate.d.ts +25 -0
  78. package/dist/validate.js +679 -0
  79. package/dist/workflow-id.d.ts +3 -0
  80. package/dist/workflow-id.js +3 -0
  81. package/package.json +95 -3
@@ -0,0 +1,280 @@
1
+ import { z } from "zod";
2
+ import { bindingsSelectionSchema, gitAccessScope, resolveGitAccess, } from "../git-access.js";
3
+ import * as S from "effect/Schema";
4
+ import { appIdSchema, releaseIdSchema, sha256DigestSchema, } from "@fungi.computer/puffball/effect";
5
+ import {} from "@fungi.computer/agar/git";
6
+ import { gitSha1Schema } from "@fungi.computer/puffball/git-representation";
7
+ import { workflowIdSchema } from "../index.js";
8
+ import { canonicalJson, triggerRequestId, triggerRun, contentDigest, } from "../index.js";
9
+ import { nodeGitAccessListSchema, } from "./run-ceiling.js";
10
+ import { compileForMember } from "./graph-start.js";
11
+ import { workflowRunDeliverySchema, } from "./graph-contract.js";
12
+ import { captureWorkflowArming, recheckWorkflowArming, workflowArmedViaSchema, } from "./arming-authority.js";
13
+ const id = z.string().min(1).max(256);
14
+ export const workflowTriggerTargetSchema = z.strictObject({
15
+ installationId: id,
16
+ workflowId: workflowIdSchema,
17
+ commit: gitSha1Schema,
18
+ digest: z.string().refine(S.is(sha256DigestSchema)),
19
+ triggerId: z.string().min(1).max(80),
20
+ bindings: bindingsSelectionSchema,
21
+ /** Every run this trigger starts reports its ending to this Agent Session. */
22
+ delivery: z
23
+ .custom(S.is(workflowRunDeliverySchema))
24
+ .optional(),
25
+ });
26
+ /** The host frame's installation selection and verified artifact, pinned at arm time. */
27
+ export const workflowInstallationAuthoritySchema = z.strictObject({
28
+ selection: z.strictObject({
29
+ instanceId: id,
30
+ owner: z.strictObject({
31
+ kind: z.enum(["installation", "deployment"]),
32
+ id,
33
+ }),
34
+ appId: z.string().refine(S.is(appIdSchema)),
35
+ releaseId: z.string().refine(S.is(releaseIdSchema)),
36
+ authorityVersion: z.number().int().positive(),
37
+ }),
38
+ artifactDigest: z.string().refine(S.is(sha256DigestSchema)),
39
+ });
40
+ export const armedWorkflowTriggerSchema = workflowTriggerTargetSchema.extend({
41
+ armedBy: id,
42
+ /** Null for a member's own arm; an Agent arm retains its live source. */
43
+ armedVia: workflowArmedViaSchema.nullable(),
44
+ grantorMembershipVersion: z.number().int().nonnegative(),
45
+ installationAuthority: workflowInstallationAuthoritySchema,
46
+ computerId: id,
47
+ nodeGitAccess: z
48
+ .custom(S.is(nodeGitAccessListSchema))
49
+ .optional(),
50
+ });
51
+ /** The member whose installation grant authorizes this principal's control. */
52
+ export const workflowTriggerPrincipalMember = (principal) => principal.kind === "member" ? principal.memberId : principal.grantorMemberId;
53
+ /** A member controls every decodable trigger; an Agent controls only its own arms. */
54
+ export const workflowTriggerControlledBy = (principal, binding) => principal.kind === "member" ||
55
+ binding.armedVia?.agentId === principal.agentId;
56
+ /** Separate arming principals keep separate stored triggers. */
57
+ export const workflowArmerKey = (armer) => [
58
+ armer.armedBy,
59
+ armer.armedVia?.agentId ?? null,
60
+ ];
61
+ const sameInstallationAuthority = (left, right) => canonicalJson(z.json().parse(left)) === canonicalJson(z.json().parse(right));
62
+ /** Published provenance and current member authority shared by every trigger kind. */
63
+ export function createTeamWorkflowTriggers(options) {
64
+ const arming = {
65
+ async member(memberId) {
66
+ try {
67
+ // The run owner's reader may carry more membership facts; arming pins only the version.
68
+ const { membershipVersion } = await options.runs.member(memberId);
69
+ return { membershipVersion };
70
+ }
71
+ catch (cause) {
72
+ if (cause instanceof Error && options.errors.isDenied(cause))
73
+ return null;
74
+ throw cause;
75
+ }
76
+ },
77
+ readAgentAccess: options.readAgentAccess,
78
+ };
79
+ const pinRefusal = {
80
+ kind: "refused",
81
+ reason: "The Workflows installation changed; re-arm this trigger",
82
+ };
83
+ async function arm(armer, raw, computerId, rawAuthority) {
84
+ const target = workflowTriggerTargetSchema.parse(raw);
85
+ const installationAuthority = workflowInstallationAuthoritySchema.parse(rawAuthority);
86
+ const memberId = armer.armedBy;
87
+ const captured = await captureWorkflowArming(armer, arming);
88
+ if (captured.kind === "refused")
89
+ return {
90
+ kind: "refused",
91
+ reason: "The arming Agent or member is no longer authorized",
92
+ };
93
+ if (!(await options.runs.memberComputer(memberId, computerId)))
94
+ throw options.errors.denied();
95
+ if (target.delivery !== undefined &&
96
+ !(await options.runs.admitDelivery(memberId, target.delivery)))
97
+ return {
98
+ kind: "refused",
99
+ reason: "The chat for this trigger's results is not available to you",
100
+ };
101
+ if (!sameInstallationAuthority(await options.admit(memberId, target.installationId, computerId), installationAuthority))
102
+ return pinRefusal;
103
+ const git = await options.source(memberId, target.installationId, computerId);
104
+ const entries = await options.runs.catalog(memberId);
105
+ const published = await options.published.resolve({ memberId, installationId: target.installationId, computerId, git }, { workflowId: target.workflowId, commit: target.commit });
106
+ if (published === null)
107
+ return {
108
+ kind: "refused",
109
+ reason: "Workflow commit is not published",
110
+ };
111
+ if (target.digest !==
112
+ (await contentDigest(S.decodeUnknownSync(S.Json)(published.definition))))
113
+ return {
114
+ kind: "refused",
115
+ reason: "Workflow publication changed; re-arm after publishing",
116
+ };
117
+ const compiled = await compileForMember(entries, {
118
+ requestId: "trigger-admission",
119
+ definition: published.definition,
120
+ bindings: target.bindings,
121
+ input: {},
122
+ });
123
+ if (compiled.kind === "invalid")
124
+ return {
125
+ kind: "refused",
126
+ reason: "Workflow is no longer valid for this member",
127
+ issues: compiled.issues,
128
+ };
129
+ const trigger = published.definition.triggers?.find((item) => item.id === target.triggerId);
130
+ if (trigger === undefined)
131
+ return {
132
+ kind: "refused",
133
+ reason: "Workflow does not declare this trigger",
134
+ };
135
+ const nodeGitAccess = [];
136
+ const graphs = [
137
+ { graphId: undefined, graph: compiled.plan.definition },
138
+ ...Object.entries(compiled.plan.definition.subgraphs ?? {}).map(([graphId, graph]) => ({ graphId, graph })),
139
+ ];
140
+ for (const { graphId, graph } of graphs) {
141
+ for (const node of graph.nodes) {
142
+ if (node.kind !== "app" || node.gitAccess === undefined)
143
+ continue;
144
+ const { repository } = node.gitAccess;
145
+ if (repository === "$trigger.repository") {
146
+ // Resolved per event at fire time, inside the event App's namespace.
147
+ if (trigger.on !== "event" ||
148
+ trigger.repository === undefined ||
149
+ node.gitAccess.binding !== trigger.app)
150
+ return {
151
+ kind: "refused",
152
+ reason: "This trigger names no event repository for the Workflow's Git access",
153
+ };
154
+ continue;
155
+ }
156
+ const resolved = resolveGitAccess(compiled.plan.bindings, node.gitAccess);
157
+ if (resolved === null)
158
+ return {
159
+ kind: "refused",
160
+ reason: "Git repository binding is unavailable",
161
+ };
162
+ const scope = gitAccessScope(resolved);
163
+ try {
164
+ await options.admitGitAccess(memberId, resolved.repository, scope);
165
+ }
166
+ catch (cause) {
167
+ if (cause instanceof Error && options.errors.isDenied(cause))
168
+ return {
169
+ kind: "refused",
170
+ reason: `You do not have Git ${scope} access to ${repository}`,
171
+ };
172
+ throw cause;
173
+ }
174
+ const entry = { nodeId: node.id, gitAccess: resolved };
175
+ nodeGitAccess.push(graphId === undefined ? entry : { ...entry, graphId });
176
+ }
177
+ }
178
+ const binding = armedWorkflowTriggerSchema.parse({
179
+ ...target,
180
+ ...captured.receipt,
181
+ installationAuthority,
182
+ computerId,
183
+ });
184
+ if (nodeGitAccess.length > 0)
185
+ binding.nodeGitAccess = nodeGitAccess;
186
+ options.runs.triggerPlans.put(binding, compiled.plan);
187
+ return {
188
+ kind: "armed",
189
+ binding,
190
+ trigger,
191
+ definition: published.definition,
192
+ };
193
+ }
194
+ return {
195
+ arm,
196
+ disarm: (binding) => {
197
+ options.runs.triggerPlans.remove(binding);
198
+ },
199
+ refusal: (binding) => options.runs.triggerPlans.refusal(binding),
200
+ invalidateInstallation: (installationId) => {
201
+ options.runs.triggerPlans.invalidateInstallation(installationId);
202
+ },
203
+ /** The single native push owner supplies host-issued Git, never a fire caller. */
204
+ async revalidatePublished(installationId, git) {
205
+ for (const { binding } of options.runs.triggerPlans.installation(installationId)) {
206
+ const published = await options.published.resolve({
207
+ memberId: binding.armedBy,
208
+ installationId,
209
+ computerId: binding.computerId,
210
+ git,
211
+ }, { workflowId: binding.workflowId, commit: binding.commit });
212
+ if (published === null)
213
+ options.runs.triggerPlans.refuse(binding, "commit no longer published");
214
+ }
215
+ },
216
+ async fire(raw, scope, event) {
217
+ const binding = armedWorkflowTriggerSchema.parse(raw);
218
+ try {
219
+ const current = await options.admit(binding.armedBy, binding.installationId, binding.computerId);
220
+ if (!sameInstallationAuthority(current, binding.installationAuthority))
221
+ return pinRefusal;
222
+ const armedBy = await recheckWorkflowArming({
223
+ armedBy: binding.armedBy,
224
+ armedVia: binding.armedVia,
225
+ grantorMembershipVersion: binding.grantorMembershipVersion,
226
+ }, arming);
227
+ if (armedBy.kind === "refused")
228
+ return {
229
+ kind: "refused",
230
+ reason: "The arming Agent or member is no longer authorized",
231
+ };
232
+ const stored = options.runs.triggerPlans.read(binding);
233
+ if (stored?.refusal !== null)
234
+ return {
235
+ kind: "refused",
236
+ reason: stored?.refusal ??
237
+ "Compiled Workflow unavailable; re-arm required",
238
+ };
239
+ const mapped = triggerRun(stored.plan.definition, binding.triggerId, event);
240
+ if (mapped.kind === "ignored")
241
+ return mapped;
242
+ if (mapped.kind === "refused")
243
+ return {
244
+ kind: "refused",
245
+ reason: mapped.code === "invalid_repository"
246
+ ? "Trigger event names no valid repository"
247
+ : "Trigger event does not match the run input",
248
+ };
249
+ const request = {
250
+ requestId: await triggerRequestId({
251
+ scope,
252
+ triggerId: binding.triggerId,
253
+ eventId: event.eventId,
254
+ }),
255
+ input: mapped.input,
256
+ };
257
+ const result = await options.runs.startCachedGraph(binding, mapped.repository === undefined
258
+ ? request
259
+ : { ...request, triggerRepository: mapped.repository });
260
+ if (result.kind === "refused")
261
+ return result;
262
+ if (result.kind === "conflict")
263
+ return {
264
+ kind: "refused",
265
+ reason: "Workflow start was refused after current member admission",
266
+ };
267
+ await options.launch(result);
268
+ return result;
269
+ }
270
+ catch (cause) {
271
+ if (cause instanceof Error && options.errors.isDenied(cause))
272
+ return {
273
+ kind: "refused",
274
+ reason: "Arming member no longer has Workflow or Computer access",
275
+ };
276
+ throw cause;
277
+ }
278
+ },
279
+ };
280
+ }