@fungi.computer/oyster 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/README.md +228 -176
- package/dist/agent-avatars.d.ts +103 -0
- package/dist/agent-avatars.js +66 -0
- package/dist/agents.d.ts +12 -2
- package/dist/agents.js +39 -2
- package/dist/app-builds.d.ts +76 -17
- package/dist/app-builds.js +207 -63
- package/dist/app-sources.d.ts +7 -22
- package/dist/app-sources.js +49 -14
- package/dist/apps/gate.d.ts +7 -7
- package/dist/apps/gate.js +6 -6
- package/dist/apps/installation.d.ts +35 -35
- package/dist/apps/installation.js +20 -19
- package/dist/build/artifact-writer.d.ts +40 -0
- package/dist/build/artifact-writer.js +181 -0
- package/dist/build/build-jobs.js +7 -1
- package/dist/build/build-policy.d.ts +10 -0
- package/dist/build/build-policy.js +10 -0
- package/dist/build/build-step.d.ts +12 -1
- package/dist/build/build-step.js +9 -1
- package/dist/build/candidate-builder.d.ts +68 -0
- package/dist/build/candidate-builder.js +91 -0
- package/dist/build/candidate-cli.d.ts +1 -0
- package/dist/build/candidate-cli.js +119 -0
- package/dist/build/candidate-cli.mjs +28220 -0
- package/dist/build/content-objects.d.ts +37 -0
- package/dist/build/content-objects.js +53 -0
- package/dist/build/effect.d.ts +18 -0
- package/dist/build/effect.js +52 -12
- package/dist/build/git-environment.d.ts +3 -0
- package/dist/build/git-environment.js +21 -9
- package/dist/build/local-build.d.ts +13 -3
- package/dist/build/local-build.js +149 -99
- package/dist/build/snapshot-package-store.d.ts +10 -0
- package/dist/build/snapshot-package-store.js +15 -0
- package/dist/build/snapshot-program-scripts.js +1 -1
- package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
- package/dist/build/snapshot-programs/snapshot-path.js +6 -0
- package/dist/build/submission-contract.d.ts +461 -17
- package/dist/build/submission-contract.js +136 -26
- package/dist/build/workspace-build.d.ts +7 -2
- package/dist/build/workspace-build.js +55 -11
- package/dist/build-contract.d.ts +3 -0
- package/dist/build-contract.js +3 -0
- package/dist/build-egress.d.ts +62 -0
- package/dist/build-egress.js +148 -0
- package/dist/build.d.ts +1 -0
- package/dist/build.js +1 -0
- package/dist/cloudflare.js +4 -4
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.d.ts +1 -1
- package/dist/frame-binding-wire.js +1 -1
- package/dist/git-journal.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/installation-consent.d.ts +3 -1
- package/dist/installation-consent.js +2 -0
- package/dist/mcp-owner.js +20 -8
- package/dist/pack.d.ts +6 -3
- package/dist/pack.js +28 -11
- package/dist/page/gill-observation.d.ts +4 -0
- package/dist/page/gill-observation.js +31 -0
- package/dist/page/mount.d.ts +4 -8
- package/dist/page/window-launch.d.ts +18 -0
- package/dist/page/window-launch.js +11 -0
- package/dist/page-script.js +4 -4
- package/dist/release.d.ts +11 -0
- package/dist/release.js +4 -1
- package/dist/shell/app-directory.d.ts +50 -45
- package/dist/shell/app-directory.js +19 -18
- package/dist/shell/shell-module.d.ts +26 -2
- package/dist/shell/shell-module.js +51 -2
- package/dist/shell/window-manager.d.ts +5 -1
- package/dist/shell/window-manager.js +21 -2
- package/dist/shell/workbench-directory.d.ts +332 -0
- package/dist/shell/workbench-directory.js +35 -0
- package/dist/shell/workbench-host-types.d.ts +1 -1
- package/dist/shell/workbench-host.js +5 -0
- package/dist/shell/workbench-model.d.ts +3 -1
- package/dist/source/index.d.ts +6 -7
- package/dist/source/index.js +6 -7
- package/dist/source/template-push.d.ts +33 -0
- package/dist/source/template-push.js +223 -0
- package/dist/source/template-repository.d.ts +2 -0
- package/dist/source/template-repository.js +13 -0
- package/dist/source/{template-write.d.ts → template-seed.d.ts} +19 -20
- package/dist/source/template-seed.js +80 -0
- package/dist/standalone/agent-directory.d.ts +4 -0
- package/dist/standalone/agent-directory.js +4 -0
- package/dist/standalone/agents.d.ts +227 -0
- package/dist/standalone/agents.js +834 -0
- package/dist/standalone/app-builds-binding.d.ts +36 -0
- package/dist/standalone/app-builds-binding.js +119 -0
- package/dist/standalone/app-set.d.ts +91 -0
- package/dist/standalone/app-set.js +395 -0
- package/dist/standalone/app-source.d.ts +41 -0
- package/dist/standalone/app-source.js +59 -0
- package/dist/standalone/builds.d.ts +18 -0
- package/dist/standalone/builds.js +101 -0
- package/dist/standalone/computer-egress.d.ts +37 -0
- package/dist/standalone/computer-egress.js +111 -0
- package/dist/standalone/computer.d.ts +122 -0
- package/dist/standalone/computer.js +714 -0
- package/dist/standalone/install-apps-cli.d.ts +2 -0
- package/dist/standalone/install-apps-cli.js +126 -0
- package/dist/standalone/instance.d.ts +35 -0
- package/dist/standalone/instance.js +74 -0
- package/dist/standalone/media.d.ts +76 -0
- package/dist/standalone/media.js +227 -0
- package/dist/standalone/preview.d.ts +51 -0
- package/dist/standalone/preview.js +167 -0
- package/dist/standalone/registry.d.ts +102 -0
- package/dist/standalone/registry.js +193 -0
- package/dist/standalone/tenancy.d.ts +42 -0
- package/dist/standalone/tenancy.js +241 -0
- package/dist/standalone/worker.d.ts +55 -0
- package/dist/standalone/worker.js +147 -0
- package/dist/standalone/workflow-authority-readers.d.ts +16 -0
- package/dist/standalone/workflow-authority-readers.js +35 -0
- package/dist/standalone/workflow-binding.d.ts +216 -0
- package/dist/standalone/workflow-binding.js +217 -0
- package/dist/standalone/workflow-events.d.ts +46 -0
- package/dist/standalone/workflow-events.js +39 -0
- package/dist/standalone/workflow-git-capture.d.ts +7 -0
- package/dist/standalone/workflow-git-capture.js +62 -0
- package/dist/standalone/workflow-host.d.ts +955 -0
- package/dist/standalone/workflow-host.js +778 -0
- package/dist/standalone/workflow-node-capture.d.ts +13 -0
- package/dist/standalone/workflow-node-capture.js +72 -0
- package/dist/standalone/workflow-root-authority.d.ts +27 -0
- package/dist/standalone/workflow-root-authority.js +87 -0
- package/dist/standalone/workflow-schedules.d.ts +52 -0
- package/dist/standalone/workflow-schedules.js +40 -0
- package/dist/standalone/workflow-trigger-host.d.ts +1716 -0
- package/dist/standalone/workflow-trigger-host.js +52 -0
- package/dist/standalone/workflows.d.ts +713 -0
- package/dist/standalone/workflows.js +554 -0
- package/dist/tenancy/auth0.d.ts +64 -0
- package/dist/tenancy/auth0.js +198 -0
- package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
- package/dist/tenancy/cloudflare/rate-limit.js +19 -0
- package/dist/tenancy/cloudflare/registry.d.ts +50 -0
- package/dist/tenancy/cloudflare/registry.js +301 -0
- package/dist/tenancy/cloudflare.d.ts +2 -0
- package/dist/tenancy/cloudflare.js +2 -0
- package/dist/tenancy/cold-directory.d.ts +13 -0
- package/dist/tenancy/cold-directory.js +43 -0
- package/dist/tenancy/contracts.d.ts +195 -0
- package/dist/tenancy/contracts.js +1 -0
- package/dist/tenancy/index.d.ts +19 -0
- package/dist/tenancy/index.js +156 -0
- package/dist/tenancy/owner-key.d.ts +26 -0
- package/dist/tenancy/owner-key.js +63 -0
- package/dist/tenancy/oyster.d.ts +4 -0
- package/dist/tenancy/oyster.js +25 -0
- package/dist/tenancy/registry-record.d.ts +7 -0
- package/dist/tenancy/registry-record.js +44 -0
- package/dist/tenancy/registry.d.ts +27 -0
- package/dist/tenancy/registry.js +138 -0
- package/dist/tenancy/resolve.d.ts +7 -0
- package/dist/tenancy/resolve.js +64 -0
- package/dist/workers-git.js +1 -4
- package/dist/workers.d.ts +2 -2
- package/dist/workers.js +3 -1
- package/docs/api/build-contract.md +829 -32
- package/docs/api/build.md +253 -17
- package/docs/api/modules.md +40 -32
- package/docs/api/pack.md +6 -3
- package/docs/api/shell/app-directory.md +267 -0
- package/docs/api/shell/shell-module.md +64 -3
- package/docs/api/shell/workbench-directory.md +119 -0
- package/docs/api/shell/workbench.md +19 -0
- package/docs/api/shell.md +127 -0
- package/docs/api/tenancy/auth0.md +110 -0
- package/docs/api/tenancy/cloudflare.md +221 -0
- package/docs/api/tenancy/cold-directory.md +46 -0
- package/docs/api/tenancy/owner-key.md +63 -0
- package/docs/api/tenancy/registry.md +77 -0
- package/docs/api/tenancy/resolve.md +69 -0
- package/docs/api/tenancy.md +430 -0
- package/docs/api/workers-git.md +140 -21
- package/docs/api/workers.md +15 -2
- package/docs/site/agent-quickstart.mdx +52 -55
- package/examples/standalone/worker.ts +7 -14
- package/examples/standalone/wrangler.jsonc +67 -6
- package/package.json +78 -25
- package/dist/build/r2-artifact-writer.d.ts +0 -31
- package/dist/build/r2-artifact-writer.js +0 -179
- package/dist/source/template-write.js +0 -344
- package/docs/media/architecture.mmd +0 -32
- package/docs/media/architecture.png +0 -0
- package/docs/media/bonsai-network.png +0 -0
- package/docs/media/bonsai-repository.png +0 -0
- package/examples/git/worker.ts +0 -39
- package/examples/git/wrangler.jsonc +0 -32
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import * as S from "effect/Schema";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { eventDisarmInputSchema, scheduleDisarmInputSchema } from "@fungi.computer/workflows-wire";
|
|
4
|
+
import type { Installation } from "../apps/index.js";
|
|
5
|
+
import type { WorkflowGitPassResult } from "@fungi.computer/agar/computer-contracts";
|
|
6
|
+
import { type AppEventFact } from "@fungi.computer/puffball/app-event-facts";
|
|
7
|
+
import type { HostRecipientEvent } from "../workers-git.js";
|
|
8
|
+
import type { FrameBindingProps } from "../cloudflare.js";
|
|
9
|
+
import type { InvocationFacts, InvocationFrame } from "../invocation.js";
|
|
10
|
+
import type { OysterJson } from "../host.js";
|
|
11
|
+
import type { StandaloneWorkflowRunsBase } from "./workflows.js";
|
|
12
|
+
export declare const runKey: S.Struct<{
|
|
13
|
+
readonly runId: S.String;
|
|
14
|
+
}>;
|
|
15
|
+
export declare const reviewKey: S.Struct<{
|
|
16
|
+
readonly runId: S.String;
|
|
17
|
+
readonly nodeId: S.String;
|
|
18
|
+
readonly iteration: S.String;
|
|
19
|
+
readonly answer: S.Union<readonly [S.Struct<{
|
|
20
|
+
readonly approved: S.Boolean;
|
|
21
|
+
}>, S.Struct<{
|
|
22
|
+
readonly choice: S.String;
|
|
23
|
+
readonly note: S.optionalKey<S.String>;
|
|
24
|
+
}>]>;
|
|
25
|
+
}>;
|
|
26
|
+
/** The App supplies its immutable publication row; the host supplies the library. */
|
|
27
|
+
export declare const triggerArmTargetSchema: z.ZodObject<{
|
|
28
|
+
digest: z.ZodString & z.ZodType<string & {
|
|
29
|
+
readonly appReleaseKind: "Sha256Digest";
|
|
30
|
+
}, string, z.core.$ZodTypeInternals<string & {
|
|
31
|
+
readonly appReleaseKind: "Sha256Digest";
|
|
32
|
+
}, string>>;
|
|
33
|
+
bindings: z.ZodRecord<z.ZodLazy<z.ZodString>, z.ZodUnion<readonly [z.ZodString, z.ZodLazy<z.ZodObject<{
|
|
34
|
+
kind: z.ZodLiteral<"team-source">;
|
|
35
|
+
}, z.core.$strict>>]>>;
|
|
36
|
+
workflowId: z.ZodString;
|
|
37
|
+
delivery: z.ZodOptional<z.ZodCustom<{
|
|
38
|
+
readonly agentId: string;
|
|
39
|
+
readonly sessionId: string;
|
|
40
|
+
}, {
|
|
41
|
+
readonly agentId: string;
|
|
42
|
+
readonly sessionId: string;
|
|
43
|
+
}>>;
|
|
44
|
+
triggerId: z.ZodString;
|
|
45
|
+
commit: z.ZodString;
|
|
46
|
+
}, z.core.$strict>;
|
|
47
|
+
export type TriggerArmTarget = z.output<typeof triggerArmTargetSchema>;
|
|
48
|
+
export type ScheduleDisarmInput = z.output<typeof scheduleDisarmInputSchema>;
|
|
49
|
+
export type EventDisarmInput = z.output<typeof eventDisarmInputSchema>;
|
|
50
|
+
/** The frame gate needs only these run-owner calls; it re-decodes each reply with the method's owner schema. */
|
|
51
|
+
export type WorkflowRunCalls = Pick<StandaloneWorkflowRunsBase, "catalog" | "start" | "status" | "cancel" | "answerReview" | "armSchedule" | "disarmSchedule" | "schedules" | "armEventTrigger" | "disarmEventTrigger" | "eventTriggers" | "runs">;
|
|
52
|
+
export type WorkflowBindingEnv = Readonly<{
|
|
53
|
+
WORKFLOW_RUNS: Readonly<{
|
|
54
|
+
getByName(installationId: string): WorkflowRunCalls;
|
|
55
|
+
}>;
|
|
56
|
+
}>;
|
|
57
|
+
/** Each Workflows library receives emitted App events at its run owner. */
|
|
58
|
+
export type WorkflowEventEnv = Readonly<{
|
|
59
|
+
WORKFLOW_RUNS: Readonly<{
|
|
60
|
+
getByName(installationId: string): Readonly<{
|
|
61
|
+
deliverEvent(fact: AppEventFact): Promise<"accepted">;
|
|
62
|
+
}>;
|
|
63
|
+
}>;
|
|
64
|
+
}>;
|
|
65
|
+
/** The guest factory and the live Instance gate consume the same owner grammars. */
|
|
66
|
+
export declare const workflowFrameMethods: {
|
|
67
|
+
WORKFLOWS: {
|
|
68
|
+
[k: string]: {
|
|
69
|
+
decodeArgs: ((input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly [{
|
|
70
|
+
readonly installationId?: string | undefined;
|
|
71
|
+
}]) | ((input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly [{
|
|
72
|
+
readonly input: {
|
|
73
|
+
readonly [x: string]: S.Json;
|
|
74
|
+
};
|
|
75
|
+
readonly bindings: Record<string, string | {
|
|
76
|
+
kind: "team-source";
|
|
77
|
+
}>;
|
|
78
|
+
readonly requestId: string;
|
|
79
|
+
readonly definition: unknown;
|
|
80
|
+
readonly delivery?: {
|
|
81
|
+
readonly agentId: string;
|
|
82
|
+
readonly sessionId: string;
|
|
83
|
+
} | undefined;
|
|
84
|
+
readonly published?: undefined;
|
|
85
|
+
} | {
|
|
86
|
+
readonly input: {
|
|
87
|
+
readonly [x: string]: S.Json;
|
|
88
|
+
};
|
|
89
|
+
readonly bindings: Record<string, string | {
|
|
90
|
+
kind: "team-source";
|
|
91
|
+
}>;
|
|
92
|
+
readonly requestId: string;
|
|
93
|
+
readonly published: {
|
|
94
|
+
workflowId: string;
|
|
95
|
+
commit: string;
|
|
96
|
+
digest: string & {
|
|
97
|
+
readonly appReleaseKind: "Sha256Digest";
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
readonly definition: S.Json;
|
|
101
|
+
readonly delivery?: {
|
|
102
|
+
readonly agentId: string;
|
|
103
|
+
readonly sessionId: string;
|
|
104
|
+
} | undefined;
|
|
105
|
+
}]) | ((input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly [{
|
|
106
|
+
readonly runId: string;
|
|
107
|
+
}]) | ((input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly [{
|
|
108
|
+
readonly runId: string;
|
|
109
|
+
readonly nodeId: string;
|
|
110
|
+
readonly iteration: string;
|
|
111
|
+
readonly answer: {
|
|
112
|
+
readonly approved: boolean;
|
|
113
|
+
} | {
|
|
114
|
+
readonly choice: string;
|
|
115
|
+
readonly note?: string | undefined;
|
|
116
|
+
};
|
|
117
|
+
}]) | ((args: unknown) => [{
|
|
118
|
+
digest: string & {
|
|
119
|
+
readonly appReleaseKind: "Sha256Digest";
|
|
120
|
+
};
|
|
121
|
+
bindings: Record<string, string | {
|
|
122
|
+
kind: "team-source";
|
|
123
|
+
}>;
|
|
124
|
+
workflowId: string;
|
|
125
|
+
triggerId: string;
|
|
126
|
+
commit: string;
|
|
127
|
+
delivery?: {
|
|
128
|
+
readonly agentId: string;
|
|
129
|
+
readonly sessionId: string;
|
|
130
|
+
} | undefined;
|
|
131
|
+
}]) | ((args: unknown) => [{
|
|
132
|
+
scheduleId: string;
|
|
133
|
+
}]) | ((args: unknown) => [Record<string, never>]) | ((args: unknown) => [{
|
|
134
|
+
digest: string & {
|
|
135
|
+
readonly appReleaseKind: "Sha256Digest";
|
|
136
|
+
};
|
|
137
|
+
bindings: Record<string, string | {
|
|
138
|
+
kind: "team-source";
|
|
139
|
+
}>;
|
|
140
|
+
workflowId: string;
|
|
141
|
+
triggerId: string;
|
|
142
|
+
commit: string;
|
|
143
|
+
delivery?: {
|
|
144
|
+
readonly agentId: string;
|
|
145
|
+
readonly sessionId: string;
|
|
146
|
+
} | undefined;
|
|
147
|
+
}]) | ((args: unknown) => [{
|
|
148
|
+
eventTriggerId: string;
|
|
149
|
+
}]) | ((args: unknown) => [Record<string, never>]) | ((args: unknown) => [{
|
|
150
|
+
workflowId?: string | undefined;
|
|
151
|
+
}]);
|
|
152
|
+
decodeResult: (value: unknown) => z.core.util.JSONType;
|
|
153
|
+
requiredOperations: () => ("catalog" | "runGraph" | "armSchedule" | "disarmSchedule" | "schedules" | "armEventTrigger" | "disarmEventTrigger" | "eventTriggers" | "cancel" | "answerReview" | "graphStatus" | "runs")[];
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* The host recipients of an App's accepted EVENTS.emit: every enabled
|
|
159
|
+
* Workflows library approved to arm triggers. The host records one durable
|
|
160
|
+
* delivery per library and returns; the emit never waits on a library.
|
|
161
|
+
*/
|
|
162
|
+
export declare function workflowEventRecipients(installations: Readonly<{
|
|
163
|
+
list(): Promise<readonly Pick<Installation, "installationId" | "status" | "approvedGrants">[]>;
|
|
164
|
+
}>): Promise<readonly string[]>;
|
|
165
|
+
/**
|
|
166
|
+
* One recorded delivery to one library: the host-attested fact reaches its
|
|
167
|
+
* run owner, which fires only its own armed triggers and replays by event id,
|
|
168
|
+
* so the delivery owner's retries never start a run twice.
|
|
169
|
+
*/
|
|
170
|
+
export declare function deliverWorkflowEvent(env: WorkflowEventEnv, libraryId: string, event: HostRecipientEvent): Promise<void>;
|
|
171
|
+
/** What a Workflow node frame attests when it asks for its Git step's pass. */
|
|
172
|
+
export declare const workflowGitPassRequestSchema: z.ZodLazy<z.ZodObject<{
|
|
173
|
+
installationId: z.ZodString;
|
|
174
|
+
idempotencyKey: z.ZodString;
|
|
175
|
+
workflow: import("zod/mini").ZodMiniObject<{
|
|
176
|
+
workflowId: import("zod/mini").ZodMiniString<string>;
|
|
177
|
+
runId: import("zod/mini").ZodMiniString<string>;
|
|
178
|
+
nodeId: import("zod/mini").ZodMiniString<string>;
|
|
179
|
+
armedBy: import("zod/mini").ZodMiniDiscriminatedUnion<[import("zod/mini").ZodMiniObject<{
|
|
180
|
+
kind: import("zod/mini").ZodMiniLiteral<"member">;
|
|
181
|
+
memberId: import("zod/mini").ZodMiniString<string>;
|
|
182
|
+
}, z.core.$strip>, import("zod/mini").ZodMiniObject<{
|
|
183
|
+
kind: import("zod/mini").ZodMiniLiteral<"agent">;
|
|
184
|
+
agentId: import("zod/mini").ZodMiniString<string>;
|
|
185
|
+
}, z.core.$strip>, import("zod/mini").ZodMiniObject<{
|
|
186
|
+
kind: import("zod/mini").ZodMiniLiteral<"team-key">;
|
|
187
|
+
keyId: import("zod/mini").ZodMiniString<string>;
|
|
188
|
+
}, z.core.$strip>], "kind">;
|
|
189
|
+
}, z.core.$strip>;
|
|
190
|
+
memberId: z.ZodString;
|
|
191
|
+
computerId: z.ZodString;
|
|
192
|
+
}, z.core.$strict>>;
|
|
193
|
+
export type WorkflowGitPassRequest = z.output<typeof workflowGitPassRequestSchema>;
|
|
194
|
+
/** The library run owners a node frame's pass request may reach. */
|
|
195
|
+
export type WorkflowGitPassEnv = Readonly<{
|
|
196
|
+
OYSTER_COMPUTER_ID: string;
|
|
197
|
+
WORKFLOW_RUNS: Readonly<{
|
|
198
|
+
getByName(installationId: string): Readonly<{
|
|
199
|
+
openWorkflowGitPass(request: WorkflowGitPassRequest): Promise<WorkflowGitPassResult | null>;
|
|
200
|
+
}>;
|
|
201
|
+
}>;
|
|
202
|
+
}>;
|
|
203
|
+
/**
|
|
204
|
+
* COMPUTER.workflowGitPass from a published Workflow node frame. Only the
|
|
205
|
+
* frame's host-attested Workflow, call key, member and pinned Computer cross;
|
|
206
|
+
* the library that dispatched that Git step derives the scope from its own
|
|
207
|
+
* stored run and mints or renews the pass.
|
|
208
|
+
*/
|
|
209
|
+
export declare function openWorkflowGitPass(env: WorkflowGitPassEnv, installations: Readonly<{
|
|
210
|
+
list(): Promise<readonly Pick<Installation, "installationId" | "status" | "approvedGrants">[]>;
|
|
211
|
+
}>, frame: Readonly<{
|
|
212
|
+
facts: InvocationFacts;
|
|
213
|
+
signal: AbortSignal;
|
|
214
|
+
}>, args: readonly unknown[]): Promise<WorkflowGitPassResult>;
|
|
215
|
+
/** Called only after the Instance's shared gate resolves this live frame. */
|
|
216
|
+
export declare function dispatchWorkflowBinding(env: WorkflowBindingEnv, frame: InvocationFrame, props: FrameBindingProps, method: string, args: readonly unknown[]): Promise<OysterJson>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import * as S from "effect/Schema";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { graphReviewSchema, reviewAnswerSchema } from "@fungi.computer/braid";
|
|
4
|
+
import { graphStartRequestSchema, workflowTriggerTargetSchema, } from "@fungi.computer/braid/runtime";
|
|
5
|
+
import { runReply, answerReviewReply, catalogReplySchema, statusReply, cancelReplySchema, runsReplySchema, runsInputSchema, eventArmReplySchema, eventDisarmInputSchema, eventTriggersReplySchema, scheduleArmReplySchema, scheduleDisarmInputSchema, schedulesReplySchema, triggerDisarmReplySchema, triggerListInputSchema, } from "@fungi.computer/workflows-wire";
|
|
6
|
+
import { workflowAttestationSchema } from "@fungi.computer/puffball/app-method-runtime";
|
|
7
|
+
import { appEventFactSchema, } from "@fungi.computer/puffball/app-event-facts";
|
|
8
|
+
import { AppInstanceError } from "../index.js";
|
|
9
|
+
export const runKey = S.Struct({ runId: S.String });
|
|
10
|
+
export const reviewKey = S.Struct({
|
|
11
|
+
runId: graphReviewSchema.fields.runId,
|
|
12
|
+
nodeId: graphReviewSchema.fields.nodeId,
|
|
13
|
+
iteration: graphReviewSchema.fields.iteration,
|
|
14
|
+
answer: reviewAnswerSchema,
|
|
15
|
+
});
|
|
16
|
+
/** The App supplies its immutable publication row; the host supplies the library. */
|
|
17
|
+
export const triggerArmTargetSchema = workflowTriggerTargetSchema.omit({
|
|
18
|
+
installationId: true,
|
|
19
|
+
});
|
|
20
|
+
const effectArgs = (schema) => S.decodeUnknownSync(schema, { onExcessProperty: "error" });
|
|
21
|
+
const workflowArgs = {
|
|
22
|
+
catalog: effectArgs(S.Tuple([S.Struct({ installationId: S.optionalKey(S.String) })])),
|
|
23
|
+
runGraph: effectArgs(S.Tuple([graphStartRequestSchema])),
|
|
24
|
+
graphStatus: effectArgs(S.Tuple([runKey])),
|
|
25
|
+
cancel: effectArgs(S.Tuple([runKey])),
|
|
26
|
+
answerReview: effectArgs(S.Tuple([reviewKey])),
|
|
27
|
+
armSchedule: (args) => z.tuple([triggerArmTargetSchema]).parse(args),
|
|
28
|
+
disarmSchedule: (args) => z.tuple([scheduleDisarmInputSchema]).parse(args),
|
|
29
|
+
schedules: (args) => z.tuple([triggerListInputSchema]).parse(args),
|
|
30
|
+
armEventTrigger: (args) => z.tuple([triggerArmTargetSchema]).parse(args),
|
|
31
|
+
disarmEventTrigger: (args) => z.tuple([eventDisarmInputSchema]).parse(args),
|
|
32
|
+
eventTriggers: (args) => z.tuple([triggerListInputSchema]).parse(args),
|
|
33
|
+
runs: (args) => z.tuple([runsInputSchema]).parse(args),
|
|
34
|
+
};
|
|
35
|
+
const workflowResults = {
|
|
36
|
+
catalog: catalogReplySchema,
|
|
37
|
+
runGraph: runReply,
|
|
38
|
+
graphStatus: statusReply,
|
|
39
|
+
cancel: cancelReplySchema,
|
|
40
|
+
answerReview: answerReviewReply,
|
|
41
|
+
armSchedule: scheduleArmReplySchema,
|
|
42
|
+
disarmSchedule: triggerDisarmReplySchema,
|
|
43
|
+
schedules: schedulesReplySchema,
|
|
44
|
+
armEventTrigger: eventArmReplySchema,
|
|
45
|
+
disarmEventTrigger: triggerDisarmReplySchema,
|
|
46
|
+
eventTriggers: eventTriggersReplySchema,
|
|
47
|
+
runs: runsReplySchema,
|
|
48
|
+
};
|
|
49
|
+
const workflowMethod = z.enum([
|
|
50
|
+
"catalog",
|
|
51
|
+
"runGraph",
|
|
52
|
+
"graphStatus",
|
|
53
|
+
"cancel",
|
|
54
|
+
"answerReview",
|
|
55
|
+
"armSchedule",
|
|
56
|
+
"disarmSchedule",
|
|
57
|
+
"schedules",
|
|
58
|
+
"armEventTrigger",
|
|
59
|
+
"disarmEventTrigger",
|
|
60
|
+
"eventTriggers",
|
|
61
|
+
"runs",
|
|
62
|
+
]);
|
|
63
|
+
/** The guest factory and the live Instance gate consume the same owner grammars. */
|
|
64
|
+
export const workflowFrameMethods = {
|
|
65
|
+
WORKFLOWS: Object.fromEntries(workflowMethod.options.map((method) => [
|
|
66
|
+
method,
|
|
67
|
+
{
|
|
68
|
+
decodeArgs: workflowArgs[method],
|
|
69
|
+
decodeResult: (value) => z.json().parse(workflowResults[method].parse(value)),
|
|
70
|
+
requiredOperations: () => [method],
|
|
71
|
+
},
|
|
72
|
+
])),
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* The host recipients of an App's accepted EVENTS.emit: every enabled
|
|
76
|
+
* Workflows library approved to arm triggers. The host records one durable
|
|
77
|
+
* delivery per library and returns; the emit never waits on a library.
|
|
78
|
+
*/
|
|
79
|
+
export async function workflowEventRecipients(installations) {
|
|
80
|
+
return (await installations.list())
|
|
81
|
+
.filter((installation) => installation.status === "enabled" &&
|
|
82
|
+
installation.approvedGrants.some((grant) => grant.capability === "workflow.arm"))
|
|
83
|
+
.map((installation) => installation.installationId);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* One recorded delivery to one library: the host-attested fact reaches its
|
|
87
|
+
* run owner, which fires only its own armed triggers and replays by event id,
|
|
88
|
+
* so the delivery owner's retries never start a run twice.
|
|
89
|
+
*/
|
|
90
|
+
export async function deliverWorkflowEvent(env, libraryId, event) {
|
|
91
|
+
const fact = appEventFactSchema.parse({
|
|
92
|
+
type: event.type,
|
|
93
|
+
payload: event.payload,
|
|
94
|
+
eventId: event.eventId,
|
|
95
|
+
installationId: event.source.installationId,
|
|
96
|
+
appId: event.source.appId,
|
|
97
|
+
releaseId: event.source.releaseId,
|
|
98
|
+
});
|
|
99
|
+
await env.WORKFLOW_RUNS.getByName(libraryId).deliverEvent(fact);
|
|
100
|
+
}
|
|
101
|
+
/** What a Workflow node frame attests when it asks for its Git step's pass. */
|
|
102
|
+
export const workflowGitPassRequestSchema = z.lazy(() => z.strictObject({
|
|
103
|
+
installationId: z.string().min(1).max(256),
|
|
104
|
+
idempotencyKey: z.string().min(1).max(128),
|
|
105
|
+
workflow: workflowAttestationSchema,
|
|
106
|
+
memberId: z.string().min(1).max(256),
|
|
107
|
+
computerId: z.string().min(1).max(256),
|
|
108
|
+
}));
|
|
109
|
+
/**
|
|
110
|
+
* COMPUTER.workflowGitPass from a published Workflow node frame. Only the
|
|
111
|
+
* frame's host-attested Workflow, call key, member and pinned Computer cross;
|
|
112
|
+
* the library that dispatched that Git step derives the scope from its own
|
|
113
|
+
* stored run and mints or renews the pass.
|
|
114
|
+
*/
|
|
115
|
+
export async function openWorkflowGitPass(env, installations, frame, args) {
|
|
116
|
+
z.tuple([z.strictObject({})]).parse(args);
|
|
117
|
+
const { subject, selection } = frame.facts;
|
|
118
|
+
if (subject.kind !== "call" ||
|
|
119
|
+
subject.context.workflow === undefined ||
|
|
120
|
+
subject.context.idempotencyKey === undefined ||
|
|
121
|
+
subject.context.actor.kind !== "member" ||
|
|
122
|
+
subject.context.computerId !== env.OYSTER_COMPUTER_ID ||
|
|
123
|
+
selection.owner.kind !== "installation")
|
|
124
|
+
throw new AppInstanceError("revoked");
|
|
125
|
+
const request = workflowGitPassRequestSchema.parse({
|
|
126
|
+
installationId: selection.owner.id,
|
|
127
|
+
idempotencyKey: subject.context.idempotencyKey,
|
|
128
|
+
workflow: subject.context.workflow,
|
|
129
|
+
memberId: subject.context.actor.memberId,
|
|
130
|
+
computerId: subject.context.computerId,
|
|
131
|
+
});
|
|
132
|
+
const libraries = (await installations.list()).filter((installation) => installation.status === "enabled" &&
|
|
133
|
+
installation.approvedGrants.some((grant) => grant.capability === "workflow.run"));
|
|
134
|
+
for (const library of libraries) {
|
|
135
|
+
frame.signal.throwIfAborted();
|
|
136
|
+
const pass = await env.WORKFLOW_RUNS.getByName(library.installationId).openWorkflowGitPass(request);
|
|
137
|
+
if (pass !== null)
|
|
138
|
+
return pass;
|
|
139
|
+
}
|
|
140
|
+
throw new AppInstanceError("revoked");
|
|
141
|
+
}
|
|
142
|
+
/** Called only after the Instance's shared gate resolves this live frame. */
|
|
143
|
+
export async function dispatchWorkflowBinding(env, frame, props, method, args) {
|
|
144
|
+
frame.signal.throwIfAborted();
|
|
145
|
+
if (props.binding !== "WORKFLOWS" ||
|
|
146
|
+
frame.facts.subject.kind !== "call" ||
|
|
147
|
+
frame.facts.selection.owner.kind !== "installation")
|
|
148
|
+
throw new AppInstanceError("revoked");
|
|
149
|
+
const runs = env.WORKFLOW_RUNS.getByName(frame.facts.selection.owner.id);
|
|
150
|
+
let result;
|
|
151
|
+
switch (method) {
|
|
152
|
+
case "catalog": {
|
|
153
|
+
const [input] = workflowArgs.catalog(args);
|
|
154
|
+
result = await runs.catalog(frame.facts, input);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "runGraph": {
|
|
158
|
+
const [input] = workflowArgs.runGraph(args);
|
|
159
|
+
result = await runs.start(frame.facts, input);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "graphStatus": {
|
|
163
|
+
const [input] = workflowArgs.graphStatus(args);
|
|
164
|
+
result = await runs.status(frame.facts, input);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "cancel": {
|
|
168
|
+
const [input] = workflowArgs.cancel(args);
|
|
169
|
+
result = await runs.cancel(frame.facts, input);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "answerReview": {
|
|
173
|
+
const [input] = workflowArgs.answerReview(args);
|
|
174
|
+
result = await runs.answerReview(frame.facts, input);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case "armSchedule": {
|
|
178
|
+
const [input] = workflowArgs.armSchedule(args);
|
|
179
|
+
result = await runs.armSchedule(frame.facts, input);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case "disarmSchedule": {
|
|
183
|
+
const [input] = workflowArgs.disarmSchedule(args);
|
|
184
|
+
result = await runs.disarmSchedule(frame.facts, input);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "schedules":
|
|
188
|
+
workflowArgs.schedules(args);
|
|
189
|
+
result = await runs.schedules(frame.facts);
|
|
190
|
+
break;
|
|
191
|
+
case "armEventTrigger": {
|
|
192
|
+
const [input] = workflowArgs.armEventTrigger(args);
|
|
193
|
+
result = await runs.armEventTrigger(frame.facts, input);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
case "disarmEventTrigger": {
|
|
197
|
+
const [input] = workflowArgs.disarmEventTrigger(args);
|
|
198
|
+
result = await runs.disarmEventTrigger(frame.facts, input);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case "eventTriggers":
|
|
202
|
+
workflowArgs.eventTriggers(args);
|
|
203
|
+
result = await runs.eventTriggers(frame.facts);
|
|
204
|
+
break;
|
|
205
|
+
case "runs": {
|
|
206
|
+
const [input] = workflowArgs.runs(args);
|
|
207
|
+
result = await runs.runs(frame.facts, input.workflowId === undefined ? {} : { workflowId: input.workflowId });
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
default:
|
|
211
|
+
throw new AppInstanceError("revoked");
|
|
212
|
+
}
|
|
213
|
+
frame.signal.throwIfAborted();
|
|
214
|
+
return z
|
|
215
|
+
.json()
|
|
216
|
+
.parse(workflowResults[workflowMethod.parse(method)].parse(result));
|
|
217
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createWorkflowEventRouter } from "@fungi.computer/braid/runtime";
|
|
2
|
+
import type { AppEventFact } from "@fungi.computer/puffball/app-event-facts";
|
|
3
|
+
import { type EventDisarmInput, type TriggerArmTarget } from "./workflow-binding.js";
|
|
4
|
+
import type { TriggerAdmission } from "./workflow-schedules.js";
|
|
5
|
+
export type EventTriggerOperation = "armEventTrigger" | "disarmEventTrigger" | "eventTriggers";
|
|
6
|
+
/** Host controls and App-event delivery compose the shared event router unchanged. */
|
|
7
|
+
export declare function createStandaloneWorkflowEvents<Frame>(options: Parameters<typeof createWorkflowEventRouter>[0] & {
|
|
8
|
+
admitInvocation(frame: Frame, operation: EventTriggerOperation): Promise<TriggerAdmission>;
|
|
9
|
+
}): {
|
|
10
|
+
/** A host-recorded App event; matching armed triggers start their runs. */
|
|
11
|
+
deliver: (fact: AppEventFact) => Promise<"accepted">;
|
|
12
|
+
arm(frame: Frame, raw: TriggerArmTarget): Promise<{
|
|
13
|
+
kind: "armed";
|
|
14
|
+
eventTriggerId: string;
|
|
15
|
+
} | {
|
|
16
|
+
error: string;
|
|
17
|
+
kind?: undefined;
|
|
18
|
+
reason?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "refused";
|
|
21
|
+
reason: string;
|
|
22
|
+
readonly error?: undefined;
|
|
23
|
+
}>;
|
|
24
|
+
disarm(frame: Frame, raw: EventDisarmInput): Promise<{
|
|
25
|
+
kind: "disarmed";
|
|
26
|
+
} | {
|
|
27
|
+
kind: "not_found";
|
|
28
|
+
} | {
|
|
29
|
+
error: string;
|
|
30
|
+
}>;
|
|
31
|
+
list(frame: Frame): Promise<{
|
|
32
|
+
error: string;
|
|
33
|
+
eventTriggers?: undefined;
|
|
34
|
+
} | {
|
|
35
|
+
eventTriggers: {
|
|
36
|
+
eventTriggerId: string;
|
|
37
|
+
workflowId: string;
|
|
38
|
+
triggerId: string;
|
|
39
|
+
emitterInstallationId: string;
|
|
40
|
+
event: string;
|
|
41
|
+
state: "active" | "paused";
|
|
42
|
+
lastError: string | null;
|
|
43
|
+
}[];
|
|
44
|
+
readonly error?: undefined;
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createWorkflowEventRouter } from "@fungi.computer/braid/runtime";
|
|
2
|
+
import { eventDisarmInputSchema } from "@fungi.computer/workflows-wire";
|
|
3
|
+
import { triggerArmTargetSchema, } from "./workflow-binding.js";
|
|
4
|
+
/** Host controls and App-event delivery compose the shared event router unchanged. */
|
|
5
|
+
export function createStandaloneWorkflowEvents(options) {
|
|
6
|
+
const router = createWorkflowEventRouter(options);
|
|
7
|
+
return {
|
|
8
|
+
/** A host-recorded App event; matching armed triggers start their runs. */
|
|
9
|
+
deliver: (fact) => router.deliver(fact),
|
|
10
|
+
async arm(frame, raw) {
|
|
11
|
+
const target = triggerArmTargetSchema.parse(raw);
|
|
12
|
+
const { libraryId, context, installationAuthority } = await options.admitInvocation(frame, "armEventTrigger");
|
|
13
|
+
if (context.actor.kind !== "member")
|
|
14
|
+
return { error: "not_granted" };
|
|
15
|
+
if (context.computerId === null)
|
|
16
|
+
return {
|
|
17
|
+
kind: "refused",
|
|
18
|
+
reason: "Select a Computer before arming an event trigger",
|
|
19
|
+
};
|
|
20
|
+
// Standalone has no Agent arming source: the owner arms as themself.
|
|
21
|
+
return router.arm({ armedBy: context.actor.memberId, armedVia: null }, { ...target, installationId: libraryId }, context.computerId, installationAuthority);
|
|
22
|
+
},
|
|
23
|
+
async disarm(frame, raw) {
|
|
24
|
+
const { eventTriggerId } = eventDisarmInputSchema.parse(raw);
|
|
25
|
+
const { libraryId, context } = await options.admitInvocation(frame, "disarmEventTrigger");
|
|
26
|
+
if (context.actor.kind !== "member")
|
|
27
|
+
return { error: "not_granted" };
|
|
28
|
+
return router.disarm({ kind: "member", memberId: context.actor.memberId }, libraryId, eventTriggerId);
|
|
29
|
+
},
|
|
30
|
+
async list(frame) {
|
|
31
|
+
const { libraryId, context } = await options.admitInvocation(frame, "eventTriggers");
|
|
32
|
+
if (context.actor.kind !== "member")
|
|
33
|
+
return { error: "not_granted" };
|
|
34
|
+
return {
|
|
35
|
+
eventTriggers: await router.list({ kind: "member", memberId: context.actor.memberId }, libraryId),
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorkflowCeilingCapturePorts } from "@fungi.computer/braid/runtime";
|
|
2
|
+
import type { StandaloneWorkflowNodeCaptureOptions } from "./workflow-node-capture.js";
|
|
3
|
+
/** Installation Git only; Team-source needs its separate authoritative namespace owner. */
|
|
4
|
+
export declare function createStandaloneWorkflowInstallationGitCapture(options: Readonly<{
|
|
5
|
+
host: Pick<StandaloneWorkflowNodeCaptureOptions["host"], "auth" | "artifacts" | "installations">;
|
|
6
|
+
readers: Pick<StandaloneWorkflowNodeCaptureOptions["readers"], "member">;
|
|
7
|
+
}>): WorkflowCeilingCapturePorts["git"];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { repositoryRefSchema } from "@fungi.computer/agar/git";
|
|
2
|
+
import { approvedBindingGrants } from "../apps/index.js";
|
|
3
|
+
import { createInstallationGitAdmission } from "../git.js";
|
|
4
|
+
import { oysterPrincipalIsOwner, resolveBackend } from "../host.js";
|
|
5
|
+
import { invocationCallerContext } from "../invocation.js";
|
|
6
|
+
/** Installation Git only; Team-source needs its separate authoritative namespace owner. */
|
|
7
|
+
export function createStandaloneWorkflowInstallationGitCapture(options) {
|
|
8
|
+
const { host, readers } = options;
|
|
9
|
+
return async (actor, member, raw, scope) => {
|
|
10
|
+
const repository = repositoryRefSchema.parse(raw);
|
|
11
|
+
if (repository.kind !== "installation")
|
|
12
|
+
return false;
|
|
13
|
+
const before = await readers.member(actor.memberId);
|
|
14
|
+
if (before?.membershipVersion !== member.membershipVersion)
|
|
15
|
+
return false;
|
|
16
|
+
const principal = await host.auth.resolve(actor);
|
|
17
|
+
if (principal === null || !oysterPrincipalIsOwner(principal))
|
|
18
|
+
return false;
|
|
19
|
+
const caller = invocationCallerContext(principal, null);
|
|
20
|
+
if (caller.actor.kind !== "member" ||
|
|
21
|
+
caller.actor.memberId !== actor.memberId)
|
|
22
|
+
return false;
|
|
23
|
+
const installation = await host.installations.get(repository.installationId);
|
|
24
|
+
if (installation?.status !== "enabled" ||
|
|
25
|
+
installation.instanceId === null ||
|
|
26
|
+
installation.installationId !== repository.installationId)
|
|
27
|
+
return false;
|
|
28
|
+
const currentInstallation = async () => {
|
|
29
|
+
const current = await host.installations.get(repository.installationId);
|
|
30
|
+
if (current?.status !== "enabled" ||
|
|
31
|
+
current.installationId !== installation.installationId ||
|
|
32
|
+
current.instanceId !== installation.instanceId ||
|
|
33
|
+
current.authorityVersion !== installation.authorityVersion ||
|
|
34
|
+
current.declaredOperationsDigest !==
|
|
35
|
+
installation.declaredOperationsDigest ||
|
|
36
|
+
current.release.appId !== installation.release.appId ||
|
|
37
|
+
current.release.releaseId !== installation.release.releaseId ||
|
|
38
|
+
current.release.artifact.artifactRef !==
|
|
39
|
+
installation.release.artifact.artifactRef ||
|
|
40
|
+
current.release.artifact.artifactDigest !==
|
|
41
|
+
installation.release.artifact.artifactDigest)
|
|
42
|
+
return null;
|
|
43
|
+
return current;
|
|
44
|
+
};
|
|
45
|
+
const admits = createInstallationGitAdmission({
|
|
46
|
+
currentInstallation,
|
|
47
|
+
declaration: async (current) => {
|
|
48
|
+
const backend = await resolveBackend(host.artifacts, current.release);
|
|
49
|
+
const declaration = backend?.declaration;
|
|
50
|
+
if (declaration == null ||
|
|
51
|
+
!approvedBindingGrants(current, declaration).some((binding) => binding.binding === "GIT" && binding.operations.includes(scope)))
|
|
52
|
+
return null;
|
|
53
|
+
return declaration;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
if (!(await admits({ kind: "actor", actor: caller.actor }, repository.repository, scope)))
|
|
57
|
+
return false;
|
|
58
|
+
const after = await readers.member(actor.memberId);
|
|
59
|
+
return (after?.membershipVersion === member.membershipVersion &&
|
|
60
|
+
(await currentInstallation()) !== null);
|
|
61
|
+
};
|
|
62
|
+
}
|