@fungi.computer/oyster 0.2.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 (287) hide show
  1. package/README.md +376 -72
  2. package/dist/agents.d.ts +3 -2
  3. package/dist/agents.js +4 -2
  4. package/dist/app-builds.d.ts +81 -8
  5. package/dist/app-builds.js +210 -60
  6. package/dist/app-storage.d.ts +3 -3
  7. package/dist/approval-capability.d.ts +5 -0
  8. package/dist/approval-capability.js +46 -0
  9. package/dist/approval-home.d.ts +51 -0
  10. package/dist/approval-home.js +226 -0
  11. package/dist/apps/catalog.d.ts +335 -0
  12. package/dist/apps/catalog.js +46 -0
  13. package/dist/apps/gate.d.ts +20 -6
  14. package/dist/apps/gate.js +42 -16
  15. package/dist/apps/index.d.ts +3 -1
  16. package/dist/apps/index.js +3 -1
  17. package/dist/apps/installation.d.ts +132 -51
  18. package/dist/apps/installation.js +176 -70
  19. package/dist/apps/permissions.d.ts +247 -0
  20. package/dist/apps/permissions.js +255 -0
  21. package/dist/boot-capability-route.d.ts +15 -0
  22. package/dist/boot-capability-route.js +196 -0
  23. package/dist/boot-capability.d.ts +103 -0
  24. package/dist/boot-capability.js +141 -0
  25. package/dist/boot-construction.d.ts +12 -0
  26. package/dist/boot-construction.js +175 -0
  27. package/dist/boot-gate.d.ts +267 -0
  28. package/dist/boot-gate.js +236 -0
  29. package/dist/boot-host-script.d.ts +2 -0
  30. package/dist/boot-host-script.js +1 -0
  31. package/dist/boot-mount.d.ts +21 -0
  32. package/dist/boot-mount.js +65 -0
  33. package/dist/browser/boot.d.ts +73 -0
  34. package/dist/browser/boot.js +502 -0
  35. package/dist/browser/browser.d.ts +104 -0
  36. package/dist/browser/browser.js +374 -0
  37. package/dist/browser/capability-source.d.ts +76 -0
  38. package/dist/browser/capability-source.js +338 -0
  39. package/dist/browser/cdp-client.d.ts +56 -0
  40. package/dist/browser/cdp-client.js +184 -0
  41. package/dist/browser/errors.d.ts +79 -0
  42. package/dist/browser/errors.js +32 -0
  43. package/dist/browser/index.d.ts +16 -0
  44. package/dist/browser/index.js +14 -0
  45. package/dist/browser/lease-registry.d.ts +61 -0
  46. package/dist/browser/lease-registry.js +101 -0
  47. package/dist/browser/operations.d.ts +225 -0
  48. package/dist/browser/operations.js +137 -0
  49. package/dist/browser/page.d.ts +25 -0
  50. package/dist/browser/page.js +172 -0
  51. package/dist/browser/quick-reads.d.ts +29 -0
  52. package/dist/browser/quick-reads.js +160 -0
  53. package/dist/browser/sessions.d.ts +52 -0
  54. package/dist/browser/sessions.js +118 -0
  55. package/dist/browser/url-policy.d.ts +18 -0
  56. package/dist/browser/url-policy.js +58 -0
  57. package/dist/build/app-imports.d.ts +12 -0
  58. package/dist/build/app-imports.js +105 -0
  59. package/dist/build/build-policy.d.ts +5 -3
  60. package/dist/build/build-policy.js +5 -3
  61. package/dist/build/candidate-builder.d.ts +1 -1
  62. package/dist/build/candidate-cli.mjs +8375 -334
  63. package/dist/build/local-build.d.ts +2 -0
  64. package/dist/build/local-build.js +28 -6
  65. package/dist/build/snapshot-package-store.d.ts +1 -1
  66. package/dist/build/snapshot-package-store.js +7 -2
  67. package/dist/build/workspace-build.d.ts +92 -5
  68. package/dist/build/workspace-build.js +93 -19
  69. package/dist/build.d.ts +1 -1
  70. package/dist/build.js +1 -1
  71. package/dist/catalog-frame.js +1 -1
  72. package/dist/cloudflare.d.ts +13 -3
  73. package/dist/cloudflare.js +102 -4
  74. package/dist/frame-binding-wire-script.js +1 -1
  75. package/dist/frame-binding-wire.js +4 -2
  76. package/dist/git-auth.d.ts +0 -2
  77. package/dist/git-auth.js +0 -2
  78. package/dist/git.d.ts +0 -1
  79. package/dist/git.js +0 -2
  80. package/dist/host.d.ts +39 -13
  81. package/dist/host.js +268 -142
  82. package/dist/index.d.ts +25 -15
  83. package/dist/index.js +39 -16
  84. package/dist/install-release-cli.d.ts +2 -0
  85. package/dist/install-release-cli.js +46 -0
  86. package/dist/installation-client.d.ts +43 -0
  87. package/dist/installation-client.js +178 -0
  88. package/dist/installation-execution.d.ts +11 -0
  89. package/dist/installation-execution.js +184 -0
  90. package/dist/installation-operations.d.ts +1437 -0
  91. package/dist/installation-operations.js +664 -0
  92. package/dist/installation-store-generation.d.ts +3 -0
  93. package/dist/installation-store-generation.js +16 -0
  94. package/dist/instance-id.d.ts +10 -0
  95. package/dist/instance-id.js +10 -0
  96. package/dist/instance-refusal.d.ts +11 -0
  97. package/dist/instance-refusal.js +21 -0
  98. package/dist/internal/delivery.js +4 -2
  99. package/dist/internal/host-gate.js +7 -1
  100. package/dist/internal/registry-installations.js +4 -4
  101. package/dist/keys.d.ts +5 -1
  102. package/dist/keys.js +10 -2
  103. package/dist/machine-link/authority.d.ts +89 -0
  104. package/dist/machine-link/authority.js +37 -0
  105. package/dist/machine-link/client.d.ts +66 -0
  106. package/dist/machine-link/client.js +94 -0
  107. package/dist/machine-link/delivery.d.ts +18 -0
  108. package/dist/machine-link/delivery.js +55 -0
  109. package/dist/machine-link/enrollment.d.ts +111 -0
  110. package/dist/machine-link/enrollment.js +286 -0
  111. package/dist/machine-link/fences.d.ts +37 -0
  112. package/dist/machine-link/fences.js +166 -0
  113. package/dist/machine-link/home.d.ts +20 -0
  114. package/dist/machine-link/home.js +24 -0
  115. package/dist/machine-link/host.d.ts +43 -0
  116. package/dist/machine-link/host.js +189 -0
  117. package/dist/machine-link/index.d.ts +15 -0
  118. package/dist/machine-link/index.js +15 -0
  119. package/dist/machine-link/link.d.ts +48 -0
  120. package/dist/machine-link/link.js +223 -0
  121. package/dist/machine-link/presence.d.ts +10 -0
  122. package/dist/machine-link/presence.js +25 -0
  123. package/dist/machine-link/workers.d.ts +6 -0
  124. package/dist/machine-link/workers.js +6 -0
  125. package/dist/mcp-owner.d.ts +2 -2
  126. package/dist/mcp-owner.js +41 -33
  127. package/dist/mcp.d.ts +15 -3
  128. package/dist/mcp.js +146 -25
  129. package/dist/pack.d.ts +14 -3
  130. package/dist/pack.js +44 -19
  131. package/dist/page/app-access.d.ts +7 -0
  132. package/dist/page/app-access.js +75 -0
  133. package/dist/page/boot-host.js +57 -0
  134. package/dist/page/capabilities.d.ts +3 -3
  135. package/dist/page/capabilities.js +16 -12
  136. package/dist/page/computer-handles.d.ts +3 -3
  137. package/dist/page/computer-target.d.ts +3 -0
  138. package/dist/page/computer-target.js +9 -0
  139. package/dist/page/mount.d.ts +23 -3
  140. package/dist/page/mount.js +152 -16
  141. package/dist/page-assets.d.ts +22 -0
  142. package/dist/page-assets.js +28 -0
  143. package/dist/page-script.d.ts +3 -0
  144. package/dist/page-script.js +5 -4
  145. package/dist/release.d.ts +13 -6
  146. package/dist/release.js +10 -7
  147. package/dist/root-data.d.ts +16 -6
  148. package/dist/root-data.js +2 -2
  149. package/dist/source/index.d.ts +1 -1
  150. package/dist/source/team-source.d.ts +20 -8
  151. package/dist/source/team-source.js +39 -10
  152. package/dist/source/template-seed.d.ts +2 -2
  153. package/dist/standalone/agent-mail.d.ts +24 -0
  154. package/dist/standalone/agent-mail.js +62 -0
  155. package/dist/standalone/agent-store.d.ts +9 -0
  156. package/dist/standalone/agent-store.js +30 -0
  157. package/dist/standalone/agents.d.ts +68 -9
  158. package/dist/standalone/agents.js +428 -56
  159. package/dist/standalone/app-builds-binding.d.ts +20 -2
  160. package/dist/standalone/app-builds-binding.js +147 -23
  161. package/dist/standalone/app-set.d.ts +5 -2
  162. package/dist/standalone/app-set.js +174 -143
  163. package/dist/standalone/browser.d.ts +11 -0
  164. package/dist/standalone/browser.js +72 -0
  165. package/dist/standalone/builds.d.ts +13 -4
  166. package/dist/standalone/builds.js +28 -44
  167. package/dist/standalone/computer-egress.d.ts +2 -0
  168. package/dist/standalone/computer-egress.js +13 -4
  169. package/dist/standalone/computer-fleet.d.ts +43 -0
  170. package/dist/standalone/computer-fleet.js +200 -0
  171. package/dist/standalone/computer.d.ts +44 -9
  172. package/dist/standalone/computer.js +129 -22
  173. package/dist/standalone/install-apps-cli.js +43 -6
  174. package/dist/standalone/instance.d.ts +2 -2
  175. package/dist/standalone/instance.js +8 -11
  176. package/dist/standalone/machine-link.d.ts +354 -0
  177. package/dist/standalone/machine-link.js +368 -0
  178. package/dist/standalone/media.d.ts +2 -2
  179. package/dist/standalone/media.js +19 -16
  180. package/dist/standalone/preview.d.ts +2 -2
  181. package/dist/standalone/preview.js +37 -10
  182. package/dist/standalone/registry.d.ts +149 -9
  183. package/dist/standalone/registry.js +299 -8
  184. package/dist/standalone/worker.d.ts +19 -5
  185. package/dist/standalone/worker.js +90 -54
  186. package/dist/standalone/workflow-binding.d.ts +18 -22
  187. package/dist/standalone/workflow-binding.js +18 -18
  188. package/dist/standalone/workflow-control.d.ts +4 -0
  189. package/dist/standalone/workflow-control.js +40 -0
  190. package/dist/standalone/workflow-events.d.ts +2 -2
  191. package/dist/standalone/workflow-events.js +9 -10
  192. package/dist/standalone/workflow-git-capture.js +4 -3
  193. package/dist/standalone/workflow-host.d.ts +162 -161
  194. package/dist/standalone/workflow-host.js +101 -21
  195. package/dist/standalone/workflow-node-capture.js +11 -4
  196. package/dist/standalone/workflow-schedules.d.ts +5 -2
  197. package/dist/standalone/workflow-schedules.js +9 -10
  198. package/dist/standalone/workflow-trigger-host.d.ts +65 -5
  199. package/dist/standalone/workflow-trigger-host.js +2 -2
  200. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  201. package/dist/standalone/workflow-webhooks.js +39 -0
  202. package/dist/standalone/workflows.d.ts +140 -43
  203. package/dist/standalone/workflows.js +198 -39
  204. package/dist/storage-frame.d.ts +6 -6
  205. package/dist/testing/capabilities.js +14 -3
  206. package/dist/testing/installation.js +26 -2
  207. package/dist/testing/worker.js +1 -1
  208. package/dist/testing/workers-runtime.d.ts +8 -2
  209. package/dist/testing/workers-runtime.js +20 -2
  210. package/dist/webhook-abuse.d.ts +25 -0
  211. package/dist/webhook-abuse.js +54 -0
  212. package/dist/webhook-custody.d.ts +144 -0
  213. package/dist/webhook-custody.js +222 -0
  214. package/dist/webhook-ingress.d.ts +36 -0
  215. package/dist/webhook-ingress.js +146 -0
  216. package/dist/webhooks.d.ts +111 -0
  217. package/dist/webhooks.js +189 -0
  218. package/dist/workers-git.d.ts +12 -17
  219. package/dist/workers-git.js +60 -52
  220. package/dist/workers.d.ts +224 -13
  221. package/dist/workers.js +351 -51
  222. package/dist/workflow-agent-authority.d.ts +8 -0
  223. package/dist/workflow-agent-authority.js +42 -0
  224. package/dist/workflows.d.ts +66 -0
  225. package/dist/workflows.js +97 -0
  226. package/docs/api/README.md +63 -37
  227. package/docs/api/agents.md +17 -0
  228. package/docs/api/app-storage.md +1 -1
  229. package/docs/api/apps.md +1394 -543
  230. package/docs/api/browser/boot.md +132 -0
  231. package/docs/api/browser.md +1440 -0
  232. package/docs/api/build-contract.md +38 -25
  233. package/docs/api/build.md +263 -84
  234. package/docs/api/cloudflare.md +93 -7
  235. package/docs/api/git.md +3 -13
  236. package/docs/api/host.md +944 -118
  237. package/docs/api/index.md +320 -18
  238. package/docs/api/keys.md +24 -1
  239. package/docs/api/mcp.md +59 -12
  240. package/docs/api/modules.md +33 -40
  241. package/docs/api/pack.md +57 -8
  242. package/docs/api/page/mount.md +92 -4
  243. package/docs/api/workers-git.md +103 -24
  244. package/docs/api/workers.md +1581 -244
  245. package/docs/site/agent-quickstart.mdx +28 -14
  246. package/examples/standalone/worker.ts +3 -1
  247. package/examples/standalone/wrangler.jsonc +2 -0
  248. package/package.json +73 -71
  249. package/dist/installation-consent.d.ts +0 -81
  250. package/dist/installation-consent.js +0 -84
  251. package/dist/shell/app-directory.d.ts +0 -542
  252. package/dist/shell/app-directory.js +0 -196
  253. package/dist/shell/browser-actions.d.ts +0 -7
  254. package/dist/shell/browser-actions.js +0 -56
  255. package/dist/shell/command-host.d.ts +0 -112
  256. package/dist/shell/command-host.js +0 -502
  257. package/dist/shell/commands.d.ts +0 -25
  258. package/dist/shell/commands.js +0 -78
  259. package/dist/shell/index.d.ts +0 -8
  260. package/dist/shell/index.js +0 -8
  261. package/dist/shell/keys.d.ts +0 -41
  262. package/dist/shell/keys.js +0 -134
  263. package/dist/shell/shell-module.d.ts +0 -55
  264. package/dist/shell/shell-module.js +0 -86
  265. package/dist/shell/window-manager.d.ts +0 -128
  266. package/dist/shell/window-manager.js +0 -730
  267. package/dist/shell/workbench-contract.d.ts +0 -68
  268. package/dist/shell/workbench-directory.d.ts +0 -332
  269. package/dist/shell/workbench-directory.js +0 -35
  270. package/dist/shell/workbench-host-types.d.ts +0 -96
  271. package/dist/shell/workbench-host-types.js +0 -1
  272. package/dist/shell/workbench-host.d.ts +0 -34
  273. package/dist/shell/workbench-host.js +0 -352
  274. package/dist/shell/workbench-model.d.ts +0 -194
  275. package/dist/shell/workbench-model.js +0 -53
  276. package/dist/shell/workbench.d.ts +0 -4
  277. package/dist/shell/workbench.js +0 -2
  278. package/docs/api/shell/app-directory.md +0 -599
  279. package/docs/api/shell/command-host.md +0 -224
  280. package/docs/api/shell/commands.md +0 -197
  281. package/docs/api/shell/keys.md +0 -183
  282. package/docs/api/shell/shell-module.md +0 -204
  283. package/docs/api/shell/window-manager.md +0 -267
  284. package/docs/api/shell/workbench-directory.md +0 -119
  285. package/docs/api/shell/workbench.md +0 -205
  286. package/docs/api/shell.md +0 -1915
  287. /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,16 +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
17
  export { agentAvatarProjectionSchema, agentAvatarSchema, avatarFromProjection, createAgentAvatarStore, type AgentAvatarStore, } from "./agent-avatars.js";
14
- /** The Cairn ID family that mints and recognizes App Instance IDs (`instance_…`). */
15
- export declare const instanceIds: Readonly<{
16
- readonly instance: import("@fungi.computer/cairn").IdCodec<"app-instance", "instance", 1>;
17
- }>;
18
- /** A stable App Instance ID minted by {@link instanceIds}. */
19
- export type InstanceId = CairnId<"app-instance", "instance">;
20
- /** Decodes a string into an {@link InstanceId}, rejecting any other ID family. */
21
- export declare const instanceIdSchema: Schema.Codec<InstanceId, string>;
18
+ export { instanceIds, instanceIdSchema, type InstanceId, } from "./instance-id.js";
22
19
  /**
23
20
  * The owner that issues authority for an Instance: an `installation` of an App
24
21
  * or a `deployment`. Owner identity never changes for the life of an Instance.
@@ -32,7 +29,7 @@ export declare const ownerSchema: Schema.Struct<{
32
29
  * runs against, at a positive authority version.
33
30
  */
34
31
  export declare const selectionSchema: Schema.Struct<{
35
- readonly instanceId: Schema.Codec<InstanceId, string, never, never>;
32
+ readonly instanceId: Schema.Codec<import("./instance-id.js").InstanceId, string, never, never>;
36
33
  readonly owner: Schema.Struct<{
37
34
  readonly kind: Schema.Literals<readonly ["installation", "deployment"]>;
38
35
  readonly id: Schema.NonEmptyString;
@@ -44,7 +41,7 @@ export declare const selectionSchema: Schema.Struct<{
44
41
  /** A complete owner-issued state: a selection plus `enabled`, `disabled` or `deleted`. */
45
42
  export declare const ownerStateSchema: Schema.Struct<{
46
43
  readonly status: Schema.Literals<readonly ["enabled", "disabled", "deleted"]>;
47
- readonly instanceId: Schema.Codec<InstanceId, string, never, never>;
44
+ readonly instanceId: Schema.Codec<import("./instance-id.js").InstanceId, string, never, never>;
48
45
  readonly owner: Schema.Struct<{
49
46
  readonly kind: Schema.Literals<readonly ["installation", "deployment"]>;
50
47
  readonly id: Schema.NonEmptyString;
@@ -63,7 +60,7 @@ export declare const instanceSchema: Schema.Struct<{
63
60
  readonly stateSchema: Schema.NullOr<Schema.Number>;
64
61
  readonly desiredStatus: Schema.Literals<readonly ["enabled", "disabled", "deleted"]>;
65
62
  readonly status: Schema.Literals<readonly ["active", "disabled", "deleting", "deleted"]>;
66
- readonly instanceId: Schema.Codec<InstanceId, string, never, never>;
63
+ readonly instanceId: Schema.Codec<import("./instance-id.js").InstanceId, string, never, never>;
67
64
  readonly owner: Schema.Struct<{
68
65
  readonly kind: Schema.Literals<readonly ["installation", "deployment"]>;
69
66
  readonly id: Schema.NonEmptyString;
@@ -85,7 +82,7 @@ export type Backend = Readonly<{
85
82
  backend: DurableBackend;
86
83
  }>;
87
84
  /** The runtime owns the bounded rejection grammar, including across RPC. */
88
- 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")[]]>;
89
86
  /** One of the bounded {@link appInstanceErrorCodeSchema} rejection codes. */
90
87
  export type AppInstanceErrorCode = typeof appInstanceErrorCodeSchema.Type;
91
88
  /** Stable runtime failure identity; message remains presentation only. */
@@ -111,8 +108,19 @@ export type InstancePorts<Host> = Readonly<{
111
108
  write: (instance: Instance) => Effect.Effect<void, Error>;
112
109
  /** Resolve a Release ID to its verified durable backend. */
113
110
  resolve: (release: Selection["releaseId"], scope: BackendResolution<Host>) => Effect.Effect<Backend, Error>;
114
- /** Abort in-flight backend work and invalidate existing connections. */
115
- 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>;
116
124
  /** Delete the backend's own state. */
117
125
  purge: () => Effect.Effect<void, Error>;
118
126
  /**
@@ -144,6 +152,8 @@ export type AppInstance<Host> = Readonly<{
144
152
  * changed input at the same version conflicts and older versions are stale.
145
153
  */
146
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>;
147
157
  /** Admit the frame's selection and forward an HTTP request to the active backend. */
148
158
  fetch: (frame: InvocationFrame<Host>, request: Request) => Effect.Effect<Response, Error>;
149
159
  /** Admit the frame and invoke a method declared by the Release's App API. */
package/dist/index.js CHANGED
@@ -1,22 +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
14
  export { agentAvatarProjectionSchema, agentAvatarSchema, avatarFromProjection, createAgentAvatarStore, } from "./agent-avatars.js";
12
- /** The Cairn ID family that mints and recognizes App Instance IDs (`instance_…`). */
13
- export const instanceIds = defineIdFamily({
14
- family: "app-instance",
15
- version: 1,
16
- kinds: { instance: { prefix: "instance" } },
17
- });
18
- /** Decodes a string into an {@link InstanceId}, rejecting any other ID family. */
19
- export const instanceIdSchema = Schema.String.pipe(Schema.refine((value) => instanceIds.instance.is(value)));
15
+ export { instanceIds, instanceIdSchema, } from "./instance-id.js";
20
16
  /**
21
17
  * The owner that issues authority for an Instance: an `installation` of an App
22
18
  * or a `deployment`. Owner identity never changes for the life of an Instance.
@@ -60,7 +56,7 @@ export function selectionOf(installation) {
60
56
  owner: { kind: "installation", id: installation.installationId },
61
57
  appId: installation.release.appId,
62
58
  releaseId: installation.release.releaseId,
63
- authorityVersion: installation.authorityVersion,
59
+ authorityVersion: installation.authority.authorityVersion,
64
60
  };
65
61
  }
66
62
  /** The runtime owns the bounded rejection grammar, including across RPC. */
@@ -68,6 +64,7 @@ export const appInstanceErrorCodeSchema = Schema.Literals([
68
64
  "invalid_owner_state",
69
65
  "invalid_invocation_facts",
70
66
  "revoked",
67
+ "disabled",
71
68
  "binding_not_granted",
72
69
  "binding_unavailable",
73
70
  "expired",
@@ -84,11 +81,22 @@ export const appInstanceErrorCodeSchema = Schema.Literals([
84
81
  "release_no_longer_matches_instance",
85
82
  "instance_release_mismatch",
86
83
  "installation_tenant_mismatch",
84
+ ...bootFailureSchema.options,
87
85
  ]);
88
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,
89
96
  invalid_owner_state: "Invalid owner state",
90
97
  invalid_invocation_facts: "Invalid invocation facts",
91
98
  revoked: "Invocation authority revoked",
99
+ disabled: "App disabled",
92
100
  binding_not_granted: "Binding operation not granted",
93
101
  binding_unavailable: "Binding operation unavailable on this host",
94
102
  expired: "Invocation deadline exceeded",
@@ -137,11 +145,11 @@ export function createAppInstance(ports) {
137
145
  }
138
146
  return current;
139
147
  });
140
- const apply = (input) => lock.withPermits(1)(Effect.gen(function* () {
148
+ const preflight = (input, readCurrent, uiCheck) => Effect.gen(function* () {
141
149
  const desired = yield* Schema.decodeUnknownEffect(ownerStateSchema)(input).pipe(Effect.mapError(() => new AppInstanceError("invalid_owner_state")));
142
150
  if (desired.instanceId !== ports.instanceId)
143
151
  return yield* failure("instance_identity_mismatch");
144
- const current = yield* read;
152
+ const current = yield* readCurrent;
145
153
  if (current && !sameOwner(current, desired))
146
154
  return yield* failure("instance_owner_mismatch");
147
155
  const retry = current !== null &&
@@ -152,14 +160,15 @@ export function createAppInstance(ports) {
152
160
  return yield* failure("conflicting_owner_state");
153
161
  if (current && desired.authorityVersion < current.authorityVersion)
154
162
  return yield* failure("stale_authority");
155
- if (current?.status === "deleted") {
163
+ if (current?.status === "deleted" || current?.status === "deleting") {
156
164
  if (retry && desired.status === "deleted")
157
- return current;
165
+ return { kind: "unchanged", receipt: current };
158
166
  return yield* failure("instance_deleted");
159
167
  }
160
168
  if (retry && current.status === "active")
161
- return current;
169
+ return { kind: "unchanged", receipt: current };
162
170
  let stateSchema = current?.stateSchema ?? null;
171
+ let bootCheck = null;
163
172
  if (desired.status === "enabled") {
164
173
  const resolved = yield* ports.resolve(desired.releaseId, {
165
174
  kind: "metadata",
@@ -170,7 +179,20 @@ export function createAppInstance(ports) {
170
179
  stateSchema !== resolved.backend.runtime.stateSchema)
171
180
  return yield* failure("state_schema_migration_unsupported");
172
181
  stateSchema = resolved.backend.runtime.stateSchema;
182
+ bootCheck = yield* ports.boot(resolved, current?.stateSchema ?? null, uiCheck);
173
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;
174
196
  const next = {
175
197
  ...desired,
176
198
  desiredStatus: desired.status,
@@ -181,7 +203,7 @@ export function createAppInstance(ports) {
181
203
  };
182
204
  // Persist the exact owner fact before stopping runtime; no resource-issued version.
183
205
  yield* ports.write(next);
184
- yield* ports.abort();
206
+ yield* ports.abort(new AppInstanceError(desired.status === "disabled" ? "disabled" : "revoked"));
185
207
  if (desired.status === "deleted")
186
208
  yield* ports.purge();
187
209
  const receipt = {
@@ -310,6 +332,7 @@ export function createAppInstance(ports) {
310
332
  }));
311
333
  return {
312
334
  syncRefused,
335
+ prepare,
313
336
  apply,
314
337
  fetch,
315
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
+ };