@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.
Files changed (39) hide show
  1. package/dist/cloudflare/graph-program.js +29 -7
  2. package/dist/code-execution.js +2 -0
  3. package/dist/compile.d.ts +2 -0
  4. package/dist/compile.js +9 -1
  5. package/dist/execution.d.ts +81 -0
  6. package/dist/execution.js +22 -0
  7. package/dist/git-access.d.ts +15 -15
  8. package/dist/git-access.js +8 -8
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.js +1 -0
  11. package/dist/review-text.d.ts +15 -0
  12. package/dist/review-text.js +40 -0
  13. package/dist/runtime/arming-authority.d.ts +5 -5
  14. package/dist/runtime/arming-authority.js +5 -5
  15. package/dist/runtime/git-pass-contract.d.ts +30 -9
  16. package/dist/runtime/git-pass-contract.js +26 -2
  17. package/dist/runtime/git-pass-store.d.ts +16 -13
  18. package/dist/runtime/git-pass-store.js +126 -50
  19. package/dist/runtime/git-pass.d.ts +3 -4
  20. package/dist/runtime/git-pass.js +3 -17
  21. package/dist/runtime/graph-contract.d.ts +5 -1
  22. package/dist/runtime/graph-run-store.d.ts +6 -0
  23. package/dist/runtime/ports.d.ts +11 -1
  24. package/dist/runtime/run-ceiling.d.ts +1 -1
  25. package/dist/runtime/run-owner.d.ts +89 -4
  26. package/dist/runtime/run-owner.js +27 -12
  27. package/dist/runtime/schedules.d.ts +1 -0
  28. package/dist/runtime/schedules.js +1 -0
  29. package/dist/runtime/trigger-owner.d.ts +10 -6
  30. package/dist/runtime/trigger-owner.js +2 -2
  31. package/dist/runtime/trigger-plans.d.ts +8 -0
  32. package/dist/runtime/trigger-run-reservations.d.ts +67 -3
  33. package/dist/runtime/trigger-run-reservations.js +126 -7
  34. package/dist/schema.d.ts +80 -8
  35. package/dist/schema.js +14 -2
  36. package/dist/validate.js +7 -0
  37. package/dist/workflow-id.d.ts +1 -1
  38. package/dist/workflow-id.js +1 -1
  39. 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
- /** Scoped pass state shared by both Computer hosts; credentials never enter the grant. */
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
- mintPassId(): string;
11
- remote(passId: string, repository: RepositoryRef): string;
10
+ mintToken?: () => string;
11
+ remote(repository: RepositoryRef): string;
12
12
  denied(): Error;
13
13
  now?: () => number;
14
14
  }>): Readonly<{
15
- openWorkflowPass(raw: WorkflowGitPassGrant): {
16
- passId: string;
17
- remote: string;
18
- expiresAt: number;
19
- bearer?: string;
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
- /** Re-admit the stored scope for every Git request; an expired pass grants nothing. */
26
+ /** Every request also needs the host's live native-invocation and domain admission. */
27
27
  admit(input: {
28
- passId: string;
28
+ bearer: string;
29
29
  repository: RepositoryRef;
30
30
  scope: "read" | "write";
31
- }): WorkflowGitPassGrant | null;
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 { workflowGitPassGrantSchema, workflowGitPassRevocationSchema, } from "./git-pass-contract.js";
5
- /** Scoped pass state shared by both Computer hosts; credentials never enter the grant. */
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
- sql.exec(`CREATE TABLE IF NOT EXISTS computer_workflow_git_passes (
10
- pass_id TEXT PRIMARY KEY, node_key TEXT NOT NULL UNIQUE, run_id TEXT NOT NULL,
11
- grant_json TEXT NOT NULL, expires_at INTEGER NOT NULL
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
- openWorkflowPass(raw) {
16
- const grant = workflowGitPassGrantSchema.parse(raw);
17
- if (grant.teamId !== options.ownerId() ||
18
- grant.computerId !== options.computerId() ||
19
- grant.workflow.runId !== grant.runId ||
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
- if (sql
26
- .exec("SELECT 1 FROM computer_workflow_git_ended_runs WHERE run_id=?", grant.runId)
27
- .toArray().length > 0)
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(`INSERT INTO computer_workflow_git_passes VALUES(?,?,?,?,?)
47
- ON CONFLICT(node_key) DO UPDATE SET expires_at=excluded.expires_at`, passId, key, grant.runId, JSON.stringify(grant), expiresAt);
48
- return workflowGitPassResultSchema.parse({
49
- passId,
50
- expiresAt,
51
- remote: options.remote(passId, grant.gitAccess.repository),
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 computer_workflow_git_passes WHERE run_id=?", runId);
131
+ sql.exec("DELETE FROM computer_workflow_git_invocations WHERE run_id=?", runId);
63
132
  });
64
133
  },
65
- /** Re-admit the stored scope for every Git request; an expired pass grants nothing. */
66
- admit(input) {
67
- const at = now();
68
- const row = sql
69
- .exec("SELECT grant_json,expires_at FROM computer_workflow_git_passes WHERE pass_id=?", input.passId)
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 (row === undefined || z.number().parse(row.expires_at) <= at)
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(z.string().parse(row.grant_json)));
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
- openWorkflowPass(context: WorkflowRunContext, target: {
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<WorkflowGitPassResult>;
38
+ }): Promise<WorkflowGitPassGrant>;
40
39
  deliverCleanups(): Promise<void>;
41
40
  };
@@ -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
- async openWorkflowPass(context, target) {
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
- const pass = workflowGitPassResultSchema.parse(await ports.open(grant.computerId, grant));
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";
@@ -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
- }): Promise<Exclude<GraphStartReply, {
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[]>>;