@fungi.computer/oyster 0.1.0 → 0.3.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 +376 -72
- package/dist/agent-avatars.d.ts +103 -0
- package/dist/agent-avatars.js +66 -0
- package/dist/agents.d.ts +15 -4
- package/dist/agents.js +43 -4
- package/dist/app-builds.d.ts +81 -8
- package/dist/app-builds.js +210 -60
- package/dist/app-storage.d.ts +3 -3
- package/dist/approval-capability.d.ts +5 -0
- package/dist/approval-capability.js +46 -0
- package/dist/approval-home.d.ts +51 -0
- package/dist/approval-home.js +226 -0
- package/dist/apps/catalog.d.ts +335 -0
- package/dist/apps/catalog.js +46 -0
- package/dist/apps/gate.d.ts +26 -12
- package/dist/apps/gate.js +48 -22
- package/dist/apps/index.d.ts +3 -1
- package/dist/apps/index.js +3 -1
- package/dist/apps/installation.d.ts +147 -66
- package/dist/apps/installation.js +193 -86
- package/dist/apps/permissions.d.ts +247 -0
- package/dist/apps/permissions.js +255 -0
- package/dist/boot-capability-route.d.ts +15 -0
- package/dist/boot-capability-route.js +196 -0
- package/dist/boot-capability.d.ts +103 -0
- package/dist/boot-capability.js +141 -0
- package/dist/boot-construction.d.ts +12 -0
- package/dist/boot-construction.js +175 -0
- package/dist/boot-gate.d.ts +267 -0
- package/dist/boot-gate.js +236 -0
- package/dist/boot-host-script.d.ts +2 -0
- package/dist/boot-host-script.js +1 -0
- package/dist/boot-mount.d.ts +21 -0
- package/dist/boot-mount.js +65 -0
- package/dist/browser/boot.d.ts +73 -0
- package/dist/browser/boot.js +502 -0
- package/dist/browser/browser.d.ts +104 -0
- package/dist/browser/browser.js +374 -0
- package/dist/browser/capability-source.d.ts +76 -0
- package/dist/browser/capability-source.js +338 -0
- package/dist/browser/cdp-client.d.ts +56 -0
- package/dist/browser/cdp-client.js +184 -0
- package/dist/browser/errors.d.ts +79 -0
- package/dist/browser/errors.js +32 -0
- package/dist/browser/index.d.ts +16 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/lease-registry.d.ts +61 -0
- package/dist/browser/lease-registry.js +101 -0
- package/dist/browser/operations.d.ts +225 -0
- package/dist/browser/operations.js +137 -0
- package/dist/browser/page.d.ts +25 -0
- package/dist/browser/page.js +172 -0
- package/dist/browser/quick-reads.d.ts +29 -0
- package/dist/browser/quick-reads.js +160 -0
- package/dist/browser/sessions.d.ts +52 -0
- package/dist/browser/sessions.js +118 -0
- package/dist/browser/url-policy.d.ts +18 -0
- package/dist/browser/url-policy.js +58 -0
- package/dist/build/app-imports.d.ts +12 -0
- package/dist/build/app-imports.js +105 -0
- package/dist/build/build-policy.d.ts +5 -3
- package/dist/build/build-policy.js +5 -3
- package/dist/build/candidate-builder.d.ts +1 -1
- package/dist/build/candidate-cli.mjs +8428 -337
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +28 -6
- package/dist/build/snapshot-package-store.d.ts +1 -1
- package/dist/build/snapshot-package-store.js +7 -2
- package/dist/build/workspace-build.d.ts +92 -5
- package/dist/build/workspace-build.js +93 -19
- package/dist/build-egress.js +4 -4
- package/dist/build.d.ts +1 -1
- package/dist/build.js +1 -1
- package/dist/catalog-frame.js +1 -1
- package/dist/cloudflare.d.ts +13 -3
- package/dist/cloudflare.js +106 -8
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.d.ts +1 -1
- package/dist/frame-binding-wire.js +5 -3
- package/dist/git-auth.d.ts +0 -2
- package/dist/git-auth.js +0 -2
- package/dist/git-journal.js +4 -4
- package/dist/git.d.ts +0 -1
- package/dist/git.js +0 -2
- package/dist/host.d.ts +39 -13
- package/dist/host.js +268 -142
- package/dist/index.d.ts +26 -15
- package/dist/index.js +40 -16
- package/dist/install-release-cli.d.ts +2 -0
- package/dist/install-release-cli.js +46 -0
- package/dist/installation-client.d.ts +43 -0
- package/dist/installation-client.js +178 -0
- package/dist/installation-execution.d.ts +11 -0
- package/dist/installation-execution.js +184 -0
- package/dist/installation-operations.d.ts +1437 -0
- package/dist/installation-operations.js +664 -0
- package/dist/installation-store-generation.d.ts +3 -0
- package/dist/installation-store-generation.js +16 -0
- package/dist/instance-id.d.ts +10 -0
- package/dist/instance-id.js +10 -0
- package/dist/instance-refusal.d.ts +11 -0
- package/dist/instance-refusal.js +21 -0
- package/dist/internal/delivery.js +4 -2
- package/dist/internal/host-gate.js +7 -1
- package/dist/internal/registry-installations.js +4 -4
- package/dist/keys.d.ts +5 -1
- package/dist/keys.js +10 -2
- package/dist/machine-link/authority.d.ts +89 -0
- package/dist/machine-link/authority.js +37 -0
- package/dist/machine-link/client.d.ts +66 -0
- package/dist/machine-link/client.js +94 -0
- package/dist/machine-link/delivery.d.ts +18 -0
- package/dist/machine-link/delivery.js +55 -0
- package/dist/machine-link/enrollment.d.ts +111 -0
- package/dist/machine-link/enrollment.js +286 -0
- package/dist/machine-link/fences.d.ts +37 -0
- package/dist/machine-link/fences.js +166 -0
- package/dist/machine-link/home.d.ts +20 -0
- package/dist/machine-link/home.js +24 -0
- package/dist/machine-link/host.d.ts +43 -0
- package/dist/machine-link/host.js +189 -0
- package/dist/machine-link/index.d.ts +15 -0
- package/dist/machine-link/index.js +15 -0
- package/dist/machine-link/link.d.ts +48 -0
- package/dist/machine-link/link.js +223 -0
- package/dist/machine-link/presence.d.ts +10 -0
- package/dist/machine-link/presence.js +25 -0
- package/dist/machine-link/workers.d.ts +6 -0
- package/dist/machine-link/workers.js +6 -0
- package/dist/mcp-owner.d.ts +2 -2
- package/dist/mcp-owner.js +41 -33
- package/dist/mcp.d.ts +15 -3
- package/dist/mcp.js +146 -25
- package/dist/pack.d.ts +14 -3
- package/dist/pack.js +44 -19
- package/dist/page/app-access.d.ts +7 -0
- package/dist/page/app-access.js +75 -0
- package/dist/page/boot-host.js +57 -0
- package/dist/page/capabilities.d.ts +3 -3
- package/dist/page/capabilities.js +16 -12
- package/dist/page/computer-handles.d.ts +3 -3
- package/dist/page/computer-target.d.ts +3 -0
- package/dist/page/computer-target.js +9 -0
- package/dist/page/mount.d.ts +23 -7
- package/dist/page/mount.js +152 -16
- package/dist/page-assets.d.ts +22 -0
- package/dist/page-assets.js +28 -0
- package/dist/page-script.d.ts +3 -0
- package/dist/page-script.js +5 -4
- package/dist/release.d.ts +13 -6
- package/dist/release.js +10 -7
- package/dist/root-data.d.ts +16 -6
- package/dist/root-data.js +2 -2
- package/dist/source/index.d.ts +1 -1
- package/dist/source/team-source.d.ts +20 -8
- package/dist/source/team-source.js +39 -10
- package/dist/source/template-seed.d.ts +2 -2
- package/dist/standalone/agent-mail.d.ts +24 -0
- package/dist/standalone/agent-mail.js +62 -0
- package/dist/standalone/agent-store.d.ts +9 -0
- package/dist/standalone/agent-store.js +30 -0
- package/dist/standalone/agents.d.ts +78 -12
- package/dist/standalone/agents.js +470 -59
- package/dist/standalone/app-builds-binding.d.ts +20 -2
- package/dist/standalone/app-builds-binding.js +151 -27
- package/dist/standalone/app-set.d.ts +5 -2
- package/dist/standalone/app-set.js +174 -143
- package/dist/standalone/browser.d.ts +11 -0
- package/dist/standalone/browser.js +72 -0
- package/dist/standalone/builds.d.ts +13 -4
- package/dist/standalone/builds.js +28 -44
- package/dist/standalone/computer-egress.d.ts +2 -0
- package/dist/standalone/computer-egress.js +13 -4
- package/dist/standalone/computer-fleet.d.ts +43 -0
- package/dist/standalone/computer-fleet.js +200 -0
- package/dist/standalone/computer.d.ts +44 -9
- package/dist/standalone/computer.js +132 -25
- package/dist/standalone/install-apps-cli.js +43 -6
- package/dist/standalone/instance.d.ts +2 -2
- package/dist/standalone/instance.js +8 -11
- package/dist/standalone/machine-link.d.ts +354 -0
- package/dist/standalone/machine-link.js +368 -0
- package/dist/standalone/media.d.ts +2 -2
- package/dist/standalone/media.js +19 -16
- package/dist/standalone/preview.d.ts +2 -2
- package/dist/standalone/preview.js +39 -12
- package/dist/standalone/registry.d.ts +149 -9
- package/dist/standalone/registry.js +299 -8
- package/dist/standalone/worker.d.ts +19 -5
- package/dist/standalone/worker.js +90 -54
- package/dist/standalone/workflow-binding.d.ts +28 -32
- package/dist/standalone/workflow-binding.js +20 -20
- package/dist/standalone/workflow-control.d.ts +4 -0
- package/dist/standalone/workflow-control.js +40 -0
- package/dist/standalone/workflow-events.d.ts +2 -2
- package/dist/standalone/workflow-events.js +9 -10
- package/dist/standalone/workflow-git-capture.js +4 -3
- package/dist/standalone/workflow-host.d.ts +177 -152
- package/dist/standalone/workflow-host.js +137 -30
- package/dist/standalone/workflow-node-capture.js +11 -4
- package/dist/standalone/workflow-schedules.d.ts +5 -2
- package/dist/standalone/workflow-schedules.js +9 -10
- package/dist/standalone/workflow-trigger-host.d.ts +88 -6
- package/dist/standalone/workflow-trigger-host.js +2 -2
- package/dist/standalone/workflow-webhooks.d.ts +22 -0
- package/dist/standalone/workflow-webhooks.js +39 -0
- package/dist/standalone/workflows.d.ts +203 -50
- package/dist/standalone/workflows.js +198 -39
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.js +14 -3
- package/dist/testing/installation.js +26 -2
- package/dist/testing/worker.js +1 -1
- package/dist/testing/workers-runtime.d.ts +8 -2
- package/dist/testing/workers-runtime.js +20 -2
- package/dist/webhook-abuse.d.ts +25 -0
- package/dist/webhook-abuse.js +54 -0
- package/dist/webhook-custody.d.ts +144 -0
- package/dist/webhook-custody.js +222 -0
- package/dist/webhook-ingress.d.ts +36 -0
- package/dist/webhook-ingress.js +146 -0
- package/dist/webhooks.d.ts +111 -0
- package/dist/webhooks.js +189 -0
- package/dist/workers-git.d.ts +12 -17
- package/dist/workers-git.js +60 -55
- package/dist/workers.d.ts +224 -13
- package/dist/workers.js +351 -51
- package/dist/workflow-agent-authority.d.ts +8 -0
- package/dist/workflow-agent-authority.js +42 -0
- package/dist/workflows.d.ts +66 -0
- package/dist/workflows.js +97 -0
- package/docs/api/README.md +63 -37
- package/docs/api/agents.md +17 -0
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +1394 -543
- package/docs/api/browser/boot.md +132 -0
- package/docs/api/browser.md +1440 -0
- package/docs/api/build-contract.md +38 -25
- package/docs/api/build.md +263 -84
- package/docs/api/cloudflare.md +93 -7
- package/docs/api/git.md +3 -13
- package/docs/api/host.md +944 -118
- package/docs/api/index.md +320 -18
- package/docs/api/keys.md +24 -1
- package/docs/api/mcp.md +59 -12
- package/docs/api/modules.md +33 -40
- package/docs/api/pack.md +57 -8
- package/docs/api/page/mount.md +92 -4
- package/docs/api/workers-git.md +103 -24
- package/docs/api/workers.md +1581 -244
- package/docs/site/agent-quickstart.mdx +28 -14
- package/examples/standalone/worker.ts +3 -1
- package/examples/standalone/wrangler.jsonc +2 -0
- package/package.json +73 -69
- package/dist/installation-consent.d.ts +0 -79
- package/dist/installation-consent.js +0 -82
- package/dist/shell/app-directory.d.ts +0 -542
- package/dist/shell/app-directory.js +0 -199
- package/dist/shell/browser-actions.d.ts +0 -7
- package/dist/shell/browser-actions.js +0 -56
- package/dist/shell/command-host.d.ts +0 -112
- package/dist/shell/command-host.js +0 -502
- package/dist/shell/commands.d.ts +0 -25
- package/dist/shell/commands.js +0 -78
- package/dist/shell/index.d.ts +0 -8
- package/dist/shell/index.js +0 -8
- package/dist/shell/keys.d.ts +0 -41
- package/dist/shell/keys.js +0 -134
- package/dist/shell/shell-module.d.ts +0 -55
- package/dist/shell/shell-module.js +0 -86
- package/dist/shell/window-manager.d.ts +0 -124
- package/dist/shell/window-manager.js +0 -711
- package/dist/shell/workbench-contract.d.ts +0 -68
- package/dist/shell/workbench-directory.d.ts +0 -332
- package/dist/shell/workbench-directory.js +0 -35
- package/dist/shell/workbench-host-types.d.ts +0 -96
- package/dist/shell/workbench-host-types.js +0 -1
- package/dist/shell/workbench-host.d.ts +0 -34
- package/dist/shell/workbench-host.js +0 -347
- package/dist/shell/workbench-model.d.ts +0 -192
- package/dist/shell/workbench-model.js +0 -53
- package/dist/shell/workbench.d.ts +0 -4
- package/dist/shell/workbench.js +0 -2
- package/docs/api/shell/app-directory.md +0 -599
- package/docs/api/shell/command-host.md +0 -224
- package/docs/api/shell/commands.md +0 -197
- package/docs/api/shell/keys.md +0 -183
- package/docs/api/shell/shell-module.md +0 -204
- package/docs/api/shell/window-manager.md +0 -267
- package/docs/api/shell/workbench-directory.md +0 -119
- package/docs/api/shell/workbench.md +0 -205
- package/docs/api/shell.md +0 -1915
- /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
|
@@ -4,7 +4,8 @@ import * as Result from "effect/Result";
|
|
|
4
4
|
import { appCapabilityIdSchema, appApiMethodNameSchema, } from "@fungi.computer/puffball/app";
|
|
5
5
|
import { hostActorSchema } from "@fungi.computer/puffball/app-method-runtime";
|
|
6
6
|
import { appIdSchema, releaseSchema, sha256DigestSchema, } from "@fungi.computer/puffball/effect";
|
|
7
|
-
import { instanceIdSchema } from "../
|
|
7
|
+
import { instanceIdSchema } from "../instance-id.js";
|
|
8
|
+
import { permissionCatalog } from "./catalog.js";
|
|
8
9
|
/** Reuse the owning Effect codec at a JSON/storage boundary. */
|
|
9
10
|
export function ownerCodec(schema) {
|
|
10
11
|
const decode = Schema.decodeUnknownResult(schema, {
|
|
@@ -20,30 +21,28 @@ export function ownerCodec(schema) {
|
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
23
|
/** Installation names cannot contain the tenant separator. */
|
|
23
|
-
export const installationIdSchema = z
|
|
24
|
-
.string()
|
|
25
|
-
.regex(/^[a-z](?:[a-z0-9]|-(?!-)){0,62}$/u);
|
|
24
|
+
export const installationIdSchema = z.lazy(() => z.string().regex(/^[a-z](?:[a-z0-9]|-(?!-)){0,62}$/u));
|
|
26
25
|
/** A resource identity minted and resolved by the host, never by this module. */
|
|
27
|
-
const resourceRefSchema = z
|
|
26
|
+
export const resourceRefSchema = z.lazy(() => z
|
|
28
27
|
.strictObject({
|
|
29
28
|
kind: z.string().min(1),
|
|
30
29
|
id: z.string().min(1),
|
|
31
30
|
})
|
|
32
|
-
.readonly();
|
|
33
|
-
const operationsSchema = z
|
|
31
|
+
.readonly());
|
|
32
|
+
const operationsSchema = z.lazy(() => z
|
|
34
33
|
.array(z.string().min(1))
|
|
35
34
|
.refine((values) => new Set(values).size === values.length, "Operations must be unique")
|
|
36
|
-
.readonly();
|
|
35
|
+
.readonly());
|
|
37
36
|
/**
|
|
38
37
|
* Consent approves capabilities, with explicit operation and resource ceilings.
|
|
39
38
|
* A Release adding a method that requires an already-approved capability needs
|
|
40
39
|
* no new consent; a new capability does. Release selection intersects stored
|
|
41
40
|
* operations instead of adding approvals. Each Worker call is still restricted
|
|
42
41
|
* to that method's declared requires, never the App's complete capability set.
|
|
43
|
-
* Only an approved install or
|
|
42
|
+
* Only an approved install or change-grants command mints capabilityApproved.
|
|
44
43
|
* Empty operation rows retain that consent; removing the capability revokes it.
|
|
45
44
|
* route:app.methods rows record approval detail, not a method allowlist: narrowing
|
|
46
|
-
* them in
|
|
45
|
+
* them in change-grants does not restrict methods using an approved capability.
|
|
47
46
|
* Exact operation rows are enforced for capability routes and backend bindings.
|
|
48
47
|
*/
|
|
49
48
|
export const approvedGrantSchema = z
|
|
@@ -58,31 +57,139 @@ export const approvedGrantSchema = z
|
|
|
58
57
|
})
|
|
59
58
|
.readonly();
|
|
60
59
|
/** One grant per capability keeps operation/resource pairs unambiguous. */
|
|
61
|
-
const approvedGrantsSchema = z
|
|
60
|
+
const approvedGrantsSchema = z.lazy(() => z
|
|
62
61
|
.array(approvedGrantSchema)
|
|
63
62
|
.refine((grants) => new Set(grants.map((grant) => grant.capability)).size === grants.length, "Capabilities must be unique")
|
|
64
|
-
.readonly();
|
|
65
|
-
/**
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.array(approvedGrantSchema
|
|
63
|
+
.readonly());
|
|
64
|
+
/** A bounded owner edit. Unnamed approvals are not part of the command. */
|
|
65
|
+
export const grantChangeSchema = z
|
|
66
|
+
.strictObject({
|
|
67
|
+
capability: ownerCodec(appCapabilityIdSchema),
|
|
68
|
+
grant: approvedGrantSchema
|
|
71
69
|
.unwrap()
|
|
72
|
-
.omit({ capabilityApproved: true })
|
|
73
|
-
.readonly()
|
|
74
|
-
.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
70
|
+
.omit({ capabilityApproved: true, capability: true })
|
|
71
|
+
.readonly()
|
|
72
|
+
.nullable(),
|
|
73
|
+
})
|
|
74
|
+
.readonly();
|
|
75
|
+
const grantChangesSchema = z
|
|
76
|
+
.array(grantChangeSchema)
|
|
77
|
+
.min(1)
|
|
78
|
+
.refine((changes) => new Set(changes.map((change) => change.capability)).size ===
|
|
79
|
+
changes.length, "Capabilities must be unique")
|
|
80
|
+
.readonly();
|
|
81
|
+
const versionSchema = z.lazy(() => z.number().int().positive());
|
|
78
82
|
/** An owner's approval of a fixed operation snapshot. */
|
|
79
|
-
const approvalSchema = z
|
|
83
|
+
const approvalSchema = z.lazy(() => z
|
|
80
84
|
.strictObject({
|
|
81
85
|
by: hostActorSchema,
|
|
82
86
|
at: z.number().int().nonnegative(),
|
|
83
87
|
authorityVersion: versionSchema,
|
|
84
88
|
})
|
|
85
|
-
.readonly();
|
|
89
|
+
.readonly());
|
|
90
|
+
/** The complete grant state. Hosts own storage and compare-and-set commits. */
|
|
91
|
+
export const appAuthoritySchema = z.strictObject({
|
|
92
|
+
authorityVersion: versionSchema,
|
|
93
|
+
declaredOperationsDigest: ownerCodec(sha256DigestSchema),
|
|
94
|
+
approvedGrants: approvedGrantsSchema,
|
|
95
|
+
approval: approvalSchema.nullable(),
|
|
96
|
+
});
|
|
97
|
+
export const grantCommandSchema = z.discriminatedUnion("kind", [
|
|
98
|
+
z.strictObject({ kind: z.literal("install") }),
|
|
99
|
+
z.strictObject({
|
|
100
|
+
kind: z.literal("change-grants"),
|
|
101
|
+
changes: grantChangesSchema,
|
|
102
|
+
}),
|
|
103
|
+
z.strictObject({ kind: z.literal("select-release") }),
|
|
104
|
+
z.strictObject({ kind: z.literal("delete") }),
|
|
105
|
+
]);
|
|
106
|
+
/** One host-independent approval policy; selection only intersects existing grants. */
|
|
107
|
+
export function applyGrantCommand(current, rawCommand, context) {
|
|
108
|
+
const refuse = (code) => Result.fail({ kind: "refused", code });
|
|
109
|
+
const parsed = grantCommandSchema.safeParse(rawCommand);
|
|
110
|
+
if (!parsed.success)
|
|
111
|
+
return refuse("invalid_command");
|
|
112
|
+
const command = parsed.data;
|
|
113
|
+
if ((current === null && command.kind !== "install") ||
|
|
114
|
+
(current !== null && command.kind === "install"))
|
|
115
|
+
return refuse("invalid_command");
|
|
116
|
+
if ((context.expectedVersion ?? 0) !== (current?.authorityVersion ?? 0) ||
|
|
117
|
+
(current !== null &&
|
|
118
|
+
command.kind !== "select-release" &&
|
|
119
|
+
current.declaredOperationsDigest !== context.declaredOperations.digest))
|
|
120
|
+
return refuse("stale_authority");
|
|
121
|
+
if (context.principal.role === null ||
|
|
122
|
+
context.principal.actor.kind === "visitor" ||
|
|
123
|
+
(command.kind === "change-grants" && context.principal.role !== "owner"))
|
|
124
|
+
return refuse("not_owner");
|
|
125
|
+
const declared = (capability) => context.declaredOperations.capabilities.find((row) => row.capability === capability)?.operations ?? [];
|
|
126
|
+
const version = (current?.authorityVersion ?? 0) + 1;
|
|
127
|
+
let grants = current?.approvedGrants ?? [];
|
|
128
|
+
let approval = current?.approval ?? null;
|
|
129
|
+
switch (command.kind) {
|
|
130
|
+
case "install":
|
|
131
|
+
grants =
|
|
132
|
+
context.principal.role === "owner"
|
|
133
|
+
? context.declaration.capabilities
|
|
134
|
+
.filter((capability) => permissionCatalog[capability].consent === "install")
|
|
135
|
+
.map((capability) => ({
|
|
136
|
+
capability,
|
|
137
|
+
capabilityApproved: true,
|
|
138
|
+
operations: declared(capability),
|
|
139
|
+
resources: "tenant-scope",
|
|
140
|
+
}))
|
|
141
|
+
: [];
|
|
142
|
+
break;
|
|
143
|
+
case "change-grants": {
|
|
144
|
+
for (const { capability, grant } of command.changes) {
|
|
145
|
+
if (!context.declaration.capabilities.includes(capability) ||
|
|
146
|
+
grant?.operations.some((operation) => !declared(capability).includes(operation)))
|
|
147
|
+
return refuse("not_declared");
|
|
148
|
+
const scope = permissionCatalog[capability].scope;
|
|
149
|
+
if (grant !== null &&
|
|
150
|
+
grant.resources !== "tenant-scope" &&
|
|
151
|
+
(scope === null ||
|
|
152
|
+
grant.resources.some((resource) => resource.kind !== scope)))
|
|
153
|
+
return refuse("invalid_command");
|
|
154
|
+
}
|
|
155
|
+
const changed = new Map(command.changes.map((change) => [change.capability, change.grant]));
|
|
156
|
+
grants = [
|
|
157
|
+
...grants.filter((grant) => !changed.has(grant.capability)),
|
|
158
|
+
...command.changes.flatMap(({ capability, grant }) => grant === null ||
|
|
159
|
+
(grant.resources !== "tenant-scope" && grant.resources.length === 0)
|
|
160
|
+
? []
|
|
161
|
+
: [{ capability, ...grant, capabilityApproved: true }]),
|
|
162
|
+
];
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case "select-release":
|
|
166
|
+
grants = grants
|
|
167
|
+
.filter((grant) => context.declaration.capabilities.includes(grant.capability))
|
|
168
|
+
.map((grant) => ({
|
|
169
|
+
...grant,
|
|
170
|
+
operations: grant.operations.filter((operation) => declared(grant.capability).includes(operation)),
|
|
171
|
+
}));
|
|
172
|
+
break;
|
|
173
|
+
case "delete":
|
|
174
|
+
grants = [];
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
if (grants.length === 0)
|
|
178
|
+
approval = null;
|
|
179
|
+
else if (command.kind === "install" || command.kind === "change-grants") {
|
|
180
|
+
approval = {
|
|
181
|
+
by: context.principal.actor,
|
|
182
|
+
at: context.now,
|
|
183
|
+
authorityVersion: version,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return Result.succeed({
|
|
187
|
+
authorityVersion: version,
|
|
188
|
+
declaredOperationsDigest: context.declaredOperations.digest,
|
|
189
|
+
approvedGrants: grants,
|
|
190
|
+
approval,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
86
193
|
const anonymousMethodsSchema = z
|
|
87
194
|
.array(z
|
|
88
195
|
.strictObject({
|
|
@@ -96,7 +203,7 @@ const anonymousMethodsSchema = z
|
|
|
96
203
|
}), "Anonymous method identities must have unique, sorted names")
|
|
97
204
|
.readonly();
|
|
98
205
|
/** Immutable operation facts derived from descriptor methods and the binding registry. */
|
|
99
|
-
export const declaredOperationsSchema = z.strictObject({
|
|
206
|
+
export const declaredOperationsSchema = z.lazy(() => z.strictObject({
|
|
100
207
|
artifactDigest: ownerCodec(sha256DigestSchema),
|
|
101
208
|
digest: ownerCodec(sha256DigestSchema),
|
|
102
209
|
anonymousMethods: anonymousMethodsSchema,
|
|
@@ -108,7 +215,7 @@ export const declaredOperationsSchema = z.strictObject({
|
|
|
108
215
|
})
|
|
109
216
|
.readonly())
|
|
110
217
|
.readonly(),
|
|
111
|
-
});
|
|
218
|
+
}));
|
|
112
219
|
/** Resolved immutable Release plus host-owned installation and approval state. */
|
|
113
220
|
export const installationSchema = z
|
|
114
221
|
.strictObject({
|
|
@@ -117,12 +224,9 @@ export const installationSchema = z
|
|
|
117
224
|
appId: ownerCodec(appIdSchema),
|
|
118
225
|
release: ownerCodec(releaseSchema),
|
|
119
226
|
releasePin: z.enum(["pinned", "floating"]),
|
|
120
|
-
|
|
227
|
+
authority: appAuthoritySchema,
|
|
121
228
|
status: z.enum(["enabled", "disabled", "deleted"]),
|
|
122
229
|
visibility: z.enum(["private", "public"]),
|
|
123
|
-
declaredOperationsDigest: ownerCodec(sha256DigestSchema),
|
|
124
|
-
approvedGrants: approvedGrantsSchema,
|
|
125
|
-
approval: approvalSchema.nullable(),
|
|
126
230
|
approvedAnonymousMethods: anonymousMethodsSchema,
|
|
127
231
|
anonymousApproval: approvalSchema.nullable(),
|
|
128
232
|
})
|
|
@@ -134,18 +238,17 @@ export const installationSchema = z
|
|
|
134
238
|
: installation.status !== "deleted" &&
|
|
135
239
|
installation.approvedAnonymousMethods.length > 0 &&
|
|
136
240
|
approval.by.kind === "member" &&
|
|
137
|
-
approval.authorityVersion <= installation.authorityVersion;
|
|
241
|
+
approval.authorityVersion <= installation.authority.authorityVersion;
|
|
138
242
|
}, "Anonymous authority requires a retained human approval receipt");
|
|
139
243
|
const releaseFields = {
|
|
140
244
|
release: ownerCodec(releaseSchema),
|
|
141
245
|
releasePin: installationSchema.shape.releasePin,
|
|
142
246
|
};
|
|
143
247
|
/** Host commands; floating targets have already been resolved to immutable Releases. */
|
|
144
|
-
export const installationCommandSchema = z.discriminatedUnion("kind", [
|
|
248
|
+
export const installationCommandSchema = z.lazy(() => z.discriminatedUnion("kind", [
|
|
145
249
|
z.strictObject({
|
|
146
250
|
kind: z.literal("install"),
|
|
147
251
|
...releaseFields,
|
|
148
|
-
grants: grantRequestSchema,
|
|
149
252
|
}),
|
|
150
253
|
z.strictObject({ kind: z.literal("select-release"), ...releaseFields }),
|
|
151
254
|
z.strictObject({
|
|
@@ -156,14 +259,17 @@ export const installationCommandSchema = z.discriminatedUnion("kind", [
|
|
|
156
259
|
kind: z.literal("set-visibility"),
|
|
157
260
|
visibility: installationSchema.shape.visibility,
|
|
158
261
|
}),
|
|
159
|
-
z.strictObject({
|
|
262
|
+
z.strictObject({
|
|
263
|
+
kind: z.literal("change-grants"),
|
|
264
|
+
changes: grantChangesSchema,
|
|
265
|
+
}),
|
|
160
266
|
z.strictObject({
|
|
161
267
|
kind: z.literal("approve-anonymous"),
|
|
162
268
|
methods: anonymousMethodsSchema,
|
|
163
269
|
}),
|
|
164
|
-
]);
|
|
270
|
+
]));
|
|
165
271
|
/** Closed refusals: failed commands produce no installation update. */
|
|
166
|
-
export const installationRefusalSchema = z.strictObject({
|
|
272
|
+
export const installationRefusalSchema = z.lazy(() => z.strictObject({
|
|
167
273
|
kind: z.literal("refused"),
|
|
168
274
|
code: z.enum([
|
|
169
275
|
"unavailable",
|
|
@@ -173,7 +279,7 @@ export const installationRefusalSchema = z.strictObject({
|
|
|
173
279
|
"release_app_mismatch",
|
|
174
280
|
"invalid_command",
|
|
175
281
|
]),
|
|
176
|
-
});
|
|
282
|
+
}));
|
|
177
283
|
/** Pure transition; hosts retain transactions, receipts, epochs and delivery. */
|
|
178
284
|
export function applyInstallationCommand(current, rawCommand, context) {
|
|
179
285
|
const refuse = (code) => Result.fail({ kind: "refused", code });
|
|
@@ -190,12 +296,10 @@ export function applyInstallationCommand(current, rawCommand, context) {
|
|
|
190
296
|
return refuse("stale_authority");
|
|
191
297
|
}
|
|
192
298
|
else if (command.kind === "install" ||
|
|
193
|
-
context.expectedVersion !== current.authorityVersion) {
|
|
299
|
+
context.expectedVersion !== current.authority.authorityVersion) {
|
|
194
300
|
return refuse("stale_authority");
|
|
195
301
|
}
|
|
196
|
-
if (
|
|
197
|
-
(command.kind === "install" && command.grants !== "none")) &&
|
|
198
|
-
context.role !== "owner")
|
|
302
|
+
if (command.kind === "change-grants" && context.role !== "owner")
|
|
199
303
|
return refuse("not_owner");
|
|
200
304
|
const anonymousConsent = context.by.kind === "member" &&
|
|
201
305
|
context.anonymousConsent?.memberId === context.by.memberId;
|
|
@@ -215,14 +319,38 @@ export function applyInstallationCommand(current, rawCommand, context) {
|
|
|
215
319
|
release.artifact.artifactDigest ||
|
|
216
320
|
(current !== null &&
|
|
217
321
|
command.kind !== "select-release" &&
|
|
218
|
-
context.declaredOperations.digest !==
|
|
322
|
+
context.declaredOperations.digest !==
|
|
323
|
+
current.authority.declaredOperationsDigest))
|
|
219
324
|
return refuse("stale_authority");
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
325
|
+
const version = (current?.authority.authorityVersion ?? 0) + 1;
|
|
326
|
+
const grantCommand = command.kind === "install" || command.kind === "select-release"
|
|
327
|
+
? { kind: command.kind }
|
|
328
|
+
: command.kind === "change-grants"
|
|
329
|
+
? command
|
|
330
|
+
: command.kind === "set-status" && command.status === "deleted"
|
|
331
|
+
? { kind: "delete" }
|
|
332
|
+
: null;
|
|
333
|
+
const authority = grantCommand === null
|
|
334
|
+
? current === null
|
|
335
|
+
? Result.fail({
|
|
336
|
+
kind: "refused",
|
|
337
|
+
code: "invalid_command",
|
|
338
|
+
})
|
|
339
|
+
: Result.succeed({
|
|
340
|
+
authorityVersion: version,
|
|
341
|
+
declaredOperationsDigest: current.authority.declaredOperationsDigest,
|
|
342
|
+
approvedGrants: current.authority.approvedGrants,
|
|
343
|
+
approval: current.authority.approval,
|
|
344
|
+
})
|
|
345
|
+
: applyGrantCommand(current?.authority ?? null, grantCommand, {
|
|
346
|
+
principal: { actor: context.by, role: context.role },
|
|
347
|
+
expectedVersion: context.expectedVersion,
|
|
348
|
+
declaration: context.declaration,
|
|
349
|
+
declaredOperations: context.declaredOperations,
|
|
350
|
+
now: context.now,
|
|
351
|
+
});
|
|
352
|
+
if (Result.isFailure(authority))
|
|
353
|
+
return refuse(authority.failure.code);
|
|
226
354
|
let anonymousMethods = current?.approvedAnonymousMethods ?? [];
|
|
227
355
|
let anonymousApproval = current?.anonymousApproval ?? null;
|
|
228
356
|
if (command.kind === "approve-anonymous") {
|
|
@@ -239,44 +367,12 @@ export function applyInstallationCommand(current, rawCommand, context) {
|
|
|
239
367
|
authorityVersion: version,
|
|
240
368
|
};
|
|
241
369
|
}
|
|
242
|
-
if (command.kind === "install" || command.kind === "set-grants") {
|
|
243
|
-
grants =
|
|
244
|
-
command.grants === "none"
|
|
245
|
-
? []
|
|
246
|
-
: command.grants === "declared"
|
|
247
|
-
? context.declaredOperations.capabilities
|
|
248
|
-
.filter((row) => context.declaration.capabilities.includes(row.capability))
|
|
249
|
-
.map((row) => ({
|
|
250
|
-
...row,
|
|
251
|
-
capabilityApproved: true,
|
|
252
|
-
resources: "tenant-scope",
|
|
253
|
-
}))
|
|
254
|
-
: command.grants.map((grant) => ({
|
|
255
|
-
...grant,
|
|
256
|
-
capabilityApproved: true,
|
|
257
|
-
}));
|
|
258
|
-
if (grants.some((grant) => !context.declaration.capabilities.includes(grant.capability) ||
|
|
259
|
-
grant.operations.some((operation) => !declared(grant.capability).includes(operation))))
|
|
260
|
-
return refuse("not_declared");
|
|
261
|
-
approval =
|
|
262
|
-
command.grants === "none"
|
|
263
|
-
? null
|
|
264
|
-
: { by: context.by, at: context.now, authorityVersion: version };
|
|
265
|
-
}
|
|
266
370
|
if (command.kind === "select-release") {
|
|
267
371
|
anonymousMethods = anonymousMethods.filter((method) => context.declaredOperations.anonymousMethods.some((declared) => declared.name === method.name && declared.digest === method.digest));
|
|
268
372
|
if (anonymousMethods.length === 0)
|
|
269
373
|
anonymousApproval = null;
|
|
270
|
-
grants = grants
|
|
271
|
-
.map((grant) => ({
|
|
272
|
-
...grant,
|
|
273
|
-
operations: grant.operations.filter((operation) => declared(grant.capability).includes(operation)),
|
|
274
|
-
}))
|
|
275
|
-
.filter((grant) => context.declaration.capabilities.includes(grant.capability));
|
|
276
374
|
}
|
|
277
375
|
if (command.kind === "set-status" && command.status === "deleted") {
|
|
278
|
-
grants = [];
|
|
279
|
-
approval = null;
|
|
280
376
|
anonymousMethods = [];
|
|
281
377
|
anonymousApproval = null;
|
|
282
378
|
}
|
|
@@ -288,16 +384,13 @@ export function applyInstallationCommand(current, rawCommand, context) {
|
|
|
288
384
|
releasePin: command.kind === "install" || command.kind === "select-release"
|
|
289
385
|
? command.releasePin
|
|
290
386
|
: current?.releasePin,
|
|
291
|
-
authorityVersion: version,
|
|
292
387
|
status: command.kind === "set-status"
|
|
293
388
|
? command.status
|
|
294
389
|
: (current?.status ?? "enabled"),
|
|
295
390
|
visibility: command.kind === "set-visibility"
|
|
296
391
|
? command.visibility
|
|
297
392
|
: (current?.visibility ?? "private"),
|
|
298
|
-
|
|
299
|
-
approvedGrants: grants,
|
|
300
|
-
approval,
|
|
393
|
+
authority: authority.success,
|
|
301
394
|
approvedAnonymousMethods: anonymousMethods,
|
|
302
395
|
anonymousApproval,
|
|
303
396
|
});
|
|
@@ -305,3 +398,17 @@ export function applyInstallationCommand(current, rawCommand, context) {
|
|
|
305
398
|
? Result.succeed({ next: next.data, authorityChanged: true })
|
|
306
399
|
: refuse("invalid_command");
|
|
307
400
|
}
|
|
401
|
+
/** Only the committed installation may activate this exact owner selection. */
|
|
402
|
+
export function isCommittedInstallation(current, state, release) {
|
|
403
|
+
return (current !== null &&
|
|
404
|
+
state.owner.kind === "installation" &&
|
|
405
|
+
current.installationId === state.owner.id &&
|
|
406
|
+
current.instanceId === state.instanceId &&
|
|
407
|
+
current.appId === state.appId &&
|
|
408
|
+
current.authority.authorityVersion === state.authorityVersion &&
|
|
409
|
+
current.status === state.status &&
|
|
410
|
+
current.release.releaseId === state.releaseId &&
|
|
411
|
+
release.releaseId === state.releaseId &&
|
|
412
|
+
release.appId === state.appId &&
|
|
413
|
+
current.release.artifact.artifactDigest === release.artifact.artifactDigest);
|
|
414
|
+
}
|