@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.
Files changed (292) hide show
  1. package/README.md +376 -72
  2. package/dist/agent-avatars.d.ts +103 -0
  3. package/dist/agent-avatars.js +66 -0
  4. package/dist/agents.d.ts +15 -4
  5. package/dist/agents.js +43 -4
  6. package/dist/app-builds.d.ts +81 -8
  7. package/dist/app-builds.js +210 -60
  8. package/dist/app-storage.d.ts +3 -3
  9. package/dist/approval-capability.d.ts +5 -0
  10. package/dist/approval-capability.js +46 -0
  11. package/dist/approval-home.d.ts +51 -0
  12. package/dist/approval-home.js +226 -0
  13. package/dist/apps/catalog.d.ts +335 -0
  14. package/dist/apps/catalog.js +46 -0
  15. package/dist/apps/gate.d.ts +26 -12
  16. package/dist/apps/gate.js +48 -22
  17. package/dist/apps/index.d.ts +3 -1
  18. package/dist/apps/index.js +3 -1
  19. package/dist/apps/installation.d.ts +147 -66
  20. package/dist/apps/installation.js +193 -86
  21. package/dist/apps/permissions.d.ts +247 -0
  22. package/dist/apps/permissions.js +255 -0
  23. package/dist/boot-capability-route.d.ts +15 -0
  24. package/dist/boot-capability-route.js +196 -0
  25. package/dist/boot-capability.d.ts +103 -0
  26. package/dist/boot-capability.js +141 -0
  27. package/dist/boot-construction.d.ts +12 -0
  28. package/dist/boot-construction.js +175 -0
  29. package/dist/boot-gate.d.ts +267 -0
  30. package/dist/boot-gate.js +236 -0
  31. package/dist/boot-host-script.d.ts +2 -0
  32. package/dist/boot-host-script.js +1 -0
  33. package/dist/boot-mount.d.ts +21 -0
  34. package/dist/boot-mount.js +65 -0
  35. package/dist/browser/boot.d.ts +73 -0
  36. package/dist/browser/boot.js +502 -0
  37. package/dist/browser/browser.d.ts +104 -0
  38. package/dist/browser/browser.js +374 -0
  39. package/dist/browser/capability-source.d.ts +76 -0
  40. package/dist/browser/capability-source.js +338 -0
  41. package/dist/browser/cdp-client.d.ts +56 -0
  42. package/dist/browser/cdp-client.js +184 -0
  43. package/dist/browser/errors.d.ts +79 -0
  44. package/dist/browser/errors.js +32 -0
  45. package/dist/browser/index.d.ts +16 -0
  46. package/dist/browser/index.js +14 -0
  47. package/dist/browser/lease-registry.d.ts +61 -0
  48. package/dist/browser/lease-registry.js +101 -0
  49. package/dist/browser/operations.d.ts +225 -0
  50. package/dist/browser/operations.js +137 -0
  51. package/dist/browser/page.d.ts +25 -0
  52. package/dist/browser/page.js +172 -0
  53. package/dist/browser/quick-reads.d.ts +29 -0
  54. package/dist/browser/quick-reads.js +160 -0
  55. package/dist/browser/sessions.d.ts +52 -0
  56. package/dist/browser/sessions.js +118 -0
  57. package/dist/browser/url-policy.d.ts +18 -0
  58. package/dist/browser/url-policy.js +58 -0
  59. package/dist/build/app-imports.d.ts +12 -0
  60. package/dist/build/app-imports.js +105 -0
  61. package/dist/build/build-policy.d.ts +5 -3
  62. package/dist/build/build-policy.js +5 -3
  63. package/dist/build/candidate-builder.d.ts +1 -1
  64. package/dist/build/candidate-cli.mjs +8428 -337
  65. package/dist/build/local-build.d.ts +2 -0
  66. package/dist/build/local-build.js +28 -6
  67. package/dist/build/snapshot-package-store.d.ts +1 -1
  68. package/dist/build/snapshot-package-store.js +7 -2
  69. package/dist/build/workspace-build.d.ts +92 -5
  70. package/dist/build/workspace-build.js +93 -19
  71. package/dist/build-egress.js +4 -4
  72. package/dist/build.d.ts +1 -1
  73. package/dist/build.js +1 -1
  74. package/dist/catalog-frame.js +1 -1
  75. package/dist/cloudflare.d.ts +13 -3
  76. package/dist/cloudflare.js +106 -8
  77. package/dist/frame-binding-wire-script.js +1 -1
  78. package/dist/frame-binding-wire.d.ts +1 -1
  79. package/dist/frame-binding-wire.js +5 -3
  80. package/dist/git-auth.d.ts +0 -2
  81. package/dist/git-auth.js +0 -2
  82. package/dist/git-journal.js +4 -4
  83. package/dist/git.d.ts +0 -1
  84. package/dist/git.js +0 -2
  85. package/dist/host.d.ts +39 -13
  86. package/dist/host.js +268 -142
  87. package/dist/index.d.ts +26 -15
  88. package/dist/index.js +40 -16
  89. package/dist/install-release-cli.d.ts +2 -0
  90. package/dist/install-release-cli.js +46 -0
  91. package/dist/installation-client.d.ts +43 -0
  92. package/dist/installation-client.js +178 -0
  93. package/dist/installation-execution.d.ts +11 -0
  94. package/dist/installation-execution.js +184 -0
  95. package/dist/installation-operations.d.ts +1437 -0
  96. package/dist/installation-operations.js +664 -0
  97. package/dist/installation-store-generation.d.ts +3 -0
  98. package/dist/installation-store-generation.js +16 -0
  99. package/dist/instance-id.d.ts +10 -0
  100. package/dist/instance-id.js +10 -0
  101. package/dist/instance-refusal.d.ts +11 -0
  102. package/dist/instance-refusal.js +21 -0
  103. package/dist/internal/delivery.js +4 -2
  104. package/dist/internal/host-gate.js +7 -1
  105. package/dist/internal/registry-installations.js +4 -4
  106. package/dist/keys.d.ts +5 -1
  107. package/dist/keys.js +10 -2
  108. package/dist/machine-link/authority.d.ts +89 -0
  109. package/dist/machine-link/authority.js +37 -0
  110. package/dist/machine-link/client.d.ts +66 -0
  111. package/dist/machine-link/client.js +94 -0
  112. package/dist/machine-link/delivery.d.ts +18 -0
  113. package/dist/machine-link/delivery.js +55 -0
  114. package/dist/machine-link/enrollment.d.ts +111 -0
  115. package/dist/machine-link/enrollment.js +286 -0
  116. package/dist/machine-link/fences.d.ts +37 -0
  117. package/dist/machine-link/fences.js +166 -0
  118. package/dist/machine-link/home.d.ts +20 -0
  119. package/dist/machine-link/home.js +24 -0
  120. package/dist/machine-link/host.d.ts +43 -0
  121. package/dist/machine-link/host.js +189 -0
  122. package/dist/machine-link/index.d.ts +15 -0
  123. package/dist/machine-link/index.js +15 -0
  124. package/dist/machine-link/link.d.ts +48 -0
  125. package/dist/machine-link/link.js +223 -0
  126. package/dist/machine-link/presence.d.ts +10 -0
  127. package/dist/machine-link/presence.js +25 -0
  128. package/dist/machine-link/workers.d.ts +6 -0
  129. package/dist/machine-link/workers.js +6 -0
  130. package/dist/mcp-owner.d.ts +2 -2
  131. package/dist/mcp-owner.js +41 -33
  132. package/dist/mcp.d.ts +15 -3
  133. package/dist/mcp.js +146 -25
  134. package/dist/pack.d.ts +14 -3
  135. package/dist/pack.js +44 -19
  136. package/dist/page/app-access.d.ts +7 -0
  137. package/dist/page/app-access.js +75 -0
  138. package/dist/page/boot-host.js +57 -0
  139. package/dist/page/capabilities.d.ts +3 -3
  140. package/dist/page/capabilities.js +16 -12
  141. package/dist/page/computer-handles.d.ts +3 -3
  142. package/dist/page/computer-target.d.ts +3 -0
  143. package/dist/page/computer-target.js +9 -0
  144. package/dist/page/mount.d.ts +23 -7
  145. package/dist/page/mount.js +152 -16
  146. package/dist/page-assets.d.ts +22 -0
  147. package/dist/page-assets.js +28 -0
  148. package/dist/page-script.d.ts +3 -0
  149. package/dist/page-script.js +5 -4
  150. package/dist/release.d.ts +13 -6
  151. package/dist/release.js +10 -7
  152. package/dist/root-data.d.ts +16 -6
  153. package/dist/root-data.js +2 -2
  154. package/dist/source/index.d.ts +1 -1
  155. package/dist/source/team-source.d.ts +20 -8
  156. package/dist/source/team-source.js +39 -10
  157. package/dist/source/template-seed.d.ts +2 -2
  158. package/dist/standalone/agent-mail.d.ts +24 -0
  159. package/dist/standalone/agent-mail.js +62 -0
  160. package/dist/standalone/agent-store.d.ts +9 -0
  161. package/dist/standalone/agent-store.js +30 -0
  162. package/dist/standalone/agents.d.ts +78 -12
  163. package/dist/standalone/agents.js +470 -59
  164. package/dist/standalone/app-builds-binding.d.ts +20 -2
  165. package/dist/standalone/app-builds-binding.js +151 -27
  166. package/dist/standalone/app-set.d.ts +5 -2
  167. package/dist/standalone/app-set.js +174 -143
  168. package/dist/standalone/browser.d.ts +11 -0
  169. package/dist/standalone/browser.js +72 -0
  170. package/dist/standalone/builds.d.ts +13 -4
  171. package/dist/standalone/builds.js +28 -44
  172. package/dist/standalone/computer-egress.d.ts +2 -0
  173. package/dist/standalone/computer-egress.js +13 -4
  174. package/dist/standalone/computer-fleet.d.ts +43 -0
  175. package/dist/standalone/computer-fleet.js +200 -0
  176. package/dist/standalone/computer.d.ts +44 -9
  177. package/dist/standalone/computer.js +132 -25
  178. package/dist/standalone/install-apps-cli.js +43 -6
  179. package/dist/standalone/instance.d.ts +2 -2
  180. package/dist/standalone/instance.js +8 -11
  181. package/dist/standalone/machine-link.d.ts +354 -0
  182. package/dist/standalone/machine-link.js +368 -0
  183. package/dist/standalone/media.d.ts +2 -2
  184. package/dist/standalone/media.js +19 -16
  185. package/dist/standalone/preview.d.ts +2 -2
  186. package/dist/standalone/preview.js +39 -12
  187. package/dist/standalone/registry.d.ts +149 -9
  188. package/dist/standalone/registry.js +299 -8
  189. package/dist/standalone/worker.d.ts +19 -5
  190. package/dist/standalone/worker.js +90 -54
  191. package/dist/standalone/workflow-binding.d.ts +28 -32
  192. package/dist/standalone/workflow-binding.js +20 -20
  193. package/dist/standalone/workflow-control.d.ts +4 -0
  194. package/dist/standalone/workflow-control.js +40 -0
  195. package/dist/standalone/workflow-events.d.ts +2 -2
  196. package/dist/standalone/workflow-events.js +9 -10
  197. package/dist/standalone/workflow-git-capture.js +4 -3
  198. package/dist/standalone/workflow-host.d.ts +177 -152
  199. package/dist/standalone/workflow-host.js +137 -30
  200. package/dist/standalone/workflow-node-capture.js +11 -4
  201. package/dist/standalone/workflow-schedules.d.ts +5 -2
  202. package/dist/standalone/workflow-schedules.js +9 -10
  203. package/dist/standalone/workflow-trigger-host.d.ts +88 -6
  204. package/dist/standalone/workflow-trigger-host.js +2 -2
  205. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  206. package/dist/standalone/workflow-webhooks.js +39 -0
  207. package/dist/standalone/workflows.d.ts +203 -50
  208. package/dist/standalone/workflows.js +198 -39
  209. package/dist/storage-frame.d.ts +6 -6
  210. package/dist/testing/capabilities.js +14 -3
  211. package/dist/testing/installation.js +26 -2
  212. package/dist/testing/worker.js +1 -1
  213. package/dist/testing/workers-runtime.d.ts +8 -2
  214. package/dist/testing/workers-runtime.js +20 -2
  215. package/dist/webhook-abuse.d.ts +25 -0
  216. package/dist/webhook-abuse.js +54 -0
  217. package/dist/webhook-custody.d.ts +144 -0
  218. package/dist/webhook-custody.js +222 -0
  219. package/dist/webhook-ingress.d.ts +36 -0
  220. package/dist/webhook-ingress.js +146 -0
  221. package/dist/webhooks.d.ts +111 -0
  222. package/dist/webhooks.js +189 -0
  223. package/dist/workers-git.d.ts +12 -17
  224. package/dist/workers-git.js +60 -55
  225. package/dist/workers.d.ts +224 -13
  226. package/dist/workers.js +351 -51
  227. package/dist/workflow-agent-authority.d.ts +8 -0
  228. package/dist/workflow-agent-authority.js +42 -0
  229. package/dist/workflows.d.ts +66 -0
  230. package/dist/workflows.js +97 -0
  231. package/docs/api/README.md +63 -37
  232. package/docs/api/agents.md +17 -0
  233. package/docs/api/app-storage.md +1 -1
  234. package/docs/api/apps.md +1394 -543
  235. package/docs/api/browser/boot.md +132 -0
  236. package/docs/api/browser.md +1440 -0
  237. package/docs/api/build-contract.md +38 -25
  238. package/docs/api/build.md +263 -84
  239. package/docs/api/cloudflare.md +93 -7
  240. package/docs/api/git.md +3 -13
  241. package/docs/api/host.md +944 -118
  242. package/docs/api/index.md +320 -18
  243. package/docs/api/keys.md +24 -1
  244. package/docs/api/mcp.md +59 -12
  245. package/docs/api/modules.md +33 -40
  246. package/docs/api/pack.md +57 -8
  247. package/docs/api/page/mount.md +92 -4
  248. package/docs/api/workers-git.md +103 -24
  249. package/docs/api/workers.md +1581 -244
  250. package/docs/site/agent-quickstart.mdx +28 -14
  251. package/examples/standalone/worker.ts +3 -1
  252. package/examples/standalone/wrangler.jsonc +2 -0
  253. package/package.json +73 -69
  254. package/dist/installation-consent.d.ts +0 -79
  255. package/dist/installation-consent.js +0 -82
  256. package/dist/shell/app-directory.d.ts +0 -542
  257. package/dist/shell/app-directory.js +0 -199
  258. package/dist/shell/browser-actions.d.ts +0 -7
  259. package/dist/shell/browser-actions.js +0 -56
  260. package/dist/shell/command-host.d.ts +0 -112
  261. package/dist/shell/command-host.js +0 -502
  262. package/dist/shell/commands.d.ts +0 -25
  263. package/dist/shell/commands.js +0 -78
  264. package/dist/shell/index.d.ts +0 -8
  265. package/dist/shell/index.js +0 -8
  266. package/dist/shell/keys.d.ts +0 -41
  267. package/dist/shell/keys.js +0 -134
  268. package/dist/shell/shell-module.d.ts +0 -55
  269. package/dist/shell/shell-module.js +0 -86
  270. package/dist/shell/window-manager.d.ts +0 -124
  271. package/dist/shell/window-manager.js +0 -711
  272. package/dist/shell/workbench-contract.d.ts +0 -68
  273. package/dist/shell/workbench-directory.d.ts +0 -332
  274. package/dist/shell/workbench-directory.js +0 -35
  275. package/dist/shell/workbench-host-types.d.ts +0 -96
  276. package/dist/shell/workbench-host-types.js +0 -1
  277. package/dist/shell/workbench-host.d.ts +0 -34
  278. package/dist/shell/workbench-host.js +0 -347
  279. package/dist/shell/workbench-model.d.ts +0 -192
  280. package/dist/shell/workbench-model.js +0 -53
  281. package/dist/shell/workbench.d.ts +0 -4
  282. package/dist/shell/workbench.js +0 -2
  283. package/docs/api/shell/app-directory.md +0 -599
  284. package/docs/api/shell/command-host.md +0 -224
  285. package/docs/api/shell/commands.md +0 -197
  286. package/docs/api/shell/keys.md +0 -183
  287. package/docs/api/shell/shell-module.md +0 -204
  288. package/docs/api/shell/window-manager.md +0 -267
  289. package/docs/api/shell/workbench-directory.md +0 -119
  290. package/docs/api/shell/workbench.md +0 -205
  291. package/docs/api/shell.md +0 -1915
  292. /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { type BackgroundSyncRefusal } from "@fungi.computer/puffball/background-replay";
2
2
  import { type AppGitPushFact, type GitPushQuestion, type GitPushVerdict } from "@fungi.computer/agar/git";
3
- import { type CairnId } from "@fungi.computer/cairn/id";
4
3
  import * as Effect from "effect/Effect";
5
4
  import * as Schema from "effect/Schema";
6
5
  import type { DurableBackend } from "@fungi.computer/puffball/backend";
@@ -9,15 +8,14 @@ import type { AppMethodReply } from "@fungi.computer/agar/backend";
9
8
  import type { AppApi } from "@fungi.computer/puffball";
10
9
  import type { InvocationFrame } from "./invocation.js";
11
10
  import type { Installation } from "./apps/installation.js";
11
+ import { type UiBootCheckOutcome } from "./boot-gate.js";
12
+ /** Runtime validator for report-only installation UI-check outcomes. */
13
+ export { uiBootCheckOutcomeSchema } from "./boot-gate.js";
14
+ /** A report-only UI-check result stored with an installation operation. */
15
+ export type { UiBootCheckOutcome } from "./boot-gate.js";
12
16
  import { type GillCall, type GillReply } from "@fungi.computer/gills";
13
- /** The Cairn ID family that mints and recognizes App Instance IDs (`instance_…`). */
14
- export declare const instanceIds: Readonly<{
15
- readonly instance: import("@fungi.computer/cairn").IdCodec<"app-instance", "instance", 1>;
16
- }>;
17
- /** A stable App Instance ID minted by {@link instanceIds}. */
18
- export type InstanceId = CairnId<"app-instance", "instance">;
19
- /** Decodes a string into an {@link InstanceId}, rejecting any other ID family. */
20
- export declare const instanceIdSchema: Schema.Codec<InstanceId, string>;
17
+ export { agentAvatarProjectionSchema, agentAvatarSchema, avatarFromProjection, createAgentAvatarStore, type AgentAvatarStore, } from "./agent-avatars.js";
18
+ export { instanceIds, instanceIdSchema, type InstanceId, } from "./instance-id.js";
21
19
  /**
22
20
  * The owner that issues authority for an Instance: an `installation` of an App
23
21
  * or a `deployment`. Owner identity never changes for the life of an Instance.
@@ -31,7 +29,7 @@ export declare const ownerSchema: Schema.Struct<{
31
29
  * runs against, at a positive authority version.
32
30
  */
33
31
  export declare const selectionSchema: Schema.Struct<{
34
- readonly instanceId: Schema.Codec<InstanceId, string, never, never>;
32
+ readonly instanceId: Schema.Codec<import("./instance-id.js").InstanceId, string, never, never>;
35
33
  readonly owner: Schema.Struct<{
36
34
  readonly kind: Schema.Literals<readonly ["installation", "deployment"]>;
37
35
  readonly id: Schema.NonEmptyString;
@@ -43,7 +41,7 @@ export declare const selectionSchema: Schema.Struct<{
43
41
  /** A complete owner-issued state: a selection plus `enabled`, `disabled` or `deleted`. */
44
42
  export declare const ownerStateSchema: Schema.Struct<{
45
43
  readonly status: Schema.Literals<readonly ["enabled", "disabled", "deleted"]>;
46
- readonly instanceId: Schema.Codec<InstanceId, string, never, never>;
44
+ readonly instanceId: Schema.Codec<import("./instance-id.js").InstanceId, string, never, never>;
47
45
  readonly owner: Schema.Struct<{
48
46
  readonly kind: Schema.Literals<readonly ["installation", "deployment"]>;
49
47
  readonly id: Schema.NonEmptyString;
@@ -62,7 +60,7 @@ export declare const instanceSchema: Schema.Struct<{
62
60
  readonly stateSchema: Schema.NullOr<Schema.Number>;
63
61
  readonly desiredStatus: Schema.Literals<readonly ["enabled", "disabled", "deleted"]>;
64
62
  readonly status: Schema.Literals<readonly ["active", "disabled", "deleting", "deleted"]>;
65
- readonly instanceId: Schema.Codec<InstanceId, string, never, never>;
63
+ readonly instanceId: Schema.Codec<import("./instance-id.js").InstanceId, string, never, never>;
66
64
  readonly owner: Schema.Struct<{
67
65
  readonly kind: Schema.Literals<readonly ["installation", "deployment"]>;
68
66
  readonly id: Schema.NonEmptyString;
@@ -84,7 +82,7 @@ export type Backend = Readonly<{
84
82
  backend: DurableBackend;
85
83
  }>;
86
84
  /** The runtime owns the bounded rejection grammar, including across RPC. */
87
- export declare const appInstanceErrorCodeSchema: Schema.Literals<readonly ["invalid_owner_state", "invalid_invocation_facts", "revoked", "binding_not_granted", "binding_unavailable", "expired", "reentrant", "instance_identity_mismatch", "instance_owner_mismatch", "conflicting_owner_state", "stale_authority", "instance_deleted", "release_app_mismatch", "state_schema_migration_unsupported", "instance_inactive", "instance_authority_version_mismatch", "release_no_longer_matches_instance", "instance_release_mismatch", "installation_tenant_mismatch"]>;
85
+ export declare const appInstanceErrorCodeSchema: Schema.Literals<readonly ["invalid_owner_state", "invalid_invocation_facts", "revoked", "disabled", "binding_not_granted", "binding_unavailable", "expired", "reentrant", "instance_identity_mismatch", "instance_owner_mismatch", "conflicting_owner_state", "stale_authority", "instance_deleted", "release_app_mismatch", "state_schema_migration_unsupported", "instance_inactive", "instance_authority_version_mismatch", "release_no_longer_matches_instance", "instance_release_mismatch", "installation_tenant_mismatch", ...("backend_load_failed" | "backend_construction_failed" | "ui_mount_failed" | "boot_runner_unavailable" | "boot_browser_binding_missing" | "boot_clone_unavailable" | "boot_timeout" | "boot_cleanup_failed")[]]>;
88
86
  /** One of the bounded {@link appInstanceErrorCodeSchema} rejection codes. */
89
87
  export type AppInstanceErrorCode = typeof appInstanceErrorCodeSchema.Type;
90
88
  /** Stable runtime failure identity; message remains presentation only. */
@@ -110,8 +108,19 @@ export type InstancePorts<Host> = Readonly<{
110
108
  write: (instance: Instance) => Effect.Effect<void, Error>;
111
109
  /** Resolve a Release ID to its verified durable backend. */
112
110
  resolve: (release: Selection["releaseId"], scope: BackendResolution<Host>) => Effect.Effect<Backend, Error>;
113
- /** Abort in-flight backend work and invalidate existing connections. */
114
- abort: () => Effect.Effect<void, Error>;
111
+ /**
112
+ * Host-owned backend construction gate plus UI-check report. Backend
113
+ * construction errors block selection; the returned UI outcome is advisory.
114
+ * `null` means this executor deliberately did not run a UI check. The prior
115
+ * schema comes from this operation's persisted read, not the candidate.
116
+ * Prepare asks for the UI check (`"run"`); apply proves construction again
117
+ * but `"reuse"`s prepare's recorded result and never opens a browser.
118
+ * UI artifacts are verified during preparation, not re-read on apply-only
119
+ * lifecycle changes; Releases and their artifacts are content-addressed.
120
+ */
121
+ boot: (backend: Backend, priorStateSchema: Instance["stateSchema"], uiCheck: "run" | "reuse") => Effect.Effect<UiBootCheckOutcome | null, Error>;
122
+ /** Abort in-flight work after fencing its authority and invalidate connections. */
123
+ abort: (reason: AppInstanceError) => Effect.Effect<void, Error>;
115
124
  /** Delete the backend's own state. */
116
125
  purge: () => Effect.Effect<void, Error>;
117
126
  /**
@@ -143,6 +152,8 @@ export type AppInstance<Host> = Readonly<{
143
152
  * changed input at the same version conflicts and older versions are stale.
144
153
  */
145
154
  apply: (input: OwnerState) => Effect.Effect<Instance, Error>;
155
+ /** Run the same lifecycle and boot checks without writing, aborting or purging. */
156
+ prepare: (input: OwnerState) => Effect.Effect<UiBootCheckOutcome | null, Error>;
146
157
  /** Admit the frame's selection and forward an HTTP request to the active backend. */
147
158
  fetch: (frame: InvocationFrame<Host>, request: Request) => Effect.Effect<Response, Error>;
148
159
  /** Admit the frame and invoke a method declared by the Release's App API. */
package/dist/index.js CHANGED
@@ -1,21 +1,18 @@
1
1
  import { backgroundSyncRefusalSchema, } from "@fungi.computer/puffball/background-replay";
2
2
  import { appGitPushFactSchema, gitPushQuestionSchema, gitPushVerdictSchema, } from "@fungi.computer/agar/git";
3
3
  import * as Semaphore from "effect/Semaphore";
4
- import { defineIdFamily } from "@fungi.computer/cairn/id";
4
+ import { instanceIdSchema } from "./instance-id.js";
5
5
  import * as Effect from "effect/Effect";
6
6
  import * as Schema from "effect/Schema";
7
7
  import { releaseIdSchema, appIdSchema } from "@fungi.computer/puffball/effect";
8
8
  import { appFacetRequest, appFacetResponse } from "./backend-request.js";
9
9
  import { appApiSchema } from "@fungi.computer/puffball/app";
10
+ import { bootFailureSchema, BootGateError, } from "./boot-gate.js";
11
+ /** Runtime validator for report-only installation UI-check outcomes. */
12
+ export { uiBootCheckOutcomeSchema } from "./boot-gate.js";
10
13
  import { gillCallSchema, gillReplySchema, } from "@fungi.computer/gills";
11
- /** The Cairn ID family that mints and recognizes App Instance IDs (`instance_…`). */
12
- export const instanceIds = defineIdFamily({
13
- family: "app-instance",
14
- version: 1,
15
- kinds: { instance: { prefix: "instance" } },
16
- });
17
- /** Decodes a string into an {@link InstanceId}, rejecting any other ID family. */
18
- export const instanceIdSchema = Schema.String.pipe(Schema.refine((value) => instanceIds.instance.is(value)));
14
+ export { agentAvatarProjectionSchema, agentAvatarSchema, avatarFromProjection, createAgentAvatarStore, } from "./agent-avatars.js";
15
+ export { instanceIds, instanceIdSchema, } from "./instance-id.js";
19
16
  /**
20
17
  * The owner that issues authority for an Instance: an `installation` of an App
21
18
  * or a `deployment`. Owner identity never changes for the life of an Instance.
@@ -59,7 +56,7 @@ export function selectionOf(installation) {
59
56
  owner: { kind: "installation", id: installation.installationId },
60
57
  appId: installation.release.appId,
61
58
  releaseId: installation.release.releaseId,
62
- authorityVersion: installation.authorityVersion,
59
+ authorityVersion: installation.authority.authorityVersion,
63
60
  };
64
61
  }
65
62
  /** The runtime owns the bounded rejection grammar, including across RPC. */
@@ -67,6 +64,7 @@ export const appInstanceErrorCodeSchema = Schema.Literals([
67
64
  "invalid_owner_state",
68
65
  "invalid_invocation_facts",
69
66
  "revoked",
67
+ "disabled",
70
68
  "binding_not_granted",
71
69
  "binding_unavailable",
72
70
  "expired",
@@ -83,11 +81,22 @@ export const appInstanceErrorCodeSchema = Schema.Literals([
83
81
  "release_no_longer_matches_instance",
84
82
  "instance_release_mismatch",
85
83
  "installation_tenant_mismatch",
84
+ ...bootFailureSchema.options,
86
85
  ]);
87
86
  const APP_INSTANCE_ERROR_MESSAGES = {
87
+ backend_load_failed: new BootGateError("backend_load_failed").message,
88
+ backend_construction_failed: new BootGateError("backend_construction_failed")
89
+ .message,
90
+ ui_mount_failed: new BootGateError("ui_mount_failed").message,
91
+ boot_runner_unavailable: new BootGateError("boot_runner_unavailable").message,
92
+ boot_browser_binding_missing: new BootGateError("boot_browser_binding_missing").message,
93
+ boot_clone_unavailable: new BootGateError("boot_clone_unavailable").message,
94
+ boot_timeout: new BootGateError("boot_timeout").message,
95
+ boot_cleanup_failed: new BootGateError("boot_cleanup_failed").message,
88
96
  invalid_owner_state: "Invalid owner state",
89
97
  invalid_invocation_facts: "Invalid invocation facts",
90
98
  revoked: "Invocation authority revoked",
99
+ disabled: "App disabled",
91
100
  binding_not_granted: "Binding operation not granted",
92
101
  binding_unavailable: "Binding operation unavailable on this host",
93
102
  expired: "Invocation deadline exceeded",
@@ -136,11 +145,11 @@ export function createAppInstance(ports) {
136
145
  }
137
146
  return current;
138
147
  });
139
- const apply = (input) => lock.withPermits(1)(Effect.gen(function* () {
148
+ const preflight = (input, readCurrent, uiCheck) => Effect.gen(function* () {
140
149
  const desired = yield* Schema.decodeUnknownEffect(ownerStateSchema)(input).pipe(Effect.mapError(() => new AppInstanceError("invalid_owner_state")));
141
150
  if (desired.instanceId !== ports.instanceId)
142
151
  return yield* failure("instance_identity_mismatch");
143
- const current = yield* read;
152
+ const current = yield* readCurrent;
144
153
  if (current && !sameOwner(current, desired))
145
154
  return yield* failure("instance_owner_mismatch");
146
155
  const retry = current !== null &&
@@ -151,14 +160,15 @@ export function createAppInstance(ports) {
151
160
  return yield* failure("conflicting_owner_state");
152
161
  if (current && desired.authorityVersion < current.authorityVersion)
153
162
  return yield* failure("stale_authority");
154
- if (current?.status === "deleted") {
163
+ if (current?.status === "deleted" || current?.status === "deleting") {
155
164
  if (retry && desired.status === "deleted")
156
- return current;
165
+ return { kind: "unchanged", receipt: current };
157
166
  return yield* failure("instance_deleted");
158
167
  }
159
168
  if (retry && current.status === "active")
160
- return current;
169
+ return { kind: "unchanged", receipt: current };
161
170
  let stateSchema = current?.stateSchema ?? null;
171
+ let bootCheck = null;
162
172
  if (desired.status === "enabled") {
163
173
  const resolved = yield* ports.resolve(desired.releaseId, {
164
174
  kind: "metadata",
@@ -169,7 +179,20 @@ export function createAppInstance(ports) {
169
179
  stateSchema !== resolved.backend.runtime.stateSchema)
170
180
  return yield* failure("state_schema_migration_unsupported");
171
181
  stateSchema = resolved.backend.runtime.stateSchema;
182
+ bootCheck = yield* ports.boot(resolved, current?.stateSchema ?? null, uiCheck);
172
183
  }
184
+ return { kind: "apply", desired, stateSchema, bootCheck };
185
+ });
186
+ const prepare = (input) => lock.withPermits(1)(Effect.gen(function* () {
187
+ // The one UI check for this selection; apply reuses its result.
188
+ const transition = yield* preflight(input, ports.read(), "run");
189
+ return transition.kind === "apply" ? transition.bootCheck : null;
190
+ }));
191
+ const apply = (input) => lock.withPermits(1)(Effect.gen(function* () {
192
+ const transition = yield* preflight(input, read, "reuse");
193
+ if (transition.kind === "unchanged")
194
+ return transition.receipt;
195
+ const { desired, stateSchema } = transition;
173
196
  const next = {
174
197
  ...desired,
175
198
  desiredStatus: desired.status,
@@ -180,7 +203,7 @@ export function createAppInstance(ports) {
180
203
  };
181
204
  // Persist the exact owner fact before stopping runtime; no resource-issued version.
182
205
  yield* ports.write(next);
183
- yield* ports.abort();
206
+ yield* ports.abort(new AppInstanceError(desired.status === "disabled" ? "disabled" : "revoked"));
184
207
  if (desired.status === "deleted")
185
208
  yield* ports.purge();
186
209
  const receipt = {
@@ -309,6 +332,7 @@ export function createAppInstance(ports) {
309
332
  }));
310
333
  return {
311
334
  syncRefused,
335
+ prepare,
312
336
  apply,
313
337
  fetch,
314
338
  call,
@@ -0,0 +1,2 @@
1
+ export declare const installReleaseUsage = "Usage: oyster install <directory|release.json> [--installation-id <name>]";
2
+ export declare function installReleaseCommand(args: readonly string[]): Promise<void>;
@@ -0,0 +1,46 @@
1
+ /** Local Release installation uses the ordinary pack uploader and owner API. */
2
+ import { stat } from "node:fs/promises";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ import process from "node:process";
5
+ import { parseArgs } from "node:util";
6
+ import { installRelease } from "./pack.js";
7
+ export const installReleaseUsage = "Usage: oyster install <directory|release.json> [--installation-id <name>]";
8
+ export async function installReleaseCommand(args) {
9
+ const { values, positionals } = parseArgs({
10
+ args: [...args],
11
+ allowPositionals: true,
12
+ strict: true,
13
+ options: {
14
+ "installation-id": { type: "string" },
15
+ help: { type: "boolean", short: "h" },
16
+ },
17
+ });
18
+ if (values.help) {
19
+ process.stdout.write(installReleaseUsage +
20
+ "\nUpload a built Release and install it using OYSTER_URL and OYSTER_OWNER_KEY.\n");
21
+ return;
22
+ }
23
+ const [source] = positionals;
24
+ if (positionals.length !== 1 || source === undefined)
25
+ throw new Error(installReleaseUsage);
26
+ const url = process.env.OYSTER_URL;
27
+ const key = process.env.OYSTER_OWNER_KEY;
28
+ if (!url || !key)
29
+ throw new Error("Set OYSTER_URL and OYSTER_OWNER_KEY for the target Oyster. " +
30
+ installReleaseUsage);
31
+ const path = resolve(source);
32
+ const info = await stat(path);
33
+ if (!info.isDirectory() &&
34
+ (!info.isFile() || basename(path) !== "release.json"))
35
+ throw new Error("Expected a Release directory or release.json");
36
+ const response = await installRelease({
37
+ directory: info.isDirectory() ? path : dirname(path),
38
+ url,
39
+ key,
40
+ installationId: values["installation-id"],
41
+ });
42
+ const body = await response.text();
43
+ if (!response.ok)
44
+ throw new Error("Installation failed (" + String(response.status) + "): " + body);
45
+ process.stdout.write(body + "\n");
46
+ }
@@ -0,0 +1,43 @@
1
+ import type { Release } from "@fungi.computer/puffball";
2
+ import { type InstallationOperation } from "./installation-operations.js";
3
+ export type InstallationCompletion = Exclude<InstallationOperation, {
4
+ state: "pending";
5
+ }>;
6
+ export type InstallationObservationBudget = Readonly<{
7
+ /** Upload/observation time; expiry never cancels admitted durable work. */
8
+ waitMs?: number;
9
+ /** Caller-owned budget; used unchanged unless waitMs is also explicit. */
10
+ signal?: AbortSignal;
11
+ }>;
12
+ /** Share one budget across pack uploads and the subsequent receipt observation. */
13
+ export declare function installationObservationSignal(input: InstallationObservationBudget): AbortSignal;
14
+ /** Observer failure supplies no installation outcome and never cancels its owner. */
15
+ export declare class InstallationObservationError extends Error {
16
+ readonly requestId: string;
17
+ constructor(requestId: string, options?: ErrorOptions);
18
+ }
19
+ /** Observe one retained command, over either HTTP or the host's decoded port. */
20
+ export declare function observeInstallation(input: Readonly<{
21
+ requestId: string;
22
+ initial: InstallationOperation | null;
23
+ read(): Promise<InstallationOperation | null>;
24
+ signal: AbortSignal;
25
+ pollMs?: number;
26
+ idle?: (ms: number, signal: AbortSignal) => Promise<void>;
27
+ }>): Promise<InstallationCompletion>;
28
+ /** Completed HTTP meaning shared by pack and the retained PUT selection door. */
29
+ export declare function completedInstallationResponse(operation: InstallationCompletion): Response;
30
+ /**
31
+ * Submit once and observe the correlated owner receipt. Both CLI paths use
32
+ * exactly this contract; no directory reads or old completed-POST branch.
33
+ */
34
+ export declare function installOnOyster(input: Readonly<{
35
+ url: string;
36
+ key: string;
37
+ installationId: string;
38
+ release: Release;
39
+ pollMs?: number;
40
+ idle?: (ms: number, signal: AbortSignal) => Promise<void>;
41
+ /** Once, after the owner supplies a correlated record, even after a lost ack. */
42
+ onAdmitted?: (operation: InstallationOperation) => void;
43
+ }> & InstallationObservationBudget): Promise<InstallationCompletion>;
@@ -0,0 +1,178 @@
1
+ import { setTimeout as sleep } from "node:timers/promises";
2
+ import { addAbortListener } from "node:events";
3
+ import * as Schema from "effect/Schema";
4
+ import { releaseSchema } from "@fungi.computer/puffball/effect";
5
+ import { instanceRefusalBody } from "./instance-refusal.js";
6
+ import { installationOperationSchema, installationCommandIds, } from "./installation-operations.js";
7
+ /**
8
+ * Installer-owned observer budget, matching the standalone installer's existing
9
+ * twenty-minute default. Not an App grant lifetime or a server execution limit.
10
+ */
11
+ const DEFAULT_INSTALLATION_WAIT_MS = 20 * 60_000;
12
+ /** Share one budget across pack uploads and the subsequent receipt observation. */
13
+ export function installationObservationSignal(input) {
14
+ if (input.signal !== undefined && input.waitMs === undefined)
15
+ return input.signal;
16
+ const timer = AbortSignal.timeout(input.waitMs ?? DEFAULT_INSTALLATION_WAIT_MS);
17
+ return input.signal === undefined
18
+ ? timer
19
+ : AbortSignal.any([input.signal, timer]);
20
+ }
21
+ /** Observer failure supplies no installation outcome and never cancels its owner. */
22
+ export class InstallationObservationError extends Error {
23
+ requestId;
24
+ constructor(requestId, options) {
25
+ super(`Installation outcome unconfirmed (${requestId})`, options);
26
+ this.requestId = requestId;
27
+ }
28
+ }
29
+ const idle = (ms, signal) => sleep(ms, undefined, { signal });
30
+ /** Observe one retained command, over either HTTP or the host's decoded port. */
31
+ export async function observeInstallation(input) {
32
+ try {
33
+ input.signal.throwIfAborted();
34
+ let operation = input.initial;
35
+ while (operation === null || operation.state === "pending") {
36
+ input.signal.throwIfAborted();
37
+ // The decoded RPC port has no cancellation argument. Stop THIS wait
38
+ // without cancelling the retained command or abandoning its late reply.
39
+ let listener;
40
+ try {
41
+ operation = await new Promise((resolve, reject) => {
42
+ listener = addAbortListener(input.signal, () => {
43
+ reject(new InstallationObservationError(input.requestId, {
44
+ cause: input.signal.reason,
45
+ }));
46
+ });
47
+ // Both late outcomes remain handled after the observer has left.
48
+ Promise.resolve()
49
+ .then(() => input.read())
50
+ .then(resolve, reject);
51
+ });
52
+ }
53
+ finally {
54
+ listener?.[Symbol.dispose]();
55
+ }
56
+ input.signal.throwIfAborted();
57
+ if (operation === null || operation.state === "pending")
58
+ await (input.idle ?? idle)(input.pollMs ?? 5_000, input.signal);
59
+ }
60
+ return operation;
61
+ }
62
+ catch (cause) {
63
+ if (cause instanceof InstallationObservationError)
64
+ throw cause;
65
+ throw new InstallationObservationError(input.requestId, { cause });
66
+ }
67
+ }
68
+ /** Completed HTTP meaning shared by pack and the retained PUT selection door. */
69
+ export function completedInstallationResponse(operation) {
70
+ if (operation.state === "unconfirmed")
71
+ throw new InstallationObservationError(operation.requestId);
72
+ if (operation.state === "refused")
73
+ return Response.json(instanceRefusalBody(operation.code), {
74
+ status: operation.status,
75
+ });
76
+ if (operation.installation === null)
77
+ return Response.json({
78
+ error: "installation_result_unavailable",
79
+ requestId: operation.requestId,
80
+ reference: operation.reference,
81
+ }, { status: 409 });
82
+ return Response.json(operation.installation, {
83
+ status: operation.creation === "replacement" ? 200 : 201,
84
+ });
85
+ }
86
+ /**
87
+ * Submit once and observe the correlated owner receipt. Both CLI paths use
88
+ * exactly this contract; no directory reads or old completed-POST branch.
89
+ */
90
+ export async function installOnOyster(input) {
91
+ const requestId = installationCommandIds.request.generate();
92
+ const signal = installationObservationSignal(input);
93
+ const headers = {
94
+ Authorization: `Bearer ${input.key}`,
95
+ Accept: "application/json",
96
+ };
97
+ let admitted = false;
98
+ const decode = async (response) => {
99
+ const operation = installationOperationSchema.parse(await response.json());
100
+ if (operation.requestId !== requestId ||
101
+ operation.requestedInstallationId !== input.installationId ||
102
+ operation.release.appId !== input.release.appId ||
103
+ operation.release.releaseId !== input.release.releaseId ||
104
+ operation.release.artifact.artifactDigest !==
105
+ input.release.artifact.artifactDigest ||
106
+ (operation.state === "succeeded" &&
107
+ operation.installation !== null &&
108
+ (operation.installation.installationId !== operation.installationId ||
109
+ operation.installation.appId !== input.release.appId ||
110
+ operation.installation.release.releaseId !==
111
+ input.release.releaseId ||
112
+ operation.installation.release.artifact.artifactDigest !==
113
+ input.release.artifact.artifactDigest)))
114
+ throw new InstallationObservationError(requestId);
115
+ if (!admitted) {
116
+ admitted = true;
117
+ input.onAdmitted?.(operation);
118
+ }
119
+ return operation;
120
+ };
121
+ try {
122
+ signal.throwIfAborted();
123
+ let operation = null;
124
+ // Catch only the transport, not a malformed authoritative acknowledgement.
125
+ let submitted;
126
+ try {
127
+ submitted = await fetch(new URL("/api/installations", input.url), {
128
+ method: "POST",
129
+ signal,
130
+ headers: {
131
+ ...headers,
132
+ "Content-Type": "application/json",
133
+ "Idempotency-Key": requestId,
134
+ },
135
+ body: JSON.stringify({
136
+ installationId: input.installationId,
137
+ release: Schema.encodeUnknownSync(releaseSchema)(input.release),
138
+ }),
139
+ });
140
+ }
141
+ catch {
142
+ signal.throwIfAborted();
143
+ submitted = null;
144
+ // Missing acknowledgement is not permission to resubmit.
145
+ }
146
+ if (submitted?.status === 202)
147
+ operation = await decode(submitted);
148
+ else if (submitted !== null && submitted.status < 500)
149
+ throw new InstallationObservationError(requestId);
150
+ const statusUrl = new URL(`/api/installations/${encodeURIComponent(input.installationId)}/status`, input.url);
151
+ statusUrl.searchParams.set("requestId", requestId);
152
+ let observation = {
153
+ requestId,
154
+ initial: operation,
155
+ signal,
156
+ read: async () => {
157
+ // Like decoded RPC reads, transport errors reach the observer with
158
+ // their original cause; they are not an authoritative pending read.
159
+ const observed = await fetch(statusUrl, { headers, signal });
160
+ if (observed.status === 200)
161
+ return decode(observed);
162
+ if (observed.status !== 404 && observed.status < 500)
163
+ throw new InstallationObservationError(requestId);
164
+ return null;
165
+ },
166
+ };
167
+ if (input.pollMs !== undefined)
168
+ observation = { ...observation, pollMs: input.pollMs };
169
+ if (input.idle !== undefined)
170
+ observation = { ...observation, idle: input.idle };
171
+ return await observeInstallation(observation);
172
+ }
173
+ catch (cause) {
174
+ if (cause instanceof InstallationObservationError)
175
+ throw cause;
176
+ throw new InstallationObservationError(requestId, { cause });
177
+ }
178
+ }
@@ -0,0 +1,11 @@
1
+ import { WorkerEntrypoint } from "cloudflare:workers";
2
+ import { type TenantScope } from "./tenant.js";
3
+ import { type RetainedInstallationCommand, type InstallationOutcome } from "./installation-operations.js";
4
+ import type { OysterHost } from "./host.js";
5
+ /** Ordinary Worker-side verification and lifecycle I/O for one retained intent. */
6
+ export declare function executeInstallation(host: OysterHost, input: RetainedInstallationCommand): Promise<InstallationOutcome>;
7
+ /** A loopback export with ordinary Worker I/O, not another resource or HTTP API. */
8
+ export declare function installationWorkerEntrypoint<Env>(hostFor: (env: Env, scope: TenantScope) => OysterHost | Promise<OysterHost>): new (ctx: ExecutionContext, env: Env) => WorkerEntrypoint<Env, unknown> & {
9
+ /** Execute the retained command these props name, in its tenant scope. */
10
+ run(): Promise<InstallationOutcome>;
11
+ };