@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
|
@@ -2,8 +2,32 @@ import { z } from "zod";
|
|
|
2
2
|
import { workflowAttestationSchema } from "@fungi.computer/puffball/app-method-runtime";
|
|
3
3
|
import { gitAccessScope, resolvedGitAccessSchema } from "../git-access.js";
|
|
4
4
|
const id = z.string().min(1).max(256);
|
|
5
|
+
/** One credential grammar for both hosts; the owner is public, the token is not. */
|
|
6
|
+
export const workflowGitPassPrefix = "wgp.";
|
|
7
|
+
export const workflowGitPassTokenSchema = z
|
|
8
|
+
.string()
|
|
9
|
+
.regex(/^wgp\.([a-zA-Z0-9_-]{1,256})\.([0-9a-f]{64})$/u)
|
|
10
|
+
.transform((bearer) => {
|
|
11
|
+
const [, ownerId = "", token = ""] = bearer.split(".");
|
|
12
|
+
return { ownerId, token };
|
|
13
|
+
});
|
|
14
|
+
/** Format the existing standalone bearer shape, never a credential-bearing URL. */
|
|
15
|
+
export function formatWorkflowGitPassToken(ownerId, token) {
|
|
16
|
+
const bearer = `${workflowGitPassPrefix}${ownerId}.${token}`;
|
|
17
|
+
workflowGitPassTokenSchema.parse(bearer);
|
|
18
|
+
return bearer;
|
|
19
|
+
}
|
|
20
|
+
/** Git reads this command-local config without writing a credential or repository config. */
|
|
21
|
+
export function workflowGitExecutionEnvironment(credential) {
|
|
22
|
+
return {
|
|
23
|
+
FUNGI_GIT_REMOTE: credential.remote,
|
|
24
|
+
GIT_CONFIG_COUNT: "1",
|
|
25
|
+
GIT_CONFIG_KEY_0: `http.${credential.remote}.extraHeader`,
|
|
26
|
+
GIT_CONFIG_VALUE_0: `Authorization: Bearer ${credential.bearer}`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
5
29
|
/** Non-secret Team-issued authority for one stored Workflow step on its pinned Computer. */
|
|
6
|
-
export const workflowGitPassGrantSchema = z
|
|
30
|
+
export const workflowGitPassGrantSchema = z.lazy(() => z
|
|
7
31
|
.strictObject({
|
|
8
32
|
teamId: id,
|
|
9
33
|
computerId: id,
|
|
@@ -17,7 +41,7 @@ export const workflowGitPassGrantSchema = z
|
|
|
17
41
|
gitAccess: resolvedGitAccessSchema,
|
|
18
42
|
triggerId: id.optional(),
|
|
19
43
|
})
|
|
20
|
-
.refine((grant) => grant.scope === gitAccessScope(grant.gitAccess), "A pass's scope is its admitted Git access");
|
|
44
|
+
.refine((grant) => grant.scope === gitAccessScope(grant.gitAccess), "A pass's scope is its admitted Git access"));
|
|
21
45
|
/** Run cleanup remains valid after the Computer's product data is erased. */
|
|
22
46
|
export const workflowGitPassRevocationSchema = z.strictObject({
|
|
23
47
|
teamId: id,
|
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
import { type RepositoryRef } from "@fungi.computer/agar/git";
|
|
2
2
|
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
3
|
-
import { type WorkflowGitPassGrant } from "./git-pass-contract.js";
|
|
4
|
-
/**
|
|
3
|
+
import { type WorkflowGitCredential, type WorkflowGitPassGrant } from "./git-pass-contract.js";
|
|
4
|
+
/** One fresh credential per newly admitted native invocation; only its digest is durable. */
|
|
5
5
|
export declare function createWorkflowGitPassStore(options: Readonly<{
|
|
6
6
|
sql: WatchdogSqliteOwner["sql"];
|
|
7
7
|
transactionSync: <T>(body: () => T) => T;
|
|
8
8
|
ownerId(): string | null;
|
|
9
9
|
computerId(): string | undefined;
|
|
10
|
-
|
|
11
|
-
remote(
|
|
10
|
+
mintToken?: () => string;
|
|
11
|
+
remote(repository: RepositoryRef): string;
|
|
12
12
|
denied(): Error;
|
|
13
13
|
now?: () => number;
|
|
14
14
|
}>): Readonly<{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
/** Only the NEW native-admission path calls this; replay never obtains another secret. */
|
|
16
|
+
issue(rawInvocationId: string, rawGrant: WorkflowGitPassGrant): Promise<WorkflowGitCredential>;
|
|
17
|
+
/** A live native replay can extend its existing lease, never issue or resurrect it. */
|
|
18
|
+
renew(rawInvocationId: string, rawGrant: WorkflowGitPassGrant): void;
|
|
19
|
+
/** Keep the public receipt so a delayed issuer cannot reopen the same invocation. */
|
|
20
|
+
close(rawInvocationId: string, rawGrant: WorkflowGitPassGrant): void;
|
|
21
21
|
/** Retain a tombstone so a delayed mint cannot resurrect a terminal run. */
|
|
22
22
|
revokeWorkflowRunGitPasses(input: {
|
|
23
23
|
teamId: string;
|
|
24
24
|
runId: string;
|
|
25
25
|
}): void;
|
|
26
|
-
/**
|
|
26
|
+
/** Every request also needs the host's live native-invocation and domain admission. */
|
|
27
27
|
admit(input: {
|
|
28
|
-
|
|
28
|
+
bearer: string;
|
|
29
29
|
repository: RepositoryRef;
|
|
30
30
|
scope: "read" | "write";
|
|
31
|
-
}):
|
|
31
|
+
}): Promise<{
|
|
32
|
+
invocationId: string;
|
|
33
|
+
grant: WorkflowGitPassGrant;
|
|
34
|
+
} | null>;
|
|
32
35
|
}>;
|
|
@@ -1,55 +1,124 @@
|
|
|
1
1
|
import { repositoryRefsEqual, } from "@fungi.computer/agar/git";
|
|
2
|
-
import { workflowGitPassResultSchema } from "@fungi.computer/agar/computer-contracts";
|
|
3
2
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import { canonicalJson } from "../index.js";
|
|
4
|
+
import { formatWorkflowGitPassToken, workflowGitPassTokenSchema, workflowGitPassGrantSchema, workflowGitPassRevocationSchema, } from "./git-pass-contract.js";
|
|
5
|
+
const invocationIdSchema = z.string().min(1).max(256);
|
|
6
|
+
const tokenSchema = z.string().regex(/^[0-9a-f]{64}$/u);
|
|
7
|
+
const leaseSchema = z.strictObject({
|
|
8
|
+
invocation_id: invocationIdSchema,
|
|
9
|
+
run_id: z.string(),
|
|
10
|
+
grant_json: z.string(),
|
|
11
|
+
token_digest: z.string().nullable(),
|
|
12
|
+
expires_at: z.number().int(),
|
|
13
|
+
state: z.enum(["open", "closed"]),
|
|
14
|
+
});
|
|
15
|
+
const remoteSchema = z
|
|
16
|
+
.url({ protocol: /^https$/, abort: true })
|
|
17
|
+
.refine((value) => {
|
|
18
|
+
const url = new URL(value);
|
|
19
|
+
return url.username === "" && url.password === "";
|
|
20
|
+
}, "Workflow Git remotes must not contain credentials");
|
|
21
|
+
function mintToken() {
|
|
22
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(32)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
23
|
+
}
|
|
24
|
+
/** A verifier digest, never a native invocation identity or replay hash. */
|
|
25
|
+
async function tokenDigest(token) {
|
|
26
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
|
|
27
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
28
|
+
}
|
|
29
|
+
/** Optional internal fields use JSON's omission semantics at the storage boundary. */
|
|
30
|
+
function serializeGrant(grant) {
|
|
31
|
+
return canonicalJson(z.json().parse(JSON.parse(JSON.stringify(grant))));
|
|
32
|
+
}
|
|
33
|
+
/** One fresh credential per newly admitted native invocation; only its digest is durable. */
|
|
6
34
|
export function createWorkflowGitPassStore(options) {
|
|
7
35
|
const { sql } = options;
|
|
8
36
|
const now = options.now ?? Date.now;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
37
|
+
// Pre-release reusable, plaintext pass state is disposable, not another reader.
|
|
38
|
+
sql.exec("DROP TABLE IF EXISTS computer_workflow_git_passes");
|
|
39
|
+
sql.exec(`CREATE TABLE IF NOT EXISTS computer_workflow_git_invocations (
|
|
40
|
+
invocation_id TEXT PRIMARY KEY, run_id TEXT NOT NULL,
|
|
41
|
+
grant_json TEXT NOT NULL, token_digest TEXT UNIQUE,
|
|
42
|
+
expires_at INTEGER NOT NULL,
|
|
43
|
+
state TEXT NOT NULL CHECK (state IN ('open', 'closed'))
|
|
12
44
|
) STRICT`);
|
|
13
45
|
sql.exec(`CREATE TABLE IF NOT EXISTS computer_workflow_git_ended_runs (run_id TEXT PRIMARY KEY) STRICT`);
|
|
46
|
+
function admittedGrant(raw) {
|
|
47
|
+
const grant = workflowGitPassGrantSchema.parse(raw);
|
|
48
|
+
if (grant.teamId !== options.ownerId() ||
|
|
49
|
+
grant.computerId !== options.computerId() ||
|
|
50
|
+
grant.workflow.runId !== grant.runId ||
|
|
51
|
+
grant.workflow.nodeId !== grant.nodeId ||
|
|
52
|
+
grant.workflow.armedBy.kind !== "member" ||
|
|
53
|
+
grant.workflow.armedBy.memberId !== grant.memberId)
|
|
54
|
+
throw options.denied();
|
|
55
|
+
return grant;
|
|
56
|
+
}
|
|
57
|
+
function ended(runId) {
|
|
58
|
+
return (sql
|
|
59
|
+
.exec("SELECT 1 FROM computer_workflow_git_ended_runs WHERE run_id=?", runId)
|
|
60
|
+
.toArray().length !== 0);
|
|
61
|
+
}
|
|
62
|
+
function read(invocationId) {
|
|
63
|
+
const row = sql
|
|
64
|
+
.exec("SELECT * FROM computer_workflow_git_invocations WHERE invocation_id=?", invocationId)
|
|
65
|
+
.toArray()[0];
|
|
66
|
+
return row === undefined ? undefined : leaseSchema.parse(row);
|
|
67
|
+
}
|
|
14
68
|
return Object.freeze({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
grant.workflow.nodeId !== grant.nodeId ||
|
|
21
|
-
grant.workflow.armedBy.kind !== "member" ||
|
|
22
|
-
grant.workflow.armedBy.memberId !== grant.memberId)
|
|
69
|
+
/** Only the NEW native-admission path calls this; replay never obtains another secret. */
|
|
70
|
+
async issue(rawInvocationId, rawGrant) {
|
|
71
|
+
const invocationId = invocationIdSchema.parse(rawInvocationId);
|
|
72
|
+
const grant = admittedGrant(rawGrant);
|
|
73
|
+
if (ended(grant.runId) || read(invocationId) !== undefined)
|
|
23
74
|
throw options.denied();
|
|
75
|
+
const remote = remoteSchema.parse(options.remote(grant.gitAccess.repository));
|
|
76
|
+
const token = tokenSchema.parse((options.mintToken ?? mintToken)());
|
|
77
|
+
const bearer = formatWorkflowGitPassToken(grant.teamId, token);
|
|
78
|
+
const digest = await tokenDigest(token);
|
|
24
79
|
return options.transactionSync(() => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
throw options.denied();
|
|
29
|
-
const key = JSON.stringify([
|
|
30
|
-
grant.runId,
|
|
31
|
-
grant.graphId ?? null,
|
|
32
|
-
grant.nodeId,
|
|
33
|
-
grant.iteration,
|
|
34
|
-
grant.scope,
|
|
35
|
-
]);
|
|
36
|
-
const row = sql
|
|
37
|
-
.exec("SELECT pass_id,grant_json,expires_at FROM computer_workflow_git_passes WHERE node_key=?", key)
|
|
38
|
-
.toArray()[0];
|
|
39
|
-
if (row !== undefined &&
|
|
40
|
-
z.string().parse(row.grant_json) !== JSON.stringify(grant))
|
|
80
|
+
// The digest await can race a terminal revocation or another issuer.
|
|
81
|
+
admittedGrant(grant);
|
|
82
|
+
if (ended(grant.runId) || read(invocationId) !== undefined)
|
|
41
83
|
throw options.denied();
|
|
42
|
-
const passId = row === undefined
|
|
43
|
-
? options.mintPassId()
|
|
44
|
-
: z.string().parse(row.pass_id);
|
|
45
84
|
const expiresAt = now() + 60_000;
|
|
46
|
-
sql.exec(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
85
|
+
sql.exec("INSERT INTO computer_workflow_git_invocations VALUES(?,?,?,?,?,'open')", invocationId, grant.runId, serializeGrant(grant), digest, expiresAt);
|
|
86
|
+
return { bearer, remote, expiresAt };
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
/** A live native replay can extend its existing lease, never issue or resurrect it. */
|
|
90
|
+
renew(rawInvocationId, rawGrant) {
|
|
91
|
+
const invocationId = invocationIdSchema.parse(rawInvocationId);
|
|
92
|
+
const grant = admittedGrant(rawGrant);
|
|
93
|
+
options.transactionSync(() => {
|
|
94
|
+
const row = read(invocationId);
|
|
95
|
+
if (row?.state !== "open" ||
|
|
96
|
+
row.expires_at <= now() ||
|
|
97
|
+
row.grant_json !== serializeGrant(grant) ||
|
|
98
|
+
ended(grant.runId))
|
|
99
|
+
throw options.denied();
|
|
100
|
+
sql.exec("UPDATE computer_workflow_git_invocations SET expires_at=? WHERE invocation_id=?", now() + 60_000, invocationId);
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
/** Keep the public receipt so a delayed issuer cannot reopen the same invocation. */
|
|
104
|
+
close(rawInvocationId, rawGrant) {
|
|
105
|
+
const invocationId = invocationIdSchema.parse(rawInvocationId);
|
|
106
|
+
options.transactionSync(() => {
|
|
107
|
+
const row = read(invocationId);
|
|
108
|
+
if (row !== undefined) {
|
|
109
|
+
// Revocation never depends on current authority or reconstructed
|
|
110
|
+
// metadata still being sufficient to admit a new command.
|
|
111
|
+
sql.exec("UPDATE computer_workflow_git_invocations SET state='closed',token_digest=NULL WHERE invocation_id=?", invocationId);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const grant = workflowGitPassGrantSchema.parse(rawGrant);
|
|
115
|
+
// The run tombstone already prevents delayed issuance. Do not recreate
|
|
116
|
+
// receipts after terminal cleanup has deleted the run's invocation rows.
|
|
117
|
+
if (ended(grant.runId))
|
|
118
|
+
return;
|
|
119
|
+
// Close can beat the issue RPC or its digest await. Retain the same
|
|
120
|
+
// public identity either way, so neither can publish a later credential.
|
|
121
|
+
sql.exec("INSERT INTO computer_workflow_git_invocations VALUES(?,?,?,NULL,0,'closed')", invocationId, grant.runId, serializeGrant(grant));
|
|
53
122
|
});
|
|
54
123
|
},
|
|
55
124
|
/** Retain a tombstone so a delayed mint cannot resurrect a terminal run. */
|
|
@@ -59,24 +128,31 @@ export function createWorkflowGitPassStore(options) {
|
|
|
59
128
|
throw options.denied();
|
|
60
129
|
options.transactionSync(() => {
|
|
61
130
|
sql.exec("INSERT OR IGNORE INTO computer_workflow_git_ended_runs VALUES(?)", runId);
|
|
62
|
-
sql.exec("DELETE FROM
|
|
131
|
+
sql.exec("DELETE FROM computer_workflow_git_invocations WHERE run_id=?", runId);
|
|
63
132
|
});
|
|
64
133
|
},
|
|
65
|
-
/**
|
|
66
|
-
admit(input) {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
134
|
+
/** Every request also needs the host's live native-invocation and domain admission. */
|
|
135
|
+
async admit(input) {
|
|
136
|
+
const parsed = workflowGitPassTokenSchema.safeParse(input.bearer);
|
|
137
|
+
if (!parsed.success || parsed.data.ownerId !== options.ownerId())
|
|
138
|
+
return null;
|
|
139
|
+
const digest = await tokenDigest(parsed.data.token);
|
|
140
|
+
const raw = sql
|
|
141
|
+
.exec("SELECT * FROM computer_workflow_git_invocations WHERE token_digest=?", digest)
|
|
70
142
|
.toArray()[0];
|
|
71
|
-
if (
|
|
143
|
+
if (raw === undefined)
|
|
144
|
+
return null;
|
|
145
|
+
const row = leaseSchema.parse(raw);
|
|
146
|
+
if (row.state !== "open" || row.expires_at <= now())
|
|
72
147
|
return null;
|
|
73
|
-
const grant = workflowGitPassGrantSchema.parse(JSON.parse(
|
|
148
|
+
const grant = workflowGitPassGrantSchema.parse(JSON.parse(row.grant_json));
|
|
74
149
|
if (grant.teamId !== options.ownerId() ||
|
|
75
150
|
grant.computerId !== options.computerId() ||
|
|
76
151
|
!repositoryRefsEqual(grant.gitAccess.repository, input.repository) ||
|
|
77
|
-
(input.scope === "write" && grant.scope !== "write")
|
|
152
|
+
(input.scope === "write" && grant.scope !== "write") ||
|
|
153
|
+
ended(grant.runId))
|
|
78
154
|
return null;
|
|
79
|
-
return grant;
|
|
155
|
+
return { invocationId: row.invocation_id, grant };
|
|
80
156
|
},
|
|
81
157
|
});
|
|
82
158
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { RepositoryRef } from "@fungi.computer/agar/git";
|
|
2
|
-
import { type WorkflowGitPassResult } from "@fungi.computer/agar/computer-contracts";
|
|
3
2
|
import type { WorkflowRunContext } from "./graph-contract.js";
|
|
4
3
|
import type { StoredWorkflowNode } from "./graph-dispatch.js";
|
|
5
4
|
import { type WorkflowGitPassGrant } from "./git-pass-contract.js";
|
|
@@ -19,7 +18,6 @@ export declare function createWorkflowGitPasses(ports: Readonly<{
|
|
|
19
18
|
method: string;
|
|
20
19
|
}): WorkflowGitPassNode | null;
|
|
21
20
|
admitMemberGitAccess(memberId: string, repository: RepositoryRef, scope: WorkflowGitScope): Promise<void>;
|
|
22
|
-
open(computerId: string, grant: WorkflowGitPassGrant): Promise<WorkflowGitPassResult>;
|
|
23
21
|
revoke(computerId: string, input: {
|
|
24
22
|
teamId: string;
|
|
25
23
|
runId: string;
|
|
@@ -33,9 +31,10 @@ export declare function createWorkflowGitPasses(ports: Readonly<{
|
|
|
33
31
|
wake?(due: number | null): Promise<void>;
|
|
34
32
|
}>): {
|
|
35
33
|
cleanupWake: () => number | null;
|
|
36
|
-
|
|
34
|
+
/** Returns only public authority; native NEW admission owns credential issuance. */
|
|
35
|
+
admitWorkflowGit(context: WorkflowRunContext, target: {
|
|
37
36
|
installationId: string;
|
|
38
37
|
method: string;
|
|
39
|
-
}): Promise<
|
|
38
|
+
}): Promise<WorkflowGitPassGrant>;
|
|
40
39
|
deliverCleanups(): Promise<void>;
|
|
41
40
|
};
|
package/dist/runtime/git-pass.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { workflowGitPassResultSchema, } from "@fungi.computer/agar/computer-contracts";
|
|
2
1
|
import { canonicalJson } from "../index.js";
|
|
3
2
|
import { gitAccessScope } from "../git-access.js";
|
|
4
3
|
import { workflowGitPassGrantSchema, } from "./git-pass-contract.js";
|
|
@@ -7,7 +6,8 @@ export function createWorkflowGitPasses(ports) {
|
|
|
7
6
|
const cleanupWake = () => ports.pending().length === 0 ? null : Date.now() + 5000;
|
|
8
7
|
return {
|
|
9
8
|
cleanupWake,
|
|
10
|
-
|
|
9
|
+
/** Returns only public authority; native NEW admission owns credential issuance. */
|
|
10
|
+
async admitWorkflowGit(context, target) {
|
|
11
11
|
const admitted = ports.admitNode(context, target);
|
|
12
12
|
if (admitted?.computerId == null ||
|
|
13
13
|
admitted.context.gitAccess === undefined ||
|
|
@@ -38,21 +38,7 @@ export function createWorkflowGitPasses(ports) {
|
|
|
38
38
|
if (canonicalJson(ports.admitNode(context, target)) !==
|
|
39
39
|
canonicalJson(admitted))
|
|
40
40
|
throw ports.denied();
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
await ports.admitMemberGitAccess(grant.memberId, grant.gitAccess.repository, scope);
|
|
44
|
-
if (canonicalJson(ports.admitNode(context, target)) !==
|
|
45
|
-
canonicalJson(admitted))
|
|
46
|
-
throw ports.denied();
|
|
47
|
-
}
|
|
48
|
-
catch (cause) {
|
|
49
|
-
await ports.revoke(grant.computerId, {
|
|
50
|
-
teamId: grant.teamId,
|
|
51
|
-
runId: grant.runId,
|
|
52
|
-
});
|
|
53
|
-
throw cause;
|
|
54
|
-
}
|
|
55
|
-
return pass;
|
|
41
|
+
return grant;
|
|
56
42
|
},
|
|
57
43
|
async deliverCleanups() {
|
|
58
44
|
try {
|
|
@@ -191,6 +191,7 @@ export declare const graphRunSchema: S.Struct<{
|
|
|
191
191
|
readonly id: S.String;
|
|
192
192
|
readonly kind: S.Literal<"review">;
|
|
193
193
|
readonly title: S.String;
|
|
194
|
+
readonly detail: S.optionalKey<S.String>;
|
|
194
195
|
readonly show: S.$Array<S.Struct<{
|
|
195
196
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
196
197
|
readonly port: S.String;
|
|
@@ -200,6 +201,7 @@ export declare const graphRunSchema: S.Struct<{
|
|
|
200
201
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
201
202
|
readonly id: S.String;
|
|
202
203
|
readonly label: S.String;
|
|
204
|
+
readonly description: S.optionalKey<S.String>;
|
|
203
205
|
}>>>;
|
|
204
206
|
}>, S.Struct<{
|
|
205
207
|
readonly id: S.String;
|
|
@@ -310,6 +312,7 @@ export declare const graphRunSchema: S.Struct<{
|
|
|
310
312
|
readonly id: S.String;
|
|
311
313
|
readonly kind: S.Literal<"review">;
|
|
312
314
|
readonly title: S.String;
|
|
315
|
+
readonly detail: S.optionalKey<S.String>;
|
|
313
316
|
readonly show: S.$Array<S.Struct<{
|
|
314
317
|
readonly node: S.Union<readonly [S.String, S.Literals<readonly ["$input", "$item"]>]>;
|
|
315
318
|
readonly port: S.String;
|
|
@@ -319,6 +322,7 @@ export declare const graphRunSchema: S.Struct<{
|
|
|
319
322
|
readonly choices: S.optionalKey<S.$Array<S.Struct<{
|
|
320
323
|
readonly id: S.String;
|
|
321
324
|
readonly label: S.String;
|
|
325
|
+
readonly description: S.optionalKey<S.String>;
|
|
322
326
|
}>>>;
|
|
323
327
|
}>, S.Struct<{
|
|
324
328
|
readonly id: S.String;
|
|
@@ -412,7 +416,7 @@ export declare const graphRunSchema: S.Struct<{
|
|
|
412
416
|
readonly graphId: S.optionalKey<S.String>;
|
|
413
417
|
readonly nodeId: S.String;
|
|
414
418
|
readonly level: S.Literals<readonly ["read", "write"]>;
|
|
415
|
-
readonly capabilities: S.$Array<S.Literals<readonly ["agent.list", "agent.manage", "agent.rename", "agent.toolPolicy", "agent.read", "agent.send", "computer.list", "computer.readFile", "computer.writeFile", "computer.execute", "llm.generation", "media.generation", "games:servers", "git.repository", "apps.open", "apps.call", "app.storage", "app.events.emit", "team.workbenchHost", "marketplace.browse", "marketplace.purchase", "workflow.run", "workflow.publish", "workflow.arm", "world", "channels.read", "channels.personal.manage"]>>;
|
|
419
|
+
readonly capabilities: S.$Array<S.Literals<readonly ["approvals", "agent.list", "agent.manage", "agent.rename", "agent.avatar", "agent.toolPolicy", "agent.read", "agent.send", "computer.list", "computer.readFile", "computer.writeFile", "computer.execute", "llm.generation", "media.generation", "games:servers", "git.repository", "apps.open", "apps.call", "app.storage", "app.events.emit", "team.workbenchHost", "marketplace.browse", "marketplace.purchase", "workflow.run", "workflow.publish", "workflow.arm", "world", "channels.read", "channels.personal.manage"]>>;
|
|
416
420
|
readonly bindings: S.$Array<S.Union<S.Struct<{
|
|
417
421
|
readonly name: S.Literal<"BACKGROUND" | "GIT" | "EVENTS" | "APPS" | "WORKFLOWS" | "APP_STORAGE" | "MEDIA" | "COMPUTER" | "INBOX" | "AGENTS">;
|
|
418
422
|
readonly operations: S.$Array<S.Literals<string[]>>;
|
|
@@ -59,6 +59,12 @@ export declare function createGraphWorkflowRuns(options: {
|
|
|
59
59
|
readonly iteration: string;
|
|
60
60
|
readonly instanceId: string;
|
|
61
61
|
readonly eventType: string;
|
|
62
|
+
readonly position?: readonly {
|
|
63
|
+
readonly node: string;
|
|
64
|
+
readonly loop: "repeat" | "map";
|
|
65
|
+
readonly index: number;
|
|
66
|
+
readonly count: number;
|
|
67
|
+
}[];
|
|
62
68
|
readonly choices?: readonly string[];
|
|
63
69
|
};
|
|
64
70
|
status: "closed" | "pending" | "answered" | "delivered" | "timed_out";
|
package/dist/runtime/ports.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Pending } from "@fungi.computer/agar/backend-definition";
|
|
2
|
+
import type { ResultRow } from "@fungi.computer/gills";
|
|
2
3
|
import type { RunCeiling } from "./run-ceiling.js";
|
|
3
4
|
import type { Catalog } from "../index.js";
|
|
4
5
|
import type * as Schema from "effect/Schema";
|
|
@@ -35,6 +36,8 @@ export type WorkflowAppCallResult = Pending | Readonly<{
|
|
|
35
36
|
kind: "complete";
|
|
36
37
|
output: Schema.Json;
|
|
37
38
|
costMilliunits: number;
|
|
39
|
+
/** How the finished call reads to a person, when the App declares a presentation. */
|
|
40
|
+
row?: ResultRow;
|
|
38
41
|
}> | Readonly<{
|
|
39
42
|
kind: "refused";
|
|
40
43
|
code: "not_granted" | "invalid_input" | "invalid_output" | "revoked" | "unknown_method";
|
|
@@ -142,10 +145,17 @@ export type WorkflowTriggerRunPorts = Readonly<{
|
|
|
142
145
|
startCachedGraph(binding: import("./trigger-owner.js").ArmedWorkflowTrigger, request: Pick<import("./graph-start.js").GraphStartRequest, "requestId" | "input"> & {
|
|
143
146
|
/** The firing event's repository, evaluated from the armed trigger. */
|
|
144
147
|
triggerRepository?: string;
|
|
145
|
-
}
|
|
148
|
+
},
|
|
149
|
+
/** Host-private binding check, evaluated with the durable run write. */
|
|
150
|
+
admission?: WorkflowTriggerAdmission): Promise<Exclude<GraphStartReply, {
|
|
146
151
|
kind: "invalid";
|
|
147
152
|
}>>;
|
|
148
153
|
}>;
|
|
154
|
+
/** Host-private checks and reservation identity, never supplied by a sender. */
|
|
155
|
+
export type WorkflowTriggerAdmission = Readonly<{
|
|
156
|
+
admitBinding(): boolean;
|
|
157
|
+
reservationId?: string;
|
|
158
|
+
}>;
|
|
149
159
|
/** Hosts retain their existing error vocabulary and denial classification. */
|
|
150
160
|
export type WorkflowErrorPort = Readonly<{
|
|
151
161
|
denied(): Error;
|
|
@@ -51,7 +51,7 @@ export declare const runCeilingSchema: S.Struct<{
|
|
|
51
51
|
readonly graphId: S.optionalKey<S.String>;
|
|
52
52
|
readonly nodeId: S.String;
|
|
53
53
|
readonly level: S.Literals<readonly ["read", "write"]>;
|
|
54
|
-
readonly capabilities: S.$Array<S.Literals<readonly ["agent.list", "agent.manage", "agent.rename", "agent.toolPolicy", "agent.read", "agent.send", "computer.list", "computer.readFile", "computer.writeFile", "computer.execute", "llm.generation", "media.generation", "games:servers", "git.repository", "apps.open", "apps.call", "app.storage", "app.events.emit", "team.workbenchHost", "marketplace.browse", "marketplace.purchase", "workflow.run", "workflow.publish", "workflow.arm", "world", "channels.read", "channels.personal.manage"]>>;
|
|
54
|
+
readonly capabilities: S.$Array<S.Literals<readonly ["approvals", "agent.list", "agent.manage", "agent.rename", "agent.avatar", "agent.toolPolicy", "agent.read", "agent.send", "computer.list", "computer.readFile", "computer.writeFile", "computer.execute", "llm.generation", "media.generation", "games:servers", "git.repository", "apps.open", "apps.call", "app.storage", "app.events.emit", "team.workbenchHost", "marketplace.browse", "marketplace.purchase", "workflow.run", "workflow.publish", "workflow.arm", "world", "channels.read", "channels.personal.manage"]>>;
|
|
55
55
|
readonly bindings: S.$Array<S.Union<S.Struct<{
|
|
56
56
|
readonly name: S.Literal<"BACKGROUND" | "GIT" | "EVENTS" | "APPS" | "WORKFLOWS" | "APP_STORAGE" | "MEDIA" | "COMPUTER" | "INBOX" | "AGENTS">;
|
|
57
57
|
readonly operations: S.$Array<S.Literals<string[]>>;
|