@yourrealm/sdk 0.0.0-alpha.1378.ce03c8dc

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 (230) hide show
  1. package/LICENSE-EXCEPTION +26 -0
  2. package/README.md +34 -0
  3. package/dist/chunk-3R6KEFOI.js +22 -0
  4. package/dist/chunk-67BDAMYX.js +376 -0
  5. package/dist/chunk-AITGS27R.js +139 -0
  6. package/dist/chunk-CMAEHQ4F.js +6 -0
  7. package/dist/chunk-MLKGABMK.js +9 -0
  8. package/dist/chunk-O3UIK34B.js +1430 -0
  9. package/dist/chunk-VHTUSZUX.js +176 -0
  10. package/dist/cli.js +9006 -0
  11. package/dist/docker.js +54 -0
  12. package/dist/index.js +1053 -0
  13. package/dist/ingress.js +436 -0
  14. package/dist/jsx-runtime.js +2 -0
  15. package/dist/preact.js +22 -0
  16. package/dist/service-url.js +11 -0
  17. package/dist/services.js +213 -0
  18. package/dist/test.js +5069 -0
  19. package/dist/ui.js +254 -0
  20. package/dist/worker-protocol.js +89 -0
  21. package/package.json +112 -0
  22. package/src/app-icon.ts +74 -0
  23. package/src/apply.ts +52 -0
  24. package/src/bundle-ui.ts +211 -0
  25. package/src/bundle.ts +546 -0
  26. package/src/cli/build-dev-landing.ts +84 -0
  27. package/src/cli/build-install-panel.ts +90 -0
  28. package/src/cli/build-runtime.ts +133 -0
  29. package/src/cli/build.ts +63 -0
  30. package/src/cli/dev.ts +107 -0
  31. package/src/cli/down.ts +60 -0
  32. package/src/cli/main.ts +56 -0
  33. package/src/cli/test.ts +61 -0
  34. package/src/cli/validate.ts +37 -0
  35. package/src/compile.ts +100 -0
  36. package/src/create-app.ts +982 -0
  37. package/src/deep-merge.ts +33 -0
  38. package/src/dev/fixture.ts +48 -0
  39. package/src/dev/install-handler.ts +383 -0
  40. package/src/dev/server.ts +968 -0
  41. package/src/dev/stack.ts +108 -0
  42. package/src/dev/state.ts +159 -0
  43. package/src/docker.ts +96 -0
  44. package/src/errors.ts +61 -0
  45. package/src/factory.ts +481 -0
  46. package/src/garage.ts +56 -0
  47. package/src/generator/hash.ts +6 -0
  48. package/src/generator/resolve-refs.ts +207 -0
  49. package/src/grants-builder.ts +100 -0
  50. package/src/grants.ts +260 -0
  51. package/src/home-api.ts +172 -0
  52. package/src/home-cookie.ts +10 -0
  53. package/src/home-registry.ts +120 -0
  54. package/src/home.ts +187 -0
  55. package/src/host-registry.ts +143 -0
  56. package/src/http-header.ts +20 -0
  57. package/src/identifiers.ts +49 -0
  58. package/src/index.ts +66 -0
  59. package/src/info.ts +64 -0
  60. package/src/ini.ts +50 -0
  61. package/src/json.ts +14 -0
  62. package/src/jsx-runtime/index.ts +5 -0
  63. package/src/keys.ts +29 -0
  64. package/src/mysql.ts +56 -0
  65. package/src/pack-build.ts +108 -0
  66. package/src/pack.ts +115 -0
  67. package/src/peer-registry.ts +121 -0
  68. package/src/peer-world.ts +117 -0
  69. package/src/permission-meta.ts +116 -0
  70. package/src/permissions.ts +108 -0
  71. package/src/pkg-root.ts +34 -0
  72. package/src/postgres.ts +79 -0
  73. package/src/preact/index.ts +27 -0
  74. package/src/redis.ts +43 -0
  75. package/src/ref.ts +271 -0
  76. package/src/remote-registry.ts +33 -0
  77. package/src/required-host-kinds.ts +110 -0
  78. package/src/resolve-context.ts +111 -0
  79. package/src/resolve.ts +471 -0
  80. package/src/runtime/docker/compose/compose-schema.ts +118 -0
  81. package/src/runtime/docker/compose/convert-to-docker-compose.ts +278 -0
  82. package/src/runtime/docker/compose/default-hardening.ts +21 -0
  83. package/src/runtime/docker/compose/effects.ts +107 -0
  84. package/src/runtime/docker/compose/index.ts +17 -0
  85. package/src/runtime/docker/compose/labels.ts +39 -0
  86. package/src/runtime/docker/compose/types.ts +78 -0
  87. package/src/runtime/docker/home-proxy-server/index.ts +177 -0
  88. package/src/runtime/docker/home-proxy-server/protocol.ts +53 -0
  89. package/src/runtime/docker/ingress/build-config.ts +819 -0
  90. package/src/runtime/docker/ingress/index.ts +16 -0
  91. package/src/runtime/docker/ingress/types.ts +182 -0
  92. package/src/runtime/docker/log-frames.ts +121 -0
  93. package/src/runtime/docker/logs-server/index.ts +366 -0
  94. package/src/runtime/docker/spawn-server/context-key.ts +82 -0
  95. package/src/runtime/docker/spawn-server/index.ts +492 -0
  96. package/src/runtime/docker/spawn-server/protocol.ts +70 -0
  97. package/src/runtime/docker/spawn-server/spec.ts +122 -0
  98. package/src/runtime/docker/stats-server/disks.ts +161 -0
  99. package/src/runtime/docker/stats-server/index.ts +322 -0
  100. package/src/runtime/home-client/index.ts +71 -0
  101. package/src/runtime/log-line.ts +24 -0
  102. package/src/runtime/logs-client/index.ts +175 -0
  103. package/src/runtime/spawn-client/index.ts +93 -0
  104. package/src/runtime/spawn-client/kaniko.ts +79 -0
  105. package/src/runtime/stats-client/index.ts +65 -0
  106. package/src/runtime/stats-sample.ts +95 -0
  107. package/src/safe-json.ts +37 -0
  108. package/src/sandbox.ts +1209 -0
  109. package/src/schema/build-apps.ts +38 -0
  110. package/src/schema/home.ts +217 -0
  111. package/src/schema/host.ts +792 -0
  112. package/src/schema/inputs.ts +26 -0
  113. package/src/schema/nodes.ts +75 -0
  114. package/src/schema/peer.ts +400 -0
  115. package/src/schema/public-domain.ts +116 -0
  116. package/src/schema/remote.ts +74 -0
  117. package/src/security.ts +594 -0
  118. package/src/serialize.ts +21 -0
  119. package/src/service-definition.ts +284 -0
  120. package/src/service-url.ts +64 -0
  121. package/src/service.ts +39 -0
  122. package/src/services/index.ts +5 -0
  123. package/src/shared.ts +65 -0
  124. package/src/speculative-silo.ts +96 -0
  125. package/src/stable-secret.ts +14 -0
  126. package/src/surfaces.ts +264 -0
  127. package/src/tailwind-bin.ts +56 -0
  128. package/src/test/boot.ts +192 -0
  129. package/src/test/compile.ts +501 -0
  130. package/src/test/describe.ts +117 -0
  131. package/src/test/index.ts +11 -0
  132. package/src/test/ingress-fixture.ts +37 -0
  133. package/src/test/types.ts +318 -0
  134. package/src/trusted-headers.ts +79 -0
  135. package/src/types.ts +126 -0
  136. package/src/ui/components/aside.tsx +96 -0
  137. package/src/ui/components/badge.tsx +42 -0
  138. package/src/ui/components/button.tsx +59 -0
  139. package/src/ui/components/card.tsx +106 -0
  140. package/src/ui/components/checkbox.tsx +37 -0
  141. package/src/ui/components/code-block.tsx +29 -0
  142. package/src/ui/components/combobox.tsx +119 -0
  143. package/src/ui/components/config-field.tsx +284 -0
  144. package/src/ui/components/dialog.tsx +137 -0
  145. package/src/ui/components/domain-editor.tsx +317 -0
  146. package/src/ui/components/domain-list.ts +205 -0
  147. package/src/ui/components/dropdown-menu.tsx +73 -0
  148. package/src/ui/components/empty-state.tsx +51 -0
  149. package/src/ui/components/highlight.ts +59 -0
  150. package/src/ui/components/icons.tsx +101 -0
  151. package/src/ui/components/image-posture.tsx +85 -0
  152. package/src/ui/components/input.tsx +28 -0
  153. package/src/ui/components/label.tsx +16 -0
  154. package/src/ui/components/line-chart.tsx +113 -0
  155. package/src/ui/components/page.tsx +39 -0
  156. package/src/ui/components/permission-row.tsx +262 -0
  157. package/src/ui/components/permission-section.tsx +51 -0
  158. package/src/ui/components/pie-chart.tsx +245 -0
  159. package/src/ui/components/schema-block.tsx +60 -0
  160. package/src/ui/components/separator.tsx +42 -0
  161. package/src/ui/components/sheet.tsx +130 -0
  162. package/src/ui/components/sidebar.tsx +650 -0
  163. package/src/ui/components/silo-picker.tsx +510 -0
  164. package/src/ui/components/skeleton.tsx +13 -0
  165. package/src/ui/components/sonner.tsx +33 -0
  166. package/src/ui/components/switch.tsx +58 -0
  167. package/src/ui/components/table-view.ts +219 -0
  168. package/src/ui/components/table.tsx +506 -0
  169. package/src/ui/components/tabs.tsx +59 -0
  170. package/src/ui/components/timeago.tsx +84 -0
  171. package/src/ui/components/tooltip.tsx +56 -0
  172. package/src/ui/components.json +21 -0
  173. package/src/ui/dev/dev-shell.tsx +145 -0
  174. package/src/ui/home/home-grid.tsx +177 -0
  175. package/src/ui/hooks/use-mobile.ts +19 -0
  176. package/src/ui/index.ts +189 -0
  177. package/src/ui/install/adapter.ts +193 -0
  178. package/src/ui/install/deploy-targets-picker.tsx +185 -0
  179. package/src/ui/install/install-app-panel.tsx +912 -0
  180. package/src/ui/install/install-error.tsx +62 -0
  181. package/src/ui/install/perms-inactive.ts +50 -0
  182. package/src/ui/install/required-fields.ts +37 -0
  183. package/src/ui/install/tabs-form.tsx +557 -0
  184. package/src/ui/tokens.css +279 -0
  185. package/src/ui/utils.ts +78 -0
  186. package/src/ui-error.ts +139 -0
  187. package/src/ui-runtime/dev-landing-entry.tsx +258 -0
  188. package/src/ui-runtime/index.ts +83 -0
  189. package/src/ui-runtime/install-entry.tsx +119 -0
  190. package/src/ui-runtime/mount.ts +367 -0
  191. package/src/ui-runtime/runtime.css +56 -0
  192. package/src/ui-surface.ts +451 -0
  193. package/src/valkey.ts +43 -0
  194. package/src/worker/extract-tar.ts +125 -0
  195. package/src/worker/identity.ts +183 -0
  196. package/src/worker/index.ts +16 -0
  197. package/src/worker/public-ip.ts +92 -0
  198. package/src/worker/queue.ts +54 -0
  199. package/src/worker/reconcile.ts +127 -0
  200. package/src/worker/start-worker.ts +227 -0
  201. package/src/worker/ui-proxy.ts +335 -0
  202. package/src/worker/uplink.ts +523 -0
  203. package/src/worker/validate.ts +34 -0
  204. package/src/worker/verify-relay.ts +102 -0
  205. package/src/worker-protocol/app-dirs.ts +94 -0
  206. package/src/worker-protocol/home-proxy-paths.ts +76 -0
  207. package/src/worker-protocol/index.ts +55 -0
  208. package/src/worker-protocol/ingress.ts +166 -0
  209. package/src/worker-protocol/logs-socket-path.ts +50 -0
  210. package/src/worker-protocol/networks.ts +39 -0
  211. package/src/worker-protocol/node-version.ts +70 -0
  212. package/src/worker-protocol/silo-paths.ts +114 -0
  213. package/src/worker-protocol/spawn-paths.ts +165 -0
  214. package/src/worker-protocol/stats-socket-path.ts +40 -0
  215. package/src/worker-protocol/update-files.ts +77 -0
  216. package/src/worker-protocol/uplink/constants.ts +86 -0
  217. package/src/worker-protocol/uplink/errors.ts +61 -0
  218. package/src/worker-protocol/uplink/frame.ts +114 -0
  219. package/src/worker-protocol/uplink/identity.ts +40 -0
  220. package/src/worker-protocol/uplink/index.ts +63 -0
  221. package/src/worker-protocol/uplink/methods.ts +462 -0
  222. package/src/worker-protocol/uplink/mux.ts +646 -0
  223. package/src/worker-protocol/uplink/protocol.ts +11 -0
  224. package/src/worker-protocol/uplink/rpc.ts +309 -0
  225. package/src/worker-protocol/uplink/session.ts +540 -0
  226. package/src/worker-protocol/uplink/test-support.ts +188 -0
  227. package/src/worker-protocol/worker-mount.ts +27 -0
  228. package/src/worker-protocol/worker-state.ts +190 -0
  229. package/src/xml.ts +49 -0
  230. package/src/yaml.ts +15 -0
@@ -0,0 +1,38 @@
1
+ // The shared contract for build-recipe data: Home resolves it (resolve.ts
2
+ // validates each service's `build`, peer.ts's `peers.buildApps` emits
3
+ // `BuildAppEntry[]`), Forge parses `/etc/forge/apps.json` against
4
+ // `buildAppsSchema`. A dependency-free leaf (only `zod`) so the Forge bundle
5
+ // pulls just this, not the peer/factory machinery, and so it stays off the
6
+ // author-facing root barrel — a Forge↔Home contract, not author surface.
7
+
8
+ import { z } from "zod";
9
+
10
+ /**
11
+ * Build-from-source recipe, mutually exclusive with `image` on a service.
12
+ * Recipe only — no repo/branch (the build service's binding lives in Forge).
13
+ * Structurally mirrors the `BuildRecipe` author interface in
14
+ * service-definition.ts; the resolver normalizes `build: true` to `{}` here.
15
+ */
16
+ export const buildRecipeSchema = z
17
+ .object({
18
+ dockerfile: z.string().optional(),
19
+ buildArgs: z.record(z.string(), z.string()).optional(),
20
+ context: z.string().optional(),
21
+ })
22
+ .strict();
23
+
24
+ /**
25
+ * One build target's resolved recipe, keyed by `(appId, service)`. Home's
26
+ * `peers.buildApps` peerable emits one entry per build-from-source service
27
+ * across apps; Forge looks the recipe up by `(appId, service)` when a push
28
+ * triggers a build.
29
+ */
30
+ export const buildAppsSchema = z.array(
31
+ z.object({
32
+ appId: z.string(),
33
+ service: z.string(),
34
+ recipe: buildRecipeSchema,
35
+ }),
36
+ );
37
+
38
+ export type BuildAppEntry = z.infer<typeof buildAppsSchema>[number];
@@ -0,0 +1,217 @@
1
+ // home.* permission factories + per-kind Home-side mechanics.
2
+ //
3
+ // Each factory is built via `homeable(...)` (the local binding of
4
+ // `makeHomeable(basePermission)`), which calls basePermission AND tags the
5
+ // kind's hooks (iterateUses / toValue / grantSchema) so the home registry
6
+ // derived in grants.ts can dispatch through them. Adding a home kind is one
7
+ // declaration here.
8
+ //
9
+ // QuickJS-safe: only `z` + pure constants at module load (the `WorkerHostPath`
10
+ // import is type-only). This module loads inside QuickJS during realm.ts eval,
11
+ // so it must touch no `Deno` or Web globals at load.
12
+
13
+ import { z } from "zod";
14
+ import type { WorkerHostPath } from "../worker-protocol/logs-socket-path.ts";
15
+ import type { WorkerMount } from "../worker-protocol/worker-mount.ts";
16
+ import { homeProxyMount } from "../worker-protocol/home-proxy-paths.ts";
17
+ import { basePermission } from "../factory.ts";
18
+ import { makeHomeable } from "../home-registry.ts";
19
+ import {
20
+ publicDomainValueSchema,
21
+ type PublicDomain,
22
+ type PublicDomainGrantData,
23
+ type PublicDomainValue,
24
+ } from "./public-domain.ts";
25
+
26
+ const homeable = makeHomeable(basePermission);
27
+
28
+ /**
29
+ * Closed set of control-plane capability scopes `home.apiAccess` can grant.
30
+ * Each names *what the app may do*, not a specific endpoint, so one scope can
31
+ * back several routes. An unknown scope is rejected at grant time (lands in
32
+ * `sanitizeGrants`'s `rejected` bucket).
33
+ *
34
+ * - `builder` — the build-service control plane: re-pin a `build:` app to a
35
+ * pushed digest (setImage), manage a template's preview children, and upsert
36
+ * a pull credential. The three always travel together — a builder wants all
37
+ * of them, nobody wants a subset — so they're one scope. Home enforces it
38
+ * **singleton**: at most one app per Home may hold `builder`, so there is
39
+ * exactly one builder and no sibling app can re-pin or shadow another app's
40
+ * images. The per-endpoint fences (build-only, lineage, self-or-lineage)
41
+ * still apply on top of the scope.
42
+ * - `whoami` — read your own identity + deployment facts (appId, granted
43
+ * scopes, worker slug, base domain). Low-privilege and **unlimited** (any
44
+ * number of apps may hold it), so `home.apiAccess` stays a general primitive
45
+ * rather than becoming builder-only once `builder` is singleton.
46
+ */
47
+ const HOME_API_ACCESS_SCOPES = ["builder", "whoami"] as const;
48
+
49
+ /**
50
+ * Resolved shape of a `home.apiAccess()` grant. Keyless by design — there is
51
+ * no token here, only the socket to talk to. `socket` is the mount for the
52
+ * per-app proxy DIRECTORY (connect to `home-proxy.sock` inside it — a file
53
+ * bind-mount of a unix socket doesn't wire across containers on macOS Docker),
54
+ * fixed at `readOnly: true` because relaying a request needs no write access to
55
+ * the directory. This kind's `resolve` hook builds it from the app's per-app
56
+ * proxy dir (`/opt/realm/worker/home-proxy/<appId>`) at eval time.
57
+ */
58
+ export interface HomeApiAccessValue {
59
+ readonly socket: WorkerMount;
60
+ }
61
+
62
+ // Type-level placeholder: `apiAccess`'s resolver builds the real per-app value,
63
+ // and this only exists so `ValueOf` can read the shape off `toValue`. Empty
64
+ // hostPath stands in for "never resolved"; only the host path is per-app.
65
+ const HOME_API_PLACEHOLDER: HomeApiAccessValue = {
66
+ socket: { ...homeProxyMount(""), hostPath: "" as WorkerHostPath },
67
+ };
68
+
69
+ /**
70
+ * Call your Home's control-plane API from this app, over the worker.
71
+ *
72
+ * The app container holds no uplink — only the Worker does — so it can't
73
+ * reach Home directly. `apiAccess` mounts a per-app worker socket dir
74
+ * (holding `home-proxy.sock`); the worker turns the request into one `apiAccess`
75
+ * call on its uplink, which Home dispatches to `/api-access/*`, and relays the
76
+ * response back.
77
+ *
78
+ * Keyless: the app holds no credential. Identity is the socket it arrived on
79
+ * (the worker asserts `X-Realm-App-Id`); authorization is the scopes you
80
+ * granted, enforced by Home. Resolves to `{ socket }` (the per-app socket
81
+ * DIRECTORY) — mount it as a volume and call `callHome`. The grant exempts the
82
+ * resolved dir from the absolute-mount gate, so a bare `apiAccess` is
83
+ * enough to wire it.
84
+ *
85
+ * Only a single app (`createApp`) may declare this. One app holding a Home
86
+ * control-plane socket on every box in the fleet is a much larger surface
87
+ * than one holding a single socket.
88
+ *
89
+ * @see https://yourrealm.eu/docs/realm-ts/home/api-access/
90
+ */
91
+ export const apiAccess = homeable(
92
+ "home.apiAccess",
93
+ z.array(z.enum(HOME_API_ACCESS_SCOPES)).min(1),
94
+ {
95
+ // No elevated *use* to collect — mounting the socket falls out of the
96
+ // grant being in place; the resolved socket is exempted from the
97
+ // absolute-mount gate via `collectAllowed` in the Home eval graph.
98
+ iterateUses: () => [],
99
+ // Type-level only; every real value comes from `resolve`, since the
100
+ // socket is per-app and the grant carries only scopes.
101
+ toValue: (): HomeApiAccessValue => HOME_API_PLACEHOLDER,
102
+ // The value the app mounts is the per-app DIRECTORY; the wire carries the
103
+ // socket file inside it (the path the worker binds).
104
+ resolve: ({ ctx }): HomeApiAccessValue => ({ socket: homeProxyMount(ctx.appId) }),
105
+ },
106
+ {
107
+ name: "Home API access",
108
+ phrase: "call the Home control-plane API",
109
+ helpText:
110
+ "This app can call your Home's control-plane API (over the worker) to use the capabilities you grant — e.g. pinning the images it builds. It holds no key; the worker proves which app it is.",
111
+ icon: "GearIcon",
112
+ danger: true,
113
+ // Single-only: a fleet-wide app holding a Home API socket on every
114
+ // box is a materially larger surface than one holding a single
115
+ // socket, and `builder` (the only scope with reach) is a singleton.
116
+ appKinds: "single",
117
+ },
118
+ );
119
+
120
+ /**
121
+ * What an ungranted (or not-yet-filled) `publicDomain` resolves to. Not an
122
+ * error state: it is what an operator who installed now and will add the
123
+ * domain later has, and it emits no public route.
124
+ */
125
+ const NO_PUBLIC_DOMAIN: PublicDomainValue = { hosts: [], domainMode: "redirect" };
126
+
127
+ /**
128
+ * Serve this app on a hostname the operator owns.
129
+ *
130
+ * Every routed service already gets an authenticated subdomain under the
131
+ * Realm domain, so "reachable from the internet" is not what this adds. What
132
+ * it adds is a name the operator brings: a real URL, an origin the app owns
133
+ * (its own cookies, OIDC issuer, CORS and absolute links), its own
134
+ * certificate, and no per-request hop through Home's gate.
135
+ *
136
+ * The author declares the leaf and wires it; the *operator* supplies the
137
+ * hostname on the install screen, the same split as `host.silo`. So there is
138
+ * no domain string anywhere in a realm.ts:
139
+ *
140
+ * ```ts
141
+ * domain: r.home
142
+ * .publicDomain()
143
+ * .reason("Serve your photos on your own domain")
144
+ * .optional(),
145
+ * // ...
146
+ * router: { containerPort: 2283, publicDomain: config.domain },
147
+ * ```
148
+ *
149
+ * A custom domain is necessarily **ungated**: Home cannot run its login on a
150
+ * domain it does not own (the `.{baseDomain}` session cookie can't reach it),
151
+ * so the app owns its own auth here. The gated auto subdomain keeps working
152
+ * alongside it.
153
+ *
154
+ * **Declare it `.optional()`.** There is no required variant in v1, and a
155
+ * required one breaks in two directions: a parked template is a row nobody can
156
+ * open an install screen for, and so is a pinless `build:` app. Nothing is
157
+ * broken without a custom domain anyway - the gated auto subdomain always
158
+ * exists, and an ungranted leaf resolves to `undefined`, which emits no public
159
+ * route.
160
+ *
161
+ * A preview child never serves one. It inherits its template's grants
162
+ * verbatim, so Home clears the resolved value for any `role: preview` app -
163
+ * without that, every child of one template would claim the same host.
164
+ *
165
+ * Only a single app (`createApp`) may declare this. A hostname resolves to
166
+ * one box, so a grant spread across a fleet would claim a name every worker
167
+ * would then fight over.
168
+ *
169
+ * @see https://yourrealm.eu/docs/realm-ts/home/public-domain/
170
+ */
171
+ export const publicDomain = homeable(
172
+ "home.publicDomain",
173
+ // Empty author data. `z.literal(true)` rather than `z.void()` leaves the
174
+ // slot for `publicDomain({ wildcard: true })` without changing any call
175
+ // site: the author declares cardinality, the operator only ever chooses
176
+ // which domain.
177
+ z.literal(true).default(true),
178
+ {
179
+ // Unlike the rest of `home.*` this kind has a real per-service use: the
180
+ // hosts a router actually serves on. `checkAppGrants` matches them against
181
+ // the granted set, so a packed bundle can't hand the VM a router carrying
182
+ // a host the operator never approved. Routers with no hosts emit nothing —
183
+ // there is no elevated thing being done.
184
+ iterateUses: function* (svc) {
185
+ const value = svc.router?.publicDomain;
186
+ if (value && value.hosts.length > 0) yield value;
187
+ },
188
+ // The grant holds the whole answer (the operator's hosts + their redirect
189
+ // choice), so there is no `resolve` hook — branding is all that separates
190
+ // stored data from the resolved value.
191
+ //
192
+ // Total on purpose: the speculative-eval paths build grants straight off
193
+ // the descriptors with an empty `grantValues` map, and this kind has no
194
+ // `perm.data` to fall back to, so throwing on `undefined` would drop the
195
+ // image cards off the install screen of every app that declares it.
196
+ toValue: (data: PublicDomainGrantData | undefined): PublicDomainValue =>
197
+ data == null
198
+ ? NO_PUBLIC_DOMAIN
199
+ : { hosts: data.hosts as PublicDomain[], domainMode: data.domainMode },
200
+ // Descriptor data and grant data diverge, the `host.silo` shape: the
201
+ // author declares nothing but intent, the operator supplies the hosts.
202
+ // The default is what an enabled-but-untouched toggle stores, and it is
203
+ // the "no route" value rather than an error, because adding the domain
204
+ // later is a normal install-then-configure flow.
205
+ grantSchema: publicDomainValueSchema.default({ hosts: [], domainMode: "redirect" }),
206
+ },
207
+ {
208
+ name: "Public domain",
209
+ phrase: "serve this app on a domain you own",
210
+ helpText:
211
+ "This app is served on a hostname you supply, outside your Realm domain and without Home's login in front of it. Anyone who can reach the name reaches the app, so only grant it to an app whose own authentication you trust.",
212
+ icon: "GlobeIcon",
213
+ // Single-only: a hostname points at one box. Fanning the same name
214
+ // across every worker would have them all claim it.
215
+ appKinds: "single",
216
+ },
217
+ );