@fungi.computer/oyster 0.6.0 → 0.7.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 +132 -78
- package/dist/agent-archive-projection.d.ts +8 -0
- package/dist/agent-archive-projection.js +49 -0
- package/dist/agent-schedules.d.ts +50 -0
- package/dist/agent-schedules.js +17 -0
- package/dist/agents.d.ts +35 -14
- package/dist/agents.js +93 -144
- package/dist/app-builds.d.ts +7 -2
- package/dist/app-builds.js +19 -2
- package/dist/approval-capability.d.ts +1 -1
- package/dist/approval-capability.js +11 -16
- package/dist/approval-home.d.ts +2 -1
- package/dist/approval-home.js +5 -3
- package/dist/apps/catalog.js +1 -1
- package/dist/apps/gate.d.ts +4 -7
- package/dist/apps/gate.js +14 -29
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/installation.d.ts +4 -42
- package/dist/apps/installation.js +7 -75
- package/dist/apps/operations.js +0 -10
- package/dist/apps/permissions.d.ts +18 -8
- package/dist/apps/permissions.js +7 -1
- package/dist/auth.d.ts +1 -24
- package/dist/auth.js +9 -39
- package/dist/authorization.d.ts +307 -0
- package/dist/authorization.js +542 -0
- package/dist/boot-host-script.js +1 -1
- package/dist/build/build-jobs.d.ts +2 -2
- package/dist/build/candidate-builder.d.ts +2 -0
- package/dist/build/candidate-builder.js +5 -1
- package/dist/build/candidate-cli.js +3 -0
- package/dist/build/candidate-cli.mjs +59 -21
- package/dist/build/job-log.d.ts +23 -0
- package/dist/build/job-log.js +88 -0
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +11 -9
- package/dist/cloudflare.d.ts +4 -1
- package/dist/cloudflare.js +1 -1
- package/dist/files.js +9 -8
- package/dist/git-admission.d.ts +3 -3
- package/dist/git-admission.js +4 -6
- package/dist/git-door.d.ts +8 -7
- package/dist/git-door.js +18 -17
- package/dist/git.d.ts +1 -1
- package/dist/host.d.ts +33 -40
- package/dist/host.js +153 -272
- package/dist/installation-execution.js +10 -5
- package/dist/installation-operations.d.ts +4 -180
- package/dist/installation-operations.js +1 -5
- package/dist/installation-store-generation.d.ts +4 -1
- package/dist/installation-store-generation.js +59 -6
- package/dist/internal/delivery.js +0 -1
- package/dist/internal/host-gate.d.ts +3 -2
- package/dist/internal/host-gate.js +5 -18
- package/dist/internal/invocation-timing.d.ts +16 -0
- package/dist/internal/invocation-timing.js +23 -0
- package/dist/internal/registry-installations.js +1 -0
- package/dist/invocation.d.ts +15 -14
- package/dist/invocation.js +58 -26
- package/dist/keys.d.ts +2 -19
- package/dist/keys.js +1 -37
- package/dist/mcp.d.ts +15 -27
- package/dist/mcp.js +105 -155
- package/dist/page/agent-browser.d.ts +15 -0
- package/dist/page/agent-browser.js +162 -0
- package/dist/page/app-permissions-port.d.ts +3 -0
- package/dist/page/app-permissions-port.js +43 -0
- package/dist/page/app-permissions.d.ts +11 -0
- package/dist/page/app-permissions.js +296 -0
- package/dist/page/isolated-page-theme.d.ts +2 -0
- package/dist/page/isolated-page-theme.js +11 -0
- package/dist/page/mount.d.ts +2 -13
- package/dist/page/mount.js +7 -93
- package/dist/page-script.d.ts +0 -3
- package/dist/page-script.js +3 -4
- package/dist/standalone/agent-archive-owner.d.ts +42 -0
- package/dist/standalone/agent-archive-owner.js +116 -0
- package/dist/standalone/agent-archive-schedules.d.ts +6 -0
- package/dist/standalone/agent-archive-schedules.js +76 -0
- package/dist/standalone/agent-archive.d.ts +31 -0
- package/dist/standalone/agent-archive.js +67 -0
- package/dist/standalone/agent-computers.d.ts +7 -6
- package/dist/standalone/agent-computers.js +4 -7
- package/dist/standalone/agent-mail.d.ts +2 -2
- package/dist/standalone/agents.d.ts +157 -20
- package/dist/standalone/agents.js +234 -30
- package/dist/standalone/app-builds-binding.js +1 -1
- package/dist/standalone/app-set.js +2 -54
- package/dist/standalone/app-source.d.ts +3 -4
- package/dist/standalone/app-source.js +12 -9
- package/dist/standalone/builds.d.ts +2 -14
- package/dist/standalone/builds.js +15 -13
- package/dist/standalone/computer-fleet.d.ts +4 -4
- package/dist/standalone/computer-fleet.js +13 -42
- package/dist/standalone/computer.d.ts +4 -3
- package/dist/standalone/computer.js +52 -53
- package/dist/standalone/dashboard-approval.d.ts +10 -0
- package/dist/standalone/dashboard-approval.js +66 -0
- package/dist/standalone/machine-link.d.ts +4 -221
- package/dist/standalone/machine-link.js +21 -15
- package/dist/standalone/media.d.ts +1 -0
- package/dist/standalone/media.js +20 -14
- package/dist/standalone/owner-routes.d.ts +16 -0
- package/dist/standalone/owner-routes.js +21 -0
- package/dist/standalone/permission-resources.d.ts +5 -0
- package/dist/standalone/permission-resources.js +31 -0
- package/dist/standalone/registry.d.ts +7 -0
- package/dist/standalone/registry.js +3 -0
- package/dist/standalone/tenancy.d.ts +0 -3
- package/dist/standalone/tenancy.js +2 -5
- package/dist/standalone/worker-routes.d.ts +19 -0
- package/dist/standalone/worker-routes.js +9 -0
- package/dist/standalone/worker.d.ts +1 -1
- package/dist/standalone/worker.js +27 -35
- package/dist/standalone/workflow-binding.d.ts +8 -3
- package/dist/standalone/workflow-binding.js +14 -5
- package/dist/standalone/workflow-git-capture.js +2 -1
- package/dist/standalone/workflow-host.d.ts +8 -103
- package/dist/standalone/workflow-host.js +1 -1
- package/dist/standalone/workflow-node-capture.js +2 -2
- package/dist/standalone/workflow-root-authority.d.ts +1 -1
- package/dist/standalone/workflow-root-authority.js +1 -1
- package/dist/standalone/workflow-schedules.d.ts +14 -5
- package/dist/standalone/workflow-schedules.js +11 -5
- package/dist/standalone/workflow-webhooks.d.ts +4 -18
- package/dist/standalone/workflow-webhooks.js +5 -5
- package/dist/standalone/workflows.d.ts +36 -28
- package/dist/standalone/workflows.js +6 -3
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.d.ts +7 -6
- package/dist/testing/capabilities.js +3 -6
- package/dist/testing/installation.d.ts +0 -1
- package/dist/testing/installation.js +2 -5
- package/dist/workers-git.d.ts +12 -4
- package/dist/workers-git.js +38 -35
- package/dist/workers.d.ts +9 -1
- package/dist/workers.js +44 -8
- package/dist/workflows.d.ts +2 -0
- package/dist/workflows.js +1 -0
- package/docs/api/README.md +7 -0
- package/docs/api/agent-schedules.md +175 -0
- package/docs/api/agents.md +34 -13
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +323 -225
- package/docs/api/auth.md +0 -55
- package/docs/api/authorization.md +1300 -0
- package/docs/api/build.md +40 -37
- package/docs/api/cloudflare.md +3 -3
- package/docs/api/git.md +20 -11
- package/docs/api/host.md +64 -192
- package/docs/api/index.md +1 -1
- package/docs/api/invocation.md +14 -14
- package/docs/api/keys.md +1 -98
- package/docs/api/mcp.md +43 -40
- package/docs/api/modules.md +2 -0
- package/docs/api/page/mount.md +16 -19
- package/docs/api/workers-git.md +46 -12
- package/docs/api/workers.md +124 -235
- package/package.json +39 -20
- package/dist/page/computer-target.d.ts +0 -3
- package/dist/page/computer-target.js +0 -9
- package/dist/standalone/channel-session-door.d.ts +0 -4
- package/dist/standalone/channel-session-door.js +0 -72
package/dist/agents.js
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* Agents for Apps on a standalone Oyster: one adapter shape and the routes
|
|
3
|
-
* the App page calls. Apps reach agents through Agar's `agent.list`,
|
|
4
|
-
* `agent.manage`, `agent.read` and `agent.send` contracts, so an App written
|
|
5
|
-
* for the Hub (Grove) runs unchanged. Each agent's own transport is
|
|
6
|
-
* Shiitake's HTTP routes (`/sessions…`, `/acp`) and Knapsack's Session browser
|
|
7
|
-
* transport (`/browser/…`), served by the adapter behind the same App grants.
|
|
8
|
-
*/
|
|
9
|
-
import { Hono } from "hono";
|
|
10
|
-
import { keyAgentAccess } from "./keys.js";
|
|
1
|
+
import { agent, agents as agentCollection, owner, routes, } from "./authorization.js";
|
|
11
2
|
import * as Schema from "effect/Schema";
|
|
12
3
|
import { z } from "zod";
|
|
13
|
-
import {
|
|
4
|
+
import {} from "./host.js";
|
|
14
5
|
import { agentAvatarSchema } from "./agent-avatars.js";
|
|
15
6
|
import { computerTargetParam } from "./computer-target.js";
|
|
16
7
|
import { agentAvatar, agentList, agentManage, agentRename, agentSessionRead, agentSessionWrite, agentToolPolicy, } from "@fungi.computer/agar/agents";
|
|
@@ -46,6 +37,13 @@ const visitSchema = Schema.Struct({
|
|
|
46
37
|
sessionId: Schema.String,
|
|
47
38
|
});
|
|
48
39
|
const agentIdPattern = /^[a-z0-9_-]{1,64}$/u;
|
|
40
|
+
/** Explicit owner-addressed settings remain reachable after list hides an archive. */
|
|
41
|
+
async function knownAgent(agents, agentId) {
|
|
42
|
+
if (!agentIdPattern.test(agentId))
|
|
43
|
+
return false;
|
|
44
|
+
return ((await agents.list()).some((agent) => agent.agentId === agentId) ||
|
|
45
|
+
(await agents.list({ archived: true })).some((agent) => agent.agentId === agentId));
|
|
46
|
+
}
|
|
49
47
|
/** How often an open inbox stream re-reads the revision. */
|
|
50
48
|
const inboxPollMs = 2_000;
|
|
51
49
|
const maxAgentRequestBytes = 512 * 1024;
|
|
@@ -53,9 +51,8 @@ function refusal(status, error) {
|
|
|
53
51
|
return Response.json({ error }, { status });
|
|
54
52
|
}
|
|
55
53
|
/** Agent adapters admitted against each App's declared capabilities. */
|
|
56
|
-
export function agentCapabilities(agents
|
|
57
|
-
const
|
|
58
|
-
const shared = { base: "/", routes };
|
|
54
|
+
export function agentCapabilities(agents) {
|
|
55
|
+
const shared = { base: "/", routes: createAgentRoutes(agents) };
|
|
59
56
|
return [
|
|
60
57
|
{
|
|
61
58
|
...shared,
|
|
@@ -136,45 +133,22 @@ export function agentCapabilities(agents, liveKey) {
|
|
|
136
133
|
},
|
|
137
134
|
];
|
|
138
135
|
}
|
|
139
|
-
function createAgentRoutes(agents
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
});
|
|
156
|
-
routes.use("/agent-policies/:agentId", async (context, next) => {
|
|
157
|
-
if (!(await access(context.env.principal, context.req.param("agentId"))))
|
|
158
|
-
return refusal(403, "Agent access denied");
|
|
159
|
-
await next();
|
|
160
|
-
return undefined;
|
|
161
|
-
});
|
|
162
|
-
routes.get("/agents", async (context) => {
|
|
163
|
-
const listed = await agents.list();
|
|
164
|
-
const actor = context.env.principal.actor;
|
|
165
|
-
if (oysterPrincipalIsOwner(context.env.principal))
|
|
166
|
-
return Response.json(listed);
|
|
167
|
-
if (actor.kind !== "team-key")
|
|
168
|
-
return Response.json([]);
|
|
169
|
-
const key = await liveKey(actor.keyId);
|
|
170
|
-
return Response.json(key === null
|
|
171
|
-
? []
|
|
172
|
-
: listed.filter((agent) => keyAgentAccess(key, agent.agentId)));
|
|
173
|
-
});
|
|
174
|
-
routes.post("/agents", async (context) => {
|
|
175
|
-
const { principal } = context.env;
|
|
176
|
-
if (!oysterPrincipalIsOwner(principal))
|
|
177
|
-
return refusal(403, "Only the owner creates agents");
|
|
136
|
+
function createAgentRoutes(agents) {
|
|
137
|
+
return (routes()
|
|
138
|
+
.get("/agents", agentCollection(), async (context, { reaches }) => {
|
|
139
|
+
const selected = context.req.query("archived");
|
|
140
|
+
if (selected !== undefined &&
|
|
141
|
+
selected !== "true" &&
|
|
142
|
+
selected !== "false")
|
|
143
|
+
return refusal(400, "Choose a valid archive view");
|
|
144
|
+
const listed = await agents.list({ archived: selected === "true" });
|
|
145
|
+
const reached = [];
|
|
146
|
+
for (const item of listed)
|
|
147
|
+
if (await reaches(item.agentId))
|
|
148
|
+
reached.push(item);
|
|
149
|
+
return Response.json(reached);
|
|
150
|
+
})
|
|
151
|
+
.post("/agents", owner("direct-or-app"), async (context, { principal }) => {
|
|
178
152
|
const body = Schema.decodeUnknownOption(createSchema)(await context.req.json());
|
|
179
153
|
if (body._tag === "None")
|
|
180
154
|
return refusal(400, "Name the agent");
|
|
@@ -186,11 +160,8 @@ function createAgentRoutes(agents, liveKey) {
|
|
|
186
160
|
// The owner's own authenticated request creates this Agent; its App tools
|
|
187
161
|
// are approved the same way direct owner approval is, with no second path.
|
|
188
162
|
return Response.json(await agents.create(body.value.name, body.value.requestId, principal, "owner-direct", avatar?.data));
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
const { principal } = context.env;
|
|
192
|
-
if (!oysterPrincipalIsOwner(principal))
|
|
193
|
-
return refusal(403, "Only the owner renames agents");
|
|
163
|
+
})
|
|
164
|
+
.put("/agent-names/:agentId", owner("direct-or-app"), async (context, { principal }) => {
|
|
194
165
|
const agentId = context.req.param("agentId");
|
|
195
166
|
if (!agentIdPattern.test(agentId))
|
|
196
167
|
return refusal(404, "No such agent");
|
|
@@ -205,11 +176,8 @@ function createAgentRoutes(agents, liveKey) {
|
|
|
205
176
|
? 409
|
|
206
177
|
: 403, result.error);
|
|
207
178
|
return Response.json(result);
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
const { principal } = context.env;
|
|
211
|
-
if (!oysterPrincipalIsOwner(principal))
|
|
212
|
-
return refusal(403, "Only the owner changes avatars");
|
|
179
|
+
})
|
|
180
|
+
.put("/agent-avatars/:agentId", owner("direct-or-app"), async (context, { principal }) => {
|
|
213
181
|
const agentId = context.req.param("agentId");
|
|
214
182
|
if (!agentIdPattern.test(agentId))
|
|
215
183
|
return refusal(404, "No such agent");
|
|
@@ -220,19 +188,19 @@ function createAgentRoutes(agents, liveKey) {
|
|
|
220
188
|
if ("error" in result)
|
|
221
189
|
return refusal(result.error === "not_found" ? 404 : 403, result.error);
|
|
222
190
|
return Response.json(result);
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
const agentId = context.req.param("agentId");
|
|
191
|
+
})
|
|
192
|
+
.get("/agent-policies/:agentId", agent(), async (_context, { agentId }) => {
|
|
226
193
|
if (!agentIdPattern.test(agentId))
|
|
227
194
|
return refusal(404, "No such agent");
|
|
228
195
|
const status = await agents.policyStatus(agentId);
|
|
229
196
|
return status === null
|
|
230
197
|
? refusal(404, "No such agent")
|
|
231
198
|
: Response.json(status);
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
199
|
+
})
|
|
200
|
+
.put("/agent-policies/:agentId", owner("direct-or-app"), async (context, { principal }) => approvePolicy(agents, principal, "owner-direct", context.req.param("agentId"), await context.req.text()))
|
|
201
|
+
.post("/agents/:agentId/archive", owner("direct-or-app"), (context, { principal }) => changeArchive(agents, context.req.param("agentId"), true, principal))
|
|
202
|
+
.post("/agents/:agentId/unarchive", owner("direct-or-app"), (context, { principal }) => changeArchive(agents, context.req.param("agentId"), false, principal))
|
|
203
|
+
.all("/agents/:agentId/*", agent(), async (context, { agentId }) => {
|
|
236
204
|
if (!agentIdPattern.test(agentId))
|
|
237
205
|
return refusal(404, "No such agent");
|
|
238
206
|
const incoming = context.req.raw;
|
|
@@ -252,49 +220,40 @@ function createAgentRoutes(agents, liveKey) {
|
|
|
252
220
|
target.searchParams.delete(computerTargetParam);
|
|
253
221
|
const response = await agents.fetch(agentId, new Request(target, incoming));
|
|
254
222
|
return response ?? refusal(404, "No such agent");
|
|
255
|
-
})
|
|
256
|
-
|
|
223
|
+
})
|
|
224
|
+
.get("/member/inbox", agentCollection(), async (_context, { authority, reaches }) => {
|
|
257
225
|
const inbox = await agents.inbox();
|
|
258
|
-
|
|
259
|
-
if (oysterPrincipalIsOwner(principal))
|
|
226
|
+
if (authority.kind === "owner")
|
|
260
227
|
return Response.json({
|
|
261
228
|
items: inbox.items,
|
|
262
229
|
next: null,
|
|
263
230
|
cursor: inbox.revision,
|
|
264
231
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
232
|
+
const items = [];
|
|
233
|
+
for (const item of inbox.items)
|
|
234
|
+
if ("agentId" in item && (await reaches(item.agentId)))
|
|
235
|
+
items.push(item);
|
|
268
236
|
return Response.json({
|
|
269
|
-
items
|
|
270
|
-
? []
|
|
271
|
-
: inbox.items.filter((item) => "agentId" in item && keyAgentAccess(key, item.agentId)),
|
|
237
|
+
items,
|
|
272
238
|
next: null,
|
|
273
239
|
// The global revision changes for ungranted Agents too. Key feeds have
|
|
274
240
|
// no scoped event stream, so expose neither that revision nor its events.
|
|
275
241
|
cursor: 0,
|
|
276
242
|
});
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
: refusal(403, "Agent access denied"));
|
|
281
|
-
routes.post("/member/inbox/visits", async (context) => {
|
|
243
|
+
})
|
|
244
|
+
.get("/member/inbox/notices", owner("direct-or-app"), () => Response.json({ items: [], next: null }))
|
|
245
|
+
.post("/member/inbox/visits", agent(async (facts) => (await facts.body(visitSchema))?.agentId ?? null), async (context, { agentId }) => {
|
|
282
246
|
const body = Schema.decodeUnknownOption(visitSchema)(await context.req.json());
|
|
283
247
|
if (body._tag === "None")
|
|
284
248
|
return refusal(400, "Invalid visit");
|
|
285
|
-
|
|
286
|
-
return refusal(403, "Agent access denied");
|
|
287
|
-
return (await agents.visit(body.value.agentId, body.value.sessionId))
|
|
249
|
+
return (await agents.visit(agentId, body.value.sessionId))
|
|
288
250
|
? new Response(null, { status: 204 })
|
|
289
251
|
: refusal(404, "No such Session");
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
// until a principal-scoped revision exists.
|
|
296
|
-
if (!oysterPrincipalIsOwner(context.env.principal))
|
|
297
|
-
return refusal(403, "Agent access denied");
|
|
252
|
+
})
|
|
253
|
+
// The feed is small (one person's agents), so a change is announced as a
|
|
254
|
+
// `reset` and the client re-reads the whole inbox. The unscoped revision
|
|
255
|
+
// also changes for other Agents, so only the owner reads it.
|
|
256
|
+
.get("/member/inbox/events", owner("direct-or-app"), (context) => {
|
|
298
257
|
const after = Number(context.req.query("after") ?? "0");
|
|
299
258
|
const signal = context.req.raw.signal;
|
|
300
259
|
const encoder = new TextEncoder();
|
|
@@ -321,20 +280,33 @@ function createAgentRoutes(agents, liveKey) {
|
|
|
321
280
|
"Cache-Control": "no-store",
|
|
322
281
|
},
|
|
323
282
|
});
|
|
324
|
-
});
|
|
325
|
-
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
/** Owner-only archive transition; creator Agents use their own control tool. */
|
|
286
|
+
async function changeArchive(agents, agentId, archived, principal) {
|
|
287
|
+
if (!agentIdPattern.test(agentId))
|
|
288
|
+
return refusal(404, "No such agent");
|
|
289
|
+
const result = await agents.setArchived(agentId, archived, principal);
|
|
290
|
+
if ("error" in result)
|
|
291
|
+
return Response.json(result, {
|
|
292
|
+
status: result.error === "not_found"
|
|
293
|
+
? 404
|
|
294
|
+
: result.error === "not_granted"
|
|
295
|
+
? 403
|
|
296
|
+
: result.archived !== undefined
|
|
297
|
+
? 503
|
|
298
|
+
: 409,
|
|
299
|
+
});
|
|
300
|
+
return Response.json(result);
|
|
326
301
|
}
|
|
327
302
|
async function approvePolicy(agents, principal, actor, agentId, body) {
|
|
328
|
-
if (!oysterPrincipalIsOwner(principal))
|
|
329
|
-
return refusal(403, "Only the owner approves Agent tools");
|
|
330
303
|
const input = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({
|
|
331
304
|
level: Schema.Literals(["read", "write"]),
|
|
332
305
|
expectedRevision: Schema.NullOr(Schema.NonEmptyString),
|
|
333
306
|
})), { onExcessProperty: "error" })(body);
|
|
334
307
|
if (input._tag === "None")
|
|
335
308
|
return refusal(400, "Choose read or write tools");
|
|
336
|
-
if (!
|
|
337
|
-
!(await agents.list()).some((agent) => agent.agentId === agentId))
|
|
309
|
+
if (!(await knownAgent(agents, agentId)))
|
|
338
310
|
return refusal(404, "No such agent");
|
|
339
311
|
const result = await agents.approveAppToolPolicy(agentId, principal, input.value.level, input.value.expectedRevision, actor);
|
|
340
312
|
const failureStatuses = {
|
|
@@ -348,11 +320,8 @@ async function approvePolicy(agents, principal, actor, agentId, body) {
|
|
|
348
320
|
}
|
|
349
321
|
/** Host-owned direct approval under the admitted owner; never an App capability. */
|
|
350
322
|
export function agentApprovalRoutes(agents) {
|
|
351
|
-
return
|
|
352
|
-
.get("/api/agent-models/:agentId", async (context) => {
|
|
353
|
-
if (context.req.raw.headers.has("X-Oyster-Installation") ||
|
|
354
|
-
!oysterPrincipalIsOwner(context.get("principal")))
|
|
355
|
-
return refusal(403, "Owner only");
|
|
323
|
+
return routes()
|
|
324
|
+
.get("/api/agent-models/:agentId", owner("direct"), async (context) => {
|
|
356
325
|
const agentId = context.req.param("agentId");
|
|
357
326
|
if (!agentIdPattern.test(agentId))
|
|
358
327
|
return refusal(404, "No such agent");
|
|
@@ -361,17 +330,14 @@ export function agentApprovalRoutes(agents) {
|
|
|
361
330
|
? refusal(404, "No such agent")
|
|
362
331
|
: Response.json(choice);
|
|
363
332
|
})
|
|
364
|
-
.put("/api/agent-models/:agentId", async (context) => {
|
|
365
|
-
if (context.req.raw.headers.has("X-Oyster-Installation") ||
|
|
366
|
-
!oysterPrincipalIsOwner(context.get("principal")))
|
|
367
|
-
return refusal(403, "Owner only");
|
|
333
|
+
.put("/api/agent-models/:agentId", owner("direct"), async (context, { principal }) => {
|
|
368
334
|
const agentId = context.req.param("agentId");
|
|
369
335
|
if (!agentIdPattern.test(agentId))
|
|
370
336
|
return refusal(404, "No such agent");
|
|
371
337
|
const input = agents.decodeModelChoice(await context.req.text());
|
|
372
338
|
if (input === null)
|
|
373
339
|
return refusal(400, "Invalid model choice");
|
|
374
|
-
const choice = await agents.setModelChoice(agentId, input,
|
|
340
|
+
const choice = await agents.setModelChoice(agentId, input, principal);
|
|
375
341
|
if ("error" in choice && choice.error === "invalid_model_choice")
|
|
376
342
|
return refusal(400, "Invalid model choice");
|
|
377
343
|
return "error" in choice
|
|
@@ -382,21 +348,15 @@ export function agentApprovalRoutes(agents) {
|
|
|
382
348
|
: 409, choice.error)
|
|
383
349
|
: Response.json(choice);
|
|
384
350
|
})
|
|
385
|
-
.get("/api/agent-policies/:agentId", async (context) => {
|
|
386
|
-
if (context.req.raw.headers.has("X-Oyster-Installation"))
|
|
387
|
-
return refusal(403, "Owner direct only");
|
|
388
|
-
if (!oysterPrincipalIsOwner(context.get("principal")))
|
|
389
|
-
return refusal(403, "Owner only");
|
|
351
|
+
.get("/api/agent-policies/:agentId", owner("direct"), async (context) => {
|
|
390
352
|
const agentId = context.req.param("agentId");
|
|
391
|
-
if (!(await agents
|
|
353
|
+
if (!(await knownAgent(agents, agentId)))
|
|
392
354
|
return refusal(404, "No such agent");
|
|
393
355
|
return Response.json({
|
|
394
356
|
expectedRevision: await agents.policyRevision(agentId),
|
|
395
357
|
});
|
|
396
358
|
})
|
|
397
|
-
.put("/api/agent-policies/:agentId", async (context) => context.req.
|
|
398
|
-
? refusal(403, "Missing admitted Agent installation")
|
|
399
|
-
: approvePolicy(agents, context.get("principal"), "owner-direct", context.req.param("agentId"), await context.req.text()));
|
|
359
|
+
.put("/api/agent-policies/:agentId", owner("direct"), async (context, { principal }) => approvePolicy(agents, principal, "owner-direct", context.req.param("agentId"), await context.req.text()));
|
|
400
360
|
}
|
|
401
361
|
const serverIdPattern = /^[a-z0-9_-]{1,64}$/u;
|
|
402
362
|
const mcpRefusalStatus = { not_found: 404, duplicate_name: 409, limit: 409 };
|
|
@@ -405,28 +365,18 @@ const mcpRefusalStatus = { not_found: 404, duplicate_name: 409, limit: 409 };
|
|
|
405
365
|
* capability. Bearer tokens go in and never come back out.
|
|
406
366
|
*/
|
|
407
367
|
export function agentMcpServerRoutes(agents) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
return refusal(403, "Owner direct only");
|
|
412
|
-
if (!oysterPrincipalIsOwner(context.get("principal")))
|
|
413
|
-
return refusal(403, "Only the owner connects MCP servers");
|
|
414
|
-
await next();
|
|
415
|
-
return undefined;
|
|
416
|
-
};
|
|
417
|
-
routes.use("/api/agent-mcp-servers", ownerOnly);
|
|
418
|
-
routes.use("/api/agent-mcp-servers/*", ownerOnly);
|
|
419
|
-
// One owner read for the settings panel: every Agent with its servers.
|
|
420
|
-
routes.get("/api/agent-mcp-servers", async () => {
|
|
368
|
+
return (routes()
|
|
369
|
+
// One owner read for the settings panel: every Agent with its servers.
|
|
370
|
+
.get("/api/agent-mcp-servers", owner("direct"), async () => {
|
|
421
371
|
const listed = await agents.list();
|
|
422
|
-
const rows = await Promise.all(listed.map(async (
|
|
423
|
-
agentId:
|
|
424
|
-
name:
|
|
425
|
-
servers: (await agents.mcpServers(
|
|
372
|
+
const rows = await Promise.all(listed.map(async (item) => ({
|
|
373
|
+
agentId: item.agentId,
|
|
374
|
+
name: item.name,
|
|
375
|
+
servers: (await agents.mcpServers(item.agentId)) ?? [],
|
|
426
376
|
})));
|
|
427
377
|
return Response.json({ agents: rows });
|
|
428
|
-
})
|
|
429
|
-
|
|
378
|
+
})
|
|
379
|
+
.post("/api/agent-mcp-servers/:agentId", owner("direct"), async (context) => {
|
|
430
380
|
if (!agentIdPattern.test(context.req.param("agentId")))
|
|
431
381
|
return refusal(404, "No such agent");
|
|
432
382
|
const length = Number(context.req.header("content-length") ?? "0");
|
|
@@ -439,8 +389,8 @@ export function agentMcpServerRoutes(agents) {
|
|
|
439
389
|
return "error" in added
|
|
440
390
|
? refusal(mcpRefusalStatus[added.error], added.error)
|
|
441
391
|
: Response.json(added, { status: 201 });
|
|
442
|
-
})
|
|
443
|
-
|
|
392
|
+
})
|
|
393
|
+
.delete("/api/agent-mcp-servers/:agentId/:serverId", owner("direct"), async (context) => {
|
|
444
394
|
const serverId = context.req.param("serverId");
|
|
445
395
|
if (!agentIdPattern.test(context.req.param("agentId")) ||
|
|
446
396
|
!serverIdPattern.test(serverId))
|
|
@@ -448,6 +398,5 @@ export function agentMcpServerRoutes(agents) {
|
|
|
448
398
|
return (await agents.removeMcpServer(context.req.param("agentId"), serverId))
|
|
449
399
|
? new Response(null, { status: 204 })
|
|
450
400
|
: refusal(404, "No such server");
|
|
451
|
-
});
|
|
452
|
-
return routes;
|
|
401
|
+
}));
|
|
453
402
|
}
|
package/dist/app-builds.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import * as Schema from "effect/Schema";
|
|
20
20
|
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
21
|
+
import { type BuildJobLog } from "./build/job-log.js";
|
|
21
22
|
import { type Release } from "@fungi.computer/puffball";
|
|
22
23
|
import { type AppPublisherCoreConfigInput } from "./build/effect.js";
|
|
23
24
|
import { type TeamAppCandidateManifest } from "./build/submission-contract.js";
|
|
@@ -144,10 +145,12 @@ export type OysterBuildComputer = Readonly<{
|
|
|
144
145
|
command: string;
|
|
145
146
|
timeoutMs: number;
|
|
146
147
|
}): Promise<Readonly<{
|
|
147
|
-
output
|
|
148
|
+
output: Readonly<{
|
|
148
149
|
exitCode: number | null;
|
|
149
150
|
stderr?: string;
|
|
150
151
|
}> | null;
|
|
152
|
+
/** Raw combined events captured before the Computer reply's stream bounds. */
|
|
153
|
+
log: BuildJobLog;
|
|
151
154
|
}>>;
|
|
152
155
|
}>;
|
|
153
156
|
export type OysterAppBuildOptions = Readonly<{
|
|
@@ -208,6 +211,8 @@ export declare function createOysterAppBuilds(options: OysterAppBuildOptions): R
|
|
|
208
211
|
run: () => Promise<OysterBuildView>;
|
|
209
212
|
}>>;
|
|
210
213
|
read(jobId: string): OysterBuildView | null;
|
|
214
|
+
/** Full raw output; the host applies the same principal guard as `read`. */
|
|
215
|
+
readLog(jobId: string): Promise<BuildJobLog | null>;
|
|
211
216
|
/** Discover the current source's latest job from the caller's Git owner,
|
|
212
217
|
* never a job id remembered in a browser or another installation's repo. */
|
|
213
218
|
latestRepositoryBuild(installationId: string, repository: string): OysterBuildView | null;
|
|
@@ -280,4 +285,4 @@ export declare function createOysterAppBuilds(options: OysterAppBuildOptions): R
|
|
|
280
285
|
submitCandidate(jobId: string, manifest: TeamAppCandidateManifest, grant: string, credentialId: string): Promise<Response>;
|
|
281
286
|
}>;
|
|
282
287
|
export type OysterAppBuilds = ReturnType<typeof createOysterAppBuilds>;
|
|
283
|
-
export {};
|
|
288
|
+
export { buildJobLogMaxBytes, type BuildJobLog } from "./build/job-log.js";
|
package/dist/app-builds.js
CHANGED
|
@@ -20,6 +20,7 @@ import * as Schema from "effect/Schema";
|
|
|
20
20
|
import * as Data from "effect/Data";
|
|
21
21
|
import * as Option from "effect/Option";
|
|
22
22
|
import { candidateFailureDiagnostic } from "./build/candidate-diagnostic.js";
|
|
23
|
+
import { buildJobDiagnostic, buildJobLogSchema, } from "./build/job-log.js";
|
|
23
24
|
import { parseArtifactRef, parseReleaseId, } from "@fungi.computer/puffball";
|
|
24
25
|
import { appIdSchema, releaseSchema, sha256DigestSchema, sourceRevisionSchema, } from "@fungi.computer/puffball/effect";
|
|
25
26
|
import { capturedPinnedRecipeSchema, normalizeAppPublisherRecipe, digestPinnedRecipe, fitsPinnedRecipe, pinnedRecipeObjectBound, } from "./build/effect.js";
|
|
@@ -235,8 +236,9 @@ export function createOysterAppBuilds(options) {
|
|
|
235
236
|
detail: job.detail,
|
|
236
237
|
});
|
|
237
238
|
const settle = (jobId, status, detail) => {
|
|
239
|
+
const diagnostic = detail === null ? null : buildJobDiagnostic(jobId, detail);
|
|
238
240
|
owner.sql.exec(`UPDATE oyster_build_jobs SET installation=CASE WHEN ?='succeeded' THEN installation ELSE ${failedInstallation} END,
|
|
239
|
-
status=?, detail=?, expires_at=MIN(expires_at, ?) WHERE job_id=? AND status='running'`, status,
|
|
241
|
+
status=?, detail=?, expires_at=MIN(expires_at, ?) WHERE job_id=? AND status='running'`, status, diagnostic, status, diagnostic, now(), jobId);
|
|
240
242
|
};
|
|
241
243
|
const required = (jobId) => {
|
|
242
244
|
const job = readJob(jobId);
|
|
@@ -244,6 +246,10 @@ export function createOysterAppBuilds(options) {
|
|
|
244
246
|
throw new Error("Unknown build job");
|
|
245
247
|
return job;
|
|
246
248
|
};
|
|
249
|
+
// A job-scoped immutable object in the same artifact store as its build.
|
|
250
|
+
// It is read only through the owner build door, never exposed as a Release.
|
|
251
|
+
const logRef = (jobId) => parseArtifactRef(`artifact:build-log-${jobId}`);
|
|
252
|
+
const logCodec = Schema.fromJsonString(buildJobLogSchema);
|
|
247
253
|
const publisher = createTeamBuildPublisher({
|
|
248
254
|
teamId: options.tenantId,
|
|
249
255
|
now,
|
|
@@ -284,7 +290,8 @@ export function createOysterAppBuilds(options) {
|
|
|
284
290
|
command: input.command,
|
|
285
291
|
timeoutMs: input.timeoutMs,
|
|
286
292
|
});
|
|
287
|
-
|
|
293
|
+
await options.artifacts.put(logRef(input.jobId), new TextEncoder().encode(Schema.encodeUnknownSync(logCodec)(result.log)));
|
|
294
|
+
if (result.output === null)
|
|
288
295
|
throw new BuildOutcomeUnknown();
|
|
289
296
|
return result;
|
|
290
297
|
},
|
|
@@ -458,6 +465,15 @@ export function createOysterAppBuilds(options) {
|
|
|
458
465
|
const job = readJob(jobId);
|
|
459
466
|
return job === null ? null : view(job);
|
|
460
467
|
},
|
|
468
|
+
/** Full raw output; the host applies the same principal guard as `read`. */
|
|
469
|
+
async readLog(jobId) {
|
|
470
|
+
if (readJob(jobId) === null)
|
|
471
|
+
return null;
|
|
472
|
+
const bytes = await options.artifacts.get(logRef(jobId));
|
|
473
|
+
return bytes === null
|
|
474
|
+
? null
|
|
475
|
+
: Schema.decodeUnknownSync(logCodec)(new TextDecoder().decode(bytes));
|
|
476
|
+
},
|
|
461
477
|
/** Discover the current source's latest job from the caller's Git owner,
|
|
462
478
|
* never a job id remembered in a browser or another installation's repo. */
|
|
463
479
|
latestRepositoryBuild(installationId, repository) {
|
|
@@ -660,3 +676,4 @@ export function createOysterAppBuilds(options) {
|
|
|
660
676
|
},
|
|
661
677
|
});
|
|
662
678
|
}
|
|
679
|
+
export { buildJobLogMaxBytes } from "./build/job-log.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Approvals } from "@fungi.computer/approvals";
|
|
2
2
|
import type { HostActor } from "@fungi.computer/puffball/app";
|
|
3
3
|
import type { OysterCapability } from "./host.js";
|
|
4
|
-
/** Ordinary capability routes; the outer App gate supplies
|
|
4
|
+
/** Ordinary capability routes; the outer App gate supplies consent, and only members answer. */
|
|
5
5
|
export declare function approvalCapability(control: (actor: HostActor) => Approvals): OysterCapability;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { member, routes as routesFor } from "./authorization.js";
|
|
2
2
|
import { bodyLimit } from "hono/body-limit";
|
|
3
3
|
import { causeClass } from "./cause-class.js";
|
|
4
4
|
import * as Schema from "effect/Schema";
|
|
5
5
|
import { ApprovalListInputSchema, ApprovalGetInputSchema, ApprovalAnswerInputSchema, AutoApproveInputSchema, } from "@fungi.computer/approvals";
|
|
6
|
-
/** Ordinary capability routes; the outer App gate supplies
|
|
6
|
+
/** Ordinary capability routes; the outer App gate supplies consent, and only members answer. */
|
|
7
7
|
export function approvalCapability(control) {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
await
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
const strict = { onExcessProperty: "error" };
|
|
9
|
+
const eligible = member(() => Response.json({ kind: "refused", reason: "not-eligible" }, { status: 403 }));
|
|
10
|
+
const options = { middleware: [bodyLimit({ maxSize: 4096 })] };
|
|
11
|
+
const routes = routesFor()
|
|
12
|
+
.post("/list", eligible, async (c, { principal }) => c.json(await control(principal.actor).list(Schema.decodeUnknownSync(ApprovalListInputSchema, strict)(await c.req.json()))), options)
|
|
13
|
+
.post("/get", eligible, async (c, { principal }) => c.json(await control(principal.actor).get(Schema.decodeUnknownSync(ApprovalGetInputSchema, strict)(await c.req.json()))), options)
|
|
14
|
+
.post("/answer", eligible, async (c, { principal }) => c.json(await control(principal.actor).answer(Schema.decodeUnknownSync(ApprovalAnswerInputSchema, strict)(await c.req.json()))), options)
|
|
15
|
+
.post("/set-auto-approve", eligible, async (c, { principal }) => c.json(await control(principal.actor).setAutoApprove(Schema.decodeUnknownSync(AutoApproveInputSchema, strict)(await c.req.json()))), options)
|
|
16
|
+
.onError((cause, context) => {
|
|
17
17
|
if (cause instanceof SyntaxError || cause instanceof Schema.SchemaError)
|
|
18
18
|
return context.json({ error: "Invalid approval request" }, 400);
|
|
19
19
|
console.warn("Native approval control failed", {
|
|
@@ -21,11 +21,6 @@ export function approvalCapability(control) {
|
|
|
21
21
|
});
|
|
22
22
|
return context.json({ error: "Approval home unavailable" }, 503);
|
|
23
23
|
});
|
|
24
|
-
const strict = { onExcessProperty: "error" };
|
|
25
|
-
routes.post("/list", async (c) => c.json(await control(c.env.principal.actor).list(Schema.decodeUnknownSync(ApprovalListInputSchema, strict)(await c.req.json()))));
|
|
26
|
-
routes.post("/get", async (c) => c.json(await control(c.env.principal.actor).get(Schema.decodeUnknownSync(ApprovalGetInputSchema, strict)(await c.req.json()))));
|
|
27
|
-
routes.post("/answer", async (c) => c.json(await control(c.env.principal.actor).answer(Schema.decodeUnknownSync(ApprovalAnswerInputSchema, strict)(await c.req.json()))));
|
|
28
|
-
routes.post("/set-auto-approve", async (c) => c.json(await control(c.env.principal.actor).setAutoApprove(Schema.decodeUnknownSync(AutoApproveInputSchema, strict)(await c.req.json()))));
|
|
29
24
|
return {
|
|
30
25
|
id: "approvals",
|
|
31
26
|
version: 1,
|
package/dist/approval-home.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ApprovalLedger, type Approvals, type PendingApproval, type ApprovalApplyResult } from "@fungi.computer/approvals";
|
|
3
3
|
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
4
|
-
import {
|
|
4
|
+
import type { OysterHost } from "./host.js";
|
|
5
5
|
import type { HostActor } from "@fungi.computer/puffball/app";
|
|
6
6
|
import type { TenantScope } from "./tenant.js";
|
|
7
7
|
declare const callSchema: z.ZodObject<{
|
|
@@ -32,6 +32,7 @@ export type OysterApprovalCall = z.output<typeof callSchema>;
|
|
|
32
32
|
export declare class OysterApprovalHome {
|
|
33
33
|
private readonly host;
|
|
34
34
|
readonly ledger: ApprovalLedger;
|
|
35
|
+
private surfaces;
|
|
35
36
|
constructor(host: Readonly<{
|
|
36
37
|
owner: WatchdogSqliteOwner;
|
|
37
38
|
scope: TenantScope;
|
package/dist/approval-home.js
CHANGED
|
@@ -3,9 +3,8 @@ import * as Schema from "effect/Schema";
|
|
|
3
3
|
import { hostActorSchema } from "@fungi.computer/puffball/app-method-runtime";
|
|
4
4
|
import { ApprovalLedger, } from "@fungi.computer/approvals";
|
|
5
5
|
import { invokeModuleOperation } from "@fungi.computer/mycelium/remote";
|
|
6
|
-
import { oysterPrincipalIsOwner } from "./
|
|
6
|
+
import { keyAppAccess, oysterPrincipalIsOwner } from "./authorization.js";
|
|
7
7
|
import { sha256 } from "./release.js";
|
|
8
|
-
import { keyAppAccess } from "./keys.js";
|
|
9
8
|
const identity = z.string().min(1).max(256);
|
|
10
9
|
const targetSchema = z.strictObject({
|
|
11
10
|
actor: hostActorSchema,
|
|
@@ -25,6 +24,9 @@ const intentRow = Schema.decodeUnknownSync(Schema.Struct({ target_json: Schema.S
|
|
|
25
24
|
export class OysterApprovalHome {
|
|
26
25
|
host;
|
|
27
26
|
ledger;
|
|
27
|
+
// The home's artifact store and capability configuration are fixed, unlike
|
|
28
|
+
// the authority read fresh from current() for every recheck and dispatch.
|
|
29
|
+
surfaces;
|
|
28
30
|
constructor(host) {
|
|
29
31
|
this.host = host;
|
|
30
32
|
host.owner.sql.exec("CREATE TABLE IF NOT EXISTS oyster_approval_targets (record_id TEXT PRIMARY KEY, target_json TEXT NOT NULL) STRICT");
|
|
@@ -180,7 +182,7 @@ export class OysterApprovalHome {
|
|
|
180
182
|
byInstallation: new Map([[target.installationId, policy]]),
|
|
181
183
|
};
|
|
182
184
|
}
|
|
183
|
-
const snapshot = await oysterAppSnapshot(host, appSurfaceCache(host), principal, [installation], access, target.sessionId
|
|
185
|
+
const snapshot = await oysterAppSnapshot(host, (this.surfaces ??= appSurfaceCache(host)), principal, [installation], access, target.sessionId, "direct");
|
|
184
186
|
const module = snapshot.modules[record.module];
|
|
185
187
|
const fence = snapshot.fences[record.module];
|
|
186
188
|
const effect = snapshot.effects[record.module]?.[record.operation];
|
package/dist/apps/catalog.js
CHANGED
|
@@ -34,7 +34,7 @@ export const permissionCatalog = {
|
|
|
34
34
|
"apps.call": entry("apps", "Call declared App methods", "call the listed methods in other Apps as you. Renamed target methods need approval again.", "run", "install"),
|
|
35
35
|
"app.storage": entry("data", "Store App data", "save its App data.", "write", "install"),
|
|
36
36
|
"app.events.emit": entry("workflows", "Send events", "tell your Workflows when something happens.", "sendPrompts", "ask"),
|
|
37
|
-
"team.workbenchHost": entry("shell", "
|
|
37
|
+
"team.workbenchHost": entry("shell", "Shell", "Full access to your Team and every App", "list", "ask", null, "Only pick a shell you trust."),
|
|
38
38
|
"marketplace.browse": entry("bazaar", "Browse the marketplace", "browse Apps in the marketplace.", "browseBazaar", "install"),
|
|
39
39
|
"marketplace.purchase": entry("bazaar", "Purchase from the marketplace", "purchase Apps from the marketplace.", "buyApps", "install"),
|
|
40
40
|
"workflow.run": entry("workflows", "Run Workflows", "start Workflows.", "run", "install"),
|
package/dist/apps/gate.d.ts
CHANGED
|
@@ -4,9 +4,7 @@ import { type AppToolAccess } from "./tools.js";
|
|
|
4
4
|
import { type ResourceRef, type DeclaredOperations, type Installation, type ApprovedGrant } from "./installation.js";
|
|
5
5
|
/** Attested by the host, never authenticated by decoding request JSON. */
|
|
6
6
|
export type HostPrincipal = Readonly<{
|
|
7
|
-
actor: HostActor
|
|
8
|
-
kind: "visitor";
|
|
9
|
-
}>;
|
|
7
|
+
actor: HostActor;
|
|
10
8
|
role: "owner" | "member" | null;
|
|
11
9
|
}>;
|
|
12
10
|
/** Requests select existing SDK methods, opaque backend calls and derived operation identities. */
|
|
@@ -54,7 +52,7 @@ export type GateRequest = z.output<typeof gateRequestSchema>;
|
|
|
54
52
|
/** Immutable values the host already read; resource/grantor freshness remains host-owned. */
|
|
55
53
|
export type GateFacts = Readonly<{
|
|
56
54
|
principal: HostPrincipal;
|
|
57
|
-
installation: Readonly<Pick<Installation, "status" | "
|
|
55
|
+
installation: Readonly<Pick<Installation, "status" | "instanceId" | "authority"> & {
|
|
58
56
|
artifactDigest: string;
|
|
59
57
|
}>;
|
|
60
58
|
declaration: AppDeclaration;
|
|
@@ -87,7 +85,6 @@ export declare const gateRefusalSchema: z.ZodLazy<z.ZodObject<{
|
|
|
87
85
|
approval_required: "approval_required";
|
|
88
86
|
requires_not_granted: "requires_not_granted";
|
|
89
87
|
invalid_input: "invalid_input";
|
|
90
|
-
visitor_read_only: "visitor_read_only";
|
|
91
88
|
}>;
|
|
92
89
|
}, z.core.$strict>>;
|
|
93
90
|
/** Either an admitted scoped ceiling or one closed refusal code. */
|
|
@@ -100,13 +97,13 @@ export type GateDecision = Readonly<{
|
|
|
100
97
|
export declare function grantsFor(installation: Pick<GateFacts["installation"], "status" | "authority" | "artifactDigest">, declaration: AppDeclaration, snapshot: DeclaredOperations, capability: ApprovedGrant["capability"], resource?: ResourceRef | "any"): ApprovedGrant | null;
|
|
101
98
|
/** Approved binding operations from an already verified declaration. Hosts still pin
|
|
102
99
|
* the selection/version/artifact and compare running frame grants as a subset. */
|
|
103
|
-
export declare function approvedBindingGrants(installation: Pick<GateFacts["installation"], "status" | "authority">, declaration: AppDeclaration): InvocationGrants;
|
|
100
|
+
export declare function approvedBindingGrants(installation: Pick<GateFacts["installation"], "status" | "authority">, declaration: AppDeclaration, resource?: ResourceRef | "any"): InvocationGrants;
|
|
104
101
|
/** Narrow captured binding operations to the method's verified requirements and effect.
|
|
105
102
|
* The captured set is the ceiling; this never adds an operation or resource authority. */
|
|
106
103
|
export declare function scopedBindings(captured: InvocationGrants, method?: AppMethodDeclaration, ceiling?: "read" | "write"): InvocationGrants;
|
|
107
104
|
/** Principal ceiling for invocation and metadata capture. Captures must pass this
|
|
108
105
|
* to scopedBindings; its default write ceiling does not encode caller access. */
|
|
109
|
-
export declare function appInvocationCeiling(facts: Pick<GateFacts, "
|
|
106
|
+
export declare function appInvocationCeiling(facts: Pick<GateFacts, "access">): "read" | "write";
|
|
110
107
|
/** Permission metadata only: this is neither an invocation grant nor input admission. */
|
|
111
108
|
export type AppMethodMetadataDecision = Readonly<{
|
|
112
109
|
kind: "admitted";
|