@spacefast/common 0.0.24 → 0.2.1

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 (320) hide show
  1. package/brand-assets/LICENSE-Haskoy.txt +93 -0
  2. package/brand-assets/haskoy-latin-variable.woff2 +0 -0
  3. package/brand-assets/spacefast-favicon.svg +4 -4
  4. package/brand-assets/spacefast-sf-full-bleed.svg +3 -3
  5. package/brand-assets/spacefast-wordmark.svg +2 -4
  6. package/dist/agents/connect-targets.d.ts +6 -146
  7. package/dist/agents/connect-targets.js +63 -295
  8. package/dist/agents/private-key-oauth.d.ts +31 -2
  9. package/dist/agents/private-key-oauth.js +166 -92
  10. package/dist/agents/registry/core.d.ts +292 -0
  11. package/dist/agents/registry/core.js +1388 -0
  12. package/dist/agents/registry/deeplink-probes.d.ts +23 -0
  13. package/dist/agents/registry/deeplink-probes.js +18 -0
  14. package/dist/agents/registry/node.d.ts +56 -0
  15. package/dist/agents/registry/node.js +339 -0
  16. package/dist/brand-assets-build.js +3 -6
  17. package/dist/brand-assets.d.ts +5 -15
  18. package/dist/brand-assets.js +12 -14
  19. package/dist/brand.d.ts +17 -0
  20. package/dist/brand.js +24 -4
  21. package/dist/config/domains.d.ts +79 -12
  22. package/dist/config/domains.js +128 -29
  23. package/dist/config/index.js +1 -2
  24. package/dist/contracts/abuse.js +9 -24
  25. package/dist/contracts/access-profiles.d.ts +86 -0
  26. package/dist/contracts/access-profiles.js +89 -0
  27. package/dist/contracts/access.d.ts +24 -30
  28. package/dist/contracts/access.js +33 -64
  29. package/dist/contracts/activity.d.ts +18 -1
  30. package/dist/contracts/activity.js +72 -26
  31. package/dist/contracts/analytics.js +8 -10
  32. package/dist/contracts/api-keys.d.ts +77 -52
  33. package/dist/contracts/api-keys.js +44 -24
  34. package/dist/contracts/application-journal.d.ts +216 -0
  35. package/dist/contracts/application-journal.js +167 -0
  36. package/dist/contracts/auth.d.ts +90 -17
  37. package/dist/contracts/auth.js +86 -17
  38. package/dist/contracts/beta.d.ts +9 -4
  39. package/dist/contracts/beta.js +20 -16
  40. package/dist/contracts/billing.d.ts +2 -1
  41. package/dist/contracts/billing.js +37 -11
  42. package/dist/contracts/bootstrap.d.ts +791 -0
  43. package/dist/contracts/bootstrap.js +51 -0
  44. package/dist/contracts/builds.d.ts +209 -18
  45. package/dist/contracts/builds.js +93 -27
  46. package/dist/contracts/channels.d.ts +43 -0
  47. package/dist/contracts/channels.js +25 -9
  48. package/dist/contracts/cli.d.ts +7 -0
  49. package/dist/contracts/cli.js +6 -0
  50. package/dist/contracts/collab-public.d.ts +1 -1
  51. package/dist/contracts/collab-public.js +5 -8
  52. package/dist/contracts/comments.d.ts +44 -34
  53. package/dist/contracts/comments.js +107 -77
  54. package/dist/contracts/commerce.d.ts +165 -0
  55. package/dist/contracts/commerce.js +146 -0
  56. package/dist/contracts/common.d.ts +3 -3
  57. package/dist/contracts/common.js +20 -31
  58. package/dist/contracts/content-contract-verification.d.ts +521 -0
  59. package/dist/contracts/content-contract-verification.js +320 -0
  60. package/dist/contracts/content-platform-fixture.d.ts +1278 -0
  61. package/dist/contracts/content-platform-fixture.js +44 -0
  62. package/dist/contracts/content-program.d.ts +994 -0
  63. package/dist/contracts/content-program.js +658 -0
  64. package/dist/contracts/content-sync.d.ts +290 -0
  65. package/dist/contracts/content-sync.js +167 -0
  66. package/dist/contracts/content.d.ts +432 -0
  67. package/dist/contracts/content.js +303 -0
  68. package/dist/contracts/continuation.d.ts +17 -1
  69. package/dist/contracts/continuation.js +23 -23
  70. package/dist/contracts/countries.js +3 -4
  71. package/dist/contracts/crons.d.ts +52 -0
  72. package/dist/contracts/crons.js +60 -0
  73. package/dist/contracts/dashboard-prefs.d.ts +56 -0
  74. package/dist/contracts/dashboard-prefs.js +51 -0
  75. package/dist/contracts/device-auth.d.ts +234 -37
  76. package/dist/contracts/device-auth.js +267 -53
  77. package/dist/contracts/docs.d.ts +1 -5
  78. package/dist/contracts/docs.js +7 -15
  79. package/dist/contracts/domains.d.ts +501 -93
  80. package/dist/contracts/domains.js +193 -55
  81. package/dist/contracts/email-preferences.d.ts +3 -3
  82. package/dist/contracts/email-preferences.js +6 -7
  83. package/dist/contracts/enums.d.ts +27 -26
  84. package/dist/contracts/enums.js +68 -61
  85. package/dist/contracts/error-code-meta.d.ts +283 -7
  86. package/dist/contracts/error-code-meta.js +97 -5
  87. package/dist/contracts/error-codes.d.ts +5 -5
  88. package/dist/contracts/error-codes.js +82 -17
  89. package/dist/contracts/events.d.ts +100 -20
  90. package/dist/contracts/events.js +107 -20
  91. package/dist/contracts/execution.d.ts +48 -64
  92. package/dist/contracts/execution.js +52 -62
  93. package/dist/contracts/feature-lifecycle.d.ts +73 -27
  94. package/dist/contracts/feature-lifecycle.js +86 -26
  95. package/dist/contracts/features.d.ts +68 -19
  96. package/dist/contracts/features.js +42 -41
  97. package/dist/contracts/fixtures/content-platform-v1.json +1108 -0
  98. package/dist/contracts/frame-session.d.ts +192 -0
  99. package/dist/contracts/frame-session.js +164 -0
  100. package/dist/contracts/functions.d.ts +338 -164
  101. package/dist/contracts/functions.js +367 -198
  102. package/dist/contracts/git.d.ts +27 -33
  103. package/dist/contracts/git.js +10 -11
  104. package/dist/contracts/grant-copy.d.ts +43 -0
  105. package/dist/contracts/grant-copy.js +44 -0
  106. package/dist/contracts/grants.d.ts +62 -54
  107. package/dist/contracts/grants.js +124 -23
  108. package/dist/contracts/ids.d.ts +4 -1
  109. package/dist/contracts/ids.js +6 -3
  110. package/dist/contracts/internal.d.ts +8 -18
  111. package/dist/contracts/internal.js +13 -14
  112. package/dist/contracts/mcp.d.ts +28 -0
  113. package/dist/contracts/mcp.js +28 -0
  114. package/dist/contracts/me.d.ts +6 -1
  115. package/dist/contracts/me.js +12 -6
  116. package/dist/contracts/notifications.d.ts +27 -29
  117. package/dist/contracts/notifications.js +9 -4
  118. package/dist/contracts/oauth-resources.d.ts +112 -10
  119. package/dist/contracts/oauth-resources.js +116 -11
  120. package/dist/contracts/oauth-scope-actions.d.ts +90 -0
  121. package/dist/contracts/oauth-scope-actions.js +126 -0
  122. package/dist/contracts/operations.d.ts +27 -4
  123. package/dist/contracts/operations.js +12 -6
  124. package/dist/contracts/pages.d.ts +31 -7
  125. package/dist/contracts/pages.js +22 -5
  126. package/dist/contracts/partner-notifications.d.ts +3 -0
  127. package/dist/contracts/partner-notifications.js +65 -0
  128. package/dist/contracts/{platform.d.ts → partner.d.ts} +29 -42
  129. package/dist/contracts/{platform.js → partner.js} +27 -40
  130. package/dist/contracts/plan-policy.js +1 -1
  131. package/dist/contracts/principal-assertion.d.ts +43 -0
  132. package/dist/contracts/principal-assertion.js +67 -0
  133. package/dist/contracts/principals.d.ts +73 -0
  134. package/dist/contracts/principals.js +59 -0
  135. package/dist/contracts/privacy.d.ts +11 -0
  136. package/dist/contracts/privacy.js +17 -0
  137. package/dist/contracts/problem-document.js +3 -4
  138. package/dist/contracts/publish-archive.d.ts +269 -161
  139. package/dist/contracts/publish-archive.js +11 -1
  140. package/dist/contracts/push-new.d.ts +8 -8
  141. package/dist/contracts/quotas.js +5 -5
  142. package/dist/contracts/realtime.d.ts +52 -0
  143. package/dist/contracts/realtime.js +54 -0
  144. package/dist/contracts/repository-connections.d.ts +44 -31
  145. package/dist/contracts/repository-connections.js +17 -5
  146. package/dist/contracts/resources.d.ts +105 -14
  147. package/dist/contracts/resources.js +150 -26
  148. package/dist/contracts/route-inventory.d.ts +358 -0
  149. package/dist/contracts/route-inventory.js +141 -0
  150. package/dist/contracts/runtime-api.d.ts +206 -82
  151. package/dist/contracts/runtime-api.js +131 -70
  152. package/dist/contracts/runtime-app.d.ts +13 -23
  153. package/dist/contracts/runtime-app.js +39 -24
  154. package/dist/contracts/runtime-components.d.ts +143 -0
  155. package/dist/contracts/runtime-components.js +199 -0
  156. package/dist/contracts/runtime-db.d.ts +49 -23
  157. package/dist/contracts/runtime-db.js +87 -22
  158. package/dist/contracts/runtime-purge.d.ts +8 -0
  159. package/dist/contracts/runtime-purge.js +5 -0
  160. package/dist/contracts/runtime-services.d.ts +43 -75
  161. package/dist/contracts/runtime-services.js +29 -49
  162. package/dist/contracts/runtime-storage.d.ts +15 -14
  163. package/dist/contracts/runtime-storage.js +30 -14
  164. package/dist/contracts/sf-config-v1.d.ts +48 -14
  165. package/dist/contracts/slugs.js +13 -4
  166. package/dist/contracts/space-config.d.ts +283 -19
  167. package/dist/contracts/space-config.js +430 -80
  168. package/dist/contracts/spaces.d.ts +376 -290
  169. package/dist/contracts/spaces.js +200 -135
  170. package/dist/contracts/superadmin-activity.d.ts +3 -43
  171. package/dist/contracts/superadmin-activity.js +6 -30
  172. package/dist/contracts/superadmin-emails.d.ts +1 -0
  173. package/dist/contracts/superadmin-emails.js +17 -1
  174. package/dist/contracts/superadmin-runtime.d.ts +199 -0
  175. package/dist/contracts/superadmin-runtime.js +141 -0
  176. package/dist/contracts/superadmin-search.d.ts +22 -2
  177. package/dist/contracts/superadmin-search.js +14 -6
  178. package/dist/contracts/superadmin-spaces.d.ts +438 -189
  179. package/dist/contracts/superadmin-spaces.js +131 -25
  180. package/dist/contracts/superadmin-teams.d.ts +5 -2
  181. package/dist/contracts/superadmin-teams.js +1 -2
  182. package/dist/contracts/superadmin-tenants.d.ts +627 -12
  183. package/dist/contracts/superadmin-tenants.js +32 -13
  184. package/dist/contracts/superadmin.d.ts +232 -37
  185. package/dist/contracts/superadmin.js +106 -22
  186. package/dist/contracts/tags.d.ts +85 -58
  187. package/dist/contracts/tags.js +47 -37
  188. package/dist/contracts/teams.d.ts +86 -26
  189. package/dist/contracts/teams.js +90 -31
  190. package/dist/contracts/tenants.d.ts +135 -0
  191. package/dist/contracts/tenants.js +120 -0
  192. package/dist/contracts/test-triggers.d.ts +70 -0
  193. package/dist/contracts/test-triggers.js +116 -0
  194. package/dist/contracts/theme-json.js +3 -8
  195. package/dist/contracts/transfers.d.ts +1 -0
  196. package/dist/contracts/transfers.js +3 -2
  197. package/dist/contracts/usage.d.ts +209 -0
  198. package/dist/contracts/usage.js +124 -12
  199. package/dist/contracts/variables.d.ts +2 -2
  200. package/dist/contracts/variables.js +9 -13
  201. package/dist/contracts/webhooks.d.ts +13 -5
  202. package/dist/contracts/webhooks.js +3 -1
  203. package/dist/contracts/zero.d.ts +72 -81
  204. package/dist/contracts/zero.js +80 -78
  205. package/dist/dashboard-paths/index.d.ts +24 -21
  206. package/dist/dashboard-paths/index.js +35 -47
  207. package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -0
  208. package/dist/dashboard-paths/route-reservations.generated.js +36 -0
  209. package/dist/docs/agent-arrival.d.ts +92 -0
  210. package/dist/docs/agent-arrival.js +227 -0
  211. package/dist/docs/agent-output-policy.d.ts +6 -0
  212. package/dist/docs/agent-output-policy.js +22 -0
  213. package/dist/docs/agent-prose.d.ts +12 -18
  214. package/dist/docs/agent-prose.js +73 -51
  215. package/dist/docs/agent-setup.d.ts +72 -41
  216. package/dist/docs/agent-setup.js +339 -436
  217. package/dist/docs/catalog.d.ts +153 -95
  218. package/dist/docs/catalog.js +163 -48
  219. package/dist/docs/error-docs.d.ts +2098 -1
  220. package/dist/docs/error-docs.js +421 -144
  221. package/dist/docs/index-build.d.ts +0 -1
  222. package/dist/docs/index-build.js +2 -3
  223. package/dist/docs/page-context-prompt.d.ts +77 -0
  224. package/dist/docs/page-context-prompt.js +131 -0
  225. package/dist/docs/reference-urls.d.ts +46 -0
  226. package/dist/docs/reference-urls.js +79 -0
  227. package/dist/docs/search.d.ts +6 -8
  228. package/dist/docs/search.js +10 -13
  229. package/dist/docs/skill-distribution.d.ts +4 -1
  230. package/dist/docs/skill-distribution.js +10 -5
  231. package/dist/docs/skills.d.ts +120 -120
  232. package/dist/docs/skills.js +121 -73
  233. package/dist/docs/start-prompts.d.ts +45 -0
  234. package/dist/docs/start-prompts.js +62 -0
  235. package/dist/domain-exploration.js +9 -13
  236. package/dist/og-template.d.ts +56 -0
  237. package/dist/og-template.js +56 -0
  238. package/dist/slug-policy/index.js +5 -11
  239. package/dist/test-helpers/fetch-stub.js +1 -0
  240. package/dist/utils/browser-credential.d.ts +13 -6
  241. package/dist/utils/browser-credential.js +14 -7
  242. package/dist/utils/build-settings.d.ts +21 -1
  243. package/dist/utils/build-settings.js +133 -15
  244. package/dist/utils/canonical-json.d.ts +3 -5
  245. package/dist/utils/canonical-json.js +4 -5
  246. package/dist/utils/cast-transport.d.ts +5 -17
  247. package/dist/utils/cast-transport.js +6 -19
  248. package/dist/utils/claim-token.js +1 -2
  249. package/dist/utils/comment-avatars.js +4 -6
  250. package/dist/utils/concurrency.js +3 -3
  251. package/dist/utils/content-type.d.ts +7 -6
  252. package/dist/utils/content-type.js +73 -51
  253. package/dist/utils/credential-policy.d.ts +50 -6
  254. package/dist/utils/credential-policy.js +126 -59
  255. package/dist/utils/dns-instructions.js +9 -9
  256. package/dist/utils/egress-policy.fixtures.json +1 -1
  257. package/dist/utils/error-code.js +1 -0
  258. package/dist/utils/generate-space-name.d.ts +3 -8
  259. package/dist/utils/generate-space-name.js +3 -8
  260. package/dist/utils/grant-decision.fixtures.json +2 -2
  261. package/dist/utils/grants.d.ts +0 -1
  262. package/dist/utils/grants.js +7 -8
  263. package/dist/utils/gravatar.d.ts +2 -4
  264. package/dist/utils/gravatar.js +6 -12
  265. package/dist/utils/id-hints.js +14 -23
  266. package/dist/utils/idempotency.d.ts +3 -3
  267. package/dist/utils/idempotency.js +6 -7
  268. package/dist/utils/local-space-state.d.ts +23 -26
  269. package/dist/utils/local-space-state.js +46 -46
  270. package/dist/utils/oauth-signed-query.d.ts +3 -5
  271. package/dist/utils/oauth-signed-query.js +6 -9
  272. package/dist/utils/one-shot-replay.d.ts +7 -6
  273. package/dist/utils/one-shot-replay.js +3 -5
  274. package/dist/utils/page-colors.d.ts +15 -19
  275. package/dist/utils/page-colors.js +23 -45
  276. package/dist/utils/page-fonts.d.ts +1 -1
  277. package/dist/utils/page-fonts.js +8 -20
  278. package/dist/utils/page-preview.d.ts +0 -1
  279. package/dist/utils/page-preview.js +0 -1
  280. package/dist/utils/pages.d.ts +33 -3
  281. package/dist/utils/pages.js +83 -30
  282. package/dist/utils/privacy-regions.d.ts +19 -0
  283. package/dist/utils/privacy-regions.js +91 -0
  284. package/dist/utils/publish-form-data.d.ts +4 -5
  285. package/dist/utils/publish-form-data.js +2 -3
  286. package/dist/utils/publish-policy.d.ts +11 -17
  287. package/dist/utils/publish-policy.fixtures.json +11 -5
  288. package/dist/utils/publish-policy.js +65 -110
  289. package/dist/utils/query-client.d.ts +2 -0
  290. package/dist/utils/query-client.js +10 -1
  291. package/dist/utils/runtime-paths.d.ts +4 -4
  292. package/dist/utils/runtime-paths.js +6 -8
  293. package/dist/utils/runtime-upload.d.ts +9 -0
  294. package/dist/utils/runtime-upload.js +12 -0
  295. package/dist/utils/secure-local-file.d.ts +3 -3
  296. package/dist/utils/secure-local-file.js +23 -40
  297. package/dist/utils/space-config.d.ts +12 -15
  298. package/dist/utils/space-config.js +13 -16
  299. package/dist/utils/static-runtime-policy.d.ts +10 -7
  300. package/dist/utils/static-runtime-policy.fixtures.json +38 -41
  301. package/dist/utils/static-runtime-policy.generated.d.ts +16 -0
  302. package/dist/utils/static-runtime-policy.generated.js +70 -0
  303. package/dist/utils/static-runtime-policy.js +31 -132
  304. package/dist/utils/storage-policy.js +1 -2
  305. package/dist/utils/upload-session.d.ts +4 -1
  306. package/dist/utils/upload-session.js +41 -19
  307. package/dist/utils/version-path.js +11 -19
  308. package/dist/utils/wpcom-auth-redirects.d.ts +5 -14
  309. package/dist/utils/wpcom-auth-redirects.js +1 -4
  310. package/dist/vocabulary.d.ts +13 -13
  311. package/dist/vocabulary.js +52 -27
  312. package/package.json +15 -18
  313. package/dist/agents/client-registry.d.ts +0 -22
  314. package/dist/agents/client-registry.js +0 -41
  315. package/dist/docs/agent-handoff-document.d.ts +0 -15
  316. package/dist/docs/agent-handoff-document.js +0 -180
  317. package/dist/docs/agent-solutions.d.ts +0 -110
  318. package/dist/docs/agent-solutions.js +0 -183
  319. package/dist/utils/query-keys.d.ts +0 -84
  320. package/dist/utils/query-keys.js +0 -108
@@ -1,13 +1,13 @@
1
1
  /** Deployment environment a URL is being built for. */
2
2
  export type DeployEnv = "dev" | "test" | "prod";
3
3
  /** Logical services that each get their own host. */
4
- export type Service = "www" | "api" | "dashboard" | "superadmin" | "cast" | "mcp" | "push" | "fieldguide";
4
+ export type Service = "www" | "api" | "access" | "dashboard" | "superadmin" | "cast" | "mcp" | "push" | "fieldguide";
5
5
  export type EnvDomains = {
6
6
  /** Apex/base origin for the marketing site (the `www` service + bare apex). */
7
7
  readonly apex: string;
8
8
  /** Cookie domain that scopes auth across every subdomain of this env. */
9
9
  readonly cookieDomain: string;
10
- /** Scheme used to build origins. SSL everywhere even local (via portless). */
10
+ /** Scheme used to build origins. SSL everywhere, even local (via portless). */
11
11
  readonly protocol: "http" | "https";
12
12
  /** Fully-qualified hostname per service. */
13
13
  readonly hosts: Readonly<Record<Service, string>>;
@@ -16,18 +16,46 @@ export type EnvDomains = {
16
16
  export declare const PROD_ENV_DOMAIN: "spacefast.com";
17
17
  /** The local-dev apex (portless serves every subdomain over HTTPS). */
18
18
  export declare const DEV_ENV_DOMAIN: "sf.localhost";
19
+ /** The deployed staging apex. Not local: staging is a hosted environment. */
20
+ export declare const STAGING_ENV_DOMAIN: "stattic.net";
21
+ /** The deployed dev apex. Local dev is separate and stays on sf.localhost. */
22
+ export declare const DEV_DEPLOY_ENV_DOMAIN: "stattic.xyz";
19
23
  /**
20
- * Construct the full per-service host map from a single env domain
21
- * (`spacefast.com`, `sf.localhost`, …). One knob services are always
22
- * `<service>.<domain>` over https, with two deliberate exceptions:
23
- * the marketing site sits on the bare prod apex, and the internal handbook
24
- * lives on the view.fast infra apex in prod.
24
+ * The three environments the deploy lanes ship to, keyed by the name each one
25
+ * carries everywhere else: the branch map in scripts/deploy-environment.mjs,
26
+ * the GitHub Environment, and `SPACEFAST_ENV` on the running service. Distinct
27
+ * from `DeployEnv` above, which names the local stacks beside prod.
28
+ */
29
+ export type HostedEnv = "dev" | "staging" | "prod";
30
+ export declare const HOSTED_ENV_DOMAINS: Readonly<Record<HostedEnv, string>>;
31
+ /**
32
+ * The hosted environment a `SPACEFAST_ENV` value names, or null. `production`
33
+ * is accepted for `prod`: it is what the running services already carry, and
34
+ * what their alert routing and email tags key on.
35
+ */
36
+ export declare function hostedEnvFor(environment: string | undefined): HostedEnv | null;
37
+ /** The apex a hosted environment serves from, or null when the name is not one. */
38
+ export declare function hostedEnvDomain(environment: string | undefined): string | null;
39
+ /**
40
+ * The per-service host map for one env domain (`spacefast.com`, `sf.localhost`,
41
+ * …). Services are `<service>.<domain>` over https, with three exceptions: the
42
+ * marketing site takes the bare apex of a hosted environment (so staging
43
+ * rehearses production's shape), and Git push and the internal handbook get
44
+ * their own apexes in prod.
25
45
  */
26
46
  export declare function envDomainsFor(domain: string): EnvDomains;
27
47
  /** Origin (scheme + host, no trailing slash) for a service in an environment. */
28
48
  export declare function serviceOrigin(service: Service, env: DeployEnv): string;
29
- /** Base URL every documentation link catalog entries, skills, error pages — resolves against. */
49
+ /** Base URL every documentation link resolves against: catalog entries, skills, error pages. */
30
50
  export declare const DOCS_BASE_URL: string;
51
+ /**
52
+ * The API origin every first-party client falls back to when nothing else names
53
+ * a provider: no `--api-url`, no `SPACEFAST_API_URL`, no saved profile or
54
+ * checkout link. The CLI and the MCP server share this one literal so a caller
55
+ * cannot be pointed at two different "defaults" depending on which surface it
56
+ * came through.
57
+ */
58
+ export declare const DEFAULT_API_URL: string;
31
59
  /**
32
60
  * The production infrastructure apex. Committed artifacts and anything
33
61
  * customer- or agent-facing (OpenAPI examples, docs, skills) must show this
@@ -37,9 +65,48 @@ export declare const DOCS_BASE_URL: string;
37
65
  export declare const PROD_VIEW_FAST_APEX: "view.fast";
38
66
  /** The local-dev infrastructure apex. Never appears in committed artifacts. */
39
67
  export declare const DEV_VIEW_FAST_APEX: "preview.space";
40
- /** Resolve the infrastructure apex, allowing an explicit deployment override. */
41
- export declare function resolveViewFastApex(environment: Readonly<Partial<Pick<NodeJS.ProcessEnv, "NODE_ENV" | "SPACEFAST_VIEW_FAST_APEX">>>): string;
68
+ /**
69
+ * Resolve the infrastructure apex, allowing an explicit deployment override.
70
+ *
71
+ * Only production has an apex here. Every other hosted environment names its own
72
+ * through `SPACEFAST_VIEW_FAST_APEX`, because a space apex is a zone that must
73
+ * exist, be delegated, and be attached at the provider. Two environments
74
+ * allocating from one apex race for the same hostname, and the loser is a
75
+ * customer's site.
76
+ *
77
+ * When nothing names the apex this falls back to the local one: a caller that
78
+ * cannot say which environment it is has no claim on production's namespace. The
79
+ * control plane's env schema is what holds deployments to naming it; builds and
80
+ * browser bundles import this registry only for its host scheme.
81
+ */
82
+ export declare function resolveViewFastApex(environment: Readonly<Partial<Pick<NodeJS.ProcessEnv, "NODE_ENV" | "SPACEFAST_ENV" | "SPACEFAST_VIEW_FAST_APEX">>>): string;
42
83
  /** Infrastructure apex used for public managed/custom domains. */
43
84
  export declare const VIEW_FAST_APEX: string;
44
- /** Reserved first-label prefix for the management namespace; users may not register it. */
45
- export declare const MANAGEMENT_LABEL_PREFIX: "wpc-manage-";
85
+ /**
86
+ * The Spacefast-owned apex under which a LINKED TEST TENANT's simulated sites and
87
+ * hostname namespaces materialize (partner-operations spec §1). It is never
88
+ * served by real infrastructure — nothing points DNS at it and no cert is
89
+ * provisioned — so it exists purely as a collision-free namespace: a test
90
+ * tenant's hostnames are `{label}.{tenant}.{TEST_APEX}` (per-tenant, see
91
+ * `testApexForTenant`), which is also the marker the runtime-API transport uses
92
+ * to recognize a simulated box and short-circuit rather than reach a real one.
93
+ *
94
+ * Defaults to `test.{VIEW_FAST_APEX}` so every environment gets a distinct test
95
+ * apex for free; a deployment can override it with `SPACEFAST_TEST_APEX`.
96
+ */
97
+ export declare function resolveTestApex(environment: Readonly<Partial<Pick<NodeJS.ProcessEnv, "NODE_ENV" | "SPACEFAST_ENV" | "SPACEFAST_VIEW_FAST_APEX" | "SPACEFAST_TEST_APEX">>>): string;
98
+ /** The resolved test-mode apex for this process. */
99
+ export declare const TEST_APEX: string;
100
+ export declare function testApexForTenant(tenantId: string, testApex?: string): string;
101
+ /**
102
+ * Whether a hostname belongs to the test-mode apex (a simulated box).
103
+ *
104
+ * STRICTLY a suffix check — the apex ITSELF is never a simulated hostname. Every
105
+ * simulated hostname is `{label}.{tenant}.{TEST_APEX}` (two labels deep), while
106
+ * the bare apex is reachable as a LIVE space's hostname: `TEST_APEX` defaults to
107
+ * `test.{VIEW_FAST_APEX}`, so a live space slugged `test` is served at exactly
108
+ * `test.view.fast`. Matching the apex would make every Runtime-API call for that
109
+ * space short-circuit to a synthetic response — engine installs reporting
110
+ * `installed` and publishes reporting success while nothing ever reaches the box.
111
+ */
112
+ export declare function isTestApexHostname(hostname: string, testApex?: string): boolean;
@@ -1,30 +1,57 @@
1
- // THE single source of truth for Spacefast hostnames and per-service origins,
2
- // across every environment. Brand strings, app URL builders, CORS allowlists,
3
- // cookie scope, and redirect rules all derive from here so a domain change is a
4
- // one-file edit instead of the ~100-site sweep this migration had to untangle.
1
+ // THE single source of truth for Spacefast hostnames and per-service origins.
2
+ // Brand strings, app URL builders, CORS allowlists, cookie scope, and redirect
3
+ // rules all derive from here, so a domain change is a one-file edit.
5
4
  //
6
- // Env-var NAMES intentionally stay `SPACEFAST_*` (see migration plan); only the
7
- // VALUES they carry are produced from this registry.
5
+ // Env-var NAMES stay `SPACEFAST_*`; only the VALUES come from this registry.
8
6
  /** The production apex. Everything else about prod derives from this string. */
9
7
  export const PROD_ENV_DOMAIN = "spacefast.com";
10
8
  /** The local-dev apex (portless serves every subdomain over HTTPS). */
11
9
  export const DEV_ENV_DOMAIN = "sf.localhost";
10
+ /** The deployed staging apex. Not local: staging is a hosted environment. */
11
+ export const STAGING_ENV_DOMAIN = "stattic.net";
12
+ /** The deployed dev apex. Local dev is separate and stays on sf.localhost. */
13
+ export const DEV_DEPLOY_ENV_DOMAIN = "stattic.xyz";
14
+ export const HOSTED_ENV_DOMAINS = {
15
+ dev: DEV_DEPLOY_ENV_DOMAIN,
16
+ staging: STAGING_ENV_DOMAIN,
17
+ prod: PROD_ENV_DOMAIN,
18
+ };
19
+ /**
20
+ * The hosted environment a `SPACEFAST_ENV` value names, or null. `production`
21
+ * is accepted for `prod`: it is what the running services already carry, and
22
+ * what their alert routing and email tags key on.
23
+ */
24
+ export function hostedEnvFor(environment) {
25
+ if (!environment)
26
+ return null;
27
+ const normalized = environment.trim().toLowerCase();
28
+ if (normalized === "production")
29
+ return "prod";
30
+ return Object.hasOwn(HOSTED_ENV_DOMAINS, normalized) ? normalized : null;
31
+ }
32
+ /** The apex a hosted environment serves from, or null when the name is not one. */
33
+ export function hostedEnvDomain(environment) {
34
+ const hosted = hostedEnvFor(environment);
35
+ return hosted ? HOSTED_ENV_DOMAINS[hosted] : null;
36
+ }
12
37
  /**
13
- * Construct the full per-service host map from a single env domain
14
- * (`spacefast.com`, `sf.localhost`, …). One knob services are always
15
- * `<service>.<domain>` over https, with two deliberate exceptions:
16
- * the marketing site sits on the bare prod apex, and the internal handbook
17
- * lives on the view.fast infra apex in prod.
38
+ * The per-service host map for one env domain (`spacefast.com`, `sf.localhost`,
39
+ * …). Services are `<service>.<domain>` over https, with three exceptions: the
40
+ * marketing site takes the bare apex of a hosted environment (so staging
41
+ * rehearses production's shape), and Git push and the internal handbook get
42
+ * their own apexes in prod.
18
43
  */
19
44
  export function envDomainsFor(domain) {
20
45
  const prod = domain === PROD_ENV_DOMAIN;
46
+ const hosted = Object.values(HOSTED_ENV_DOMAINS).some((hostedDomain) => hostedDomain === domain);
21
47
  return {
22
48
  apex: domain,
23
49
  cookieDomain: `.${domain}`,
24
50
  protocol: "https",
25
51
  hosts: {
26
- www: prod ? domain : `www.${domain}`,
52
+ www: hosted ? domain : `www.${domain}`,
27
53
  api: `api.${domain}`,
54
+ access: `access.${domain}`,
28
55
  dashboard: `my.${domain}`,
29
56
  superadmin: `superadmin.${domain}`,
30
57
  cast: `cast.${domain}`,
@@ -35,13 +62,11 @@ export function envDomainsFor(domain) {
35
62
  };
36
63
  }
37
64
  const PROD = envDomainsFor(PROD_ENV_DOMAIN);
38
- // Local dev runs every service under *.sf.localhost via portless (HTTPS, no
39
- // ports in the URL portless proxies the raw dev ports). `remove all other
40
- // portless records` per the migration spec: sf.localhost is the only TLD.
65
+ // Local dev runs every service under *.sf.localhost via portless, which proxies
66
+ // the raw dev ports so URLs carry no port. sf.localhost is the only local TLD.
41
67
  const DEV = envDomainsFor(DEV_ENV_DOMAIN);
42
- // The test stack shares the sf.localhost hostname scheme but runs as its own
43
- // isolated compose project on its own ports/resources (see ports config), so it
44
- // can run side-by-side with dev. URL host scheme is identical to dev.
68
+ // The test stack uses dev's hostname scheme but its own isolated compose project
69
+ // and ports (see ports config), so the two run side by side.
45
70
  const TEST = DEV;
46
71
  const BY_ENV = {
47
72
  dev: DEV,
@@ -53,8 +78,16 @@ export function serviceOrigin(service, env) {
53
78
  const d = BY_ENV[env];
54
79
  return `${d.protocol}://${d.hosts[service]}`;
55
80
  }
56
- /** Base URL every documentation link catalog entries, skills, error pages — resolves against. */
81
+ /** Base URL every documentation link resolves against: catalog entries, skills, error pages. */
57
82
  export const DOCS_BASE_URL = `${serviceOrigin("www", "prod")}/docs`;
83
+ /**
84
+ * The API origin every first-party client falls back to when nothing else names
85
+ * a provider: no `--api-url`, no `SPACEFAST_API_URL`, no saved profile or
86
+ * checkout link. The CLI and the MCP server share this one literal so a caller
87
+ * cannot be pointed at two different "defaults" depending on which surface it
88
+ * came through.
89
+ */
90
+ export const DEFAULT_API_URL = serviceOrigin("api", "prod");
58
91
  // --- Apexes that are NOT spacefast.com subdomains -------------------------------
59
92
  /**
60
93
  * The production infrastructure apex. Committed artifacts and anything
@@ -65,18 +98,84 @@ export const DOCS_BASE_URL = `${serviceOrigin("www", "prod")}/docs`;
65
98
  export const PROD_VIEW_FAST_APEX = "view.fast";
66
99
  /** The local-dev infrastructure apex. Never appears in committed artifacts. */
67
100
  export const DEV_VIEW_FAST_APEX = "preview.space";
68
- /** Resolve the infrastructure apex, allowing an explicit deployment override. */
101
+ /**
102
+ * Resolve the infrastructure apex, allowing an explicit deployment override.
103
+ *
104
+ * Only production has an apex here. Every other hosted environment names its own
105
+ * through `SPACEFAST_VIEW_FAST_APEX`, because a space apex is a zone that must
106
+ * exist, be delegated, and be attached at the provider. Two environments
107
+ * allocating from one apex race for the same hostname, and the loser is a
108
+ * customer's site.
109
+ *
110
+ * When nothing names the apex this falls back to the local one: a caller that
111
+ * cannot say which environment it is has no claim on production's namespace. The
112
+ * control plane's env schema is what holds deployments to naming it; builds and
113
+ * browser bundles import this registry only for its host scheme.
114
+ */
69
115
  export function resolveViewFastApex(environment) {
116
+ const explicit = environment.SPACEFAST_VIEW_FAST_APEX?.trim().toLowerCase();
117
+ if (explicit)
118
+ return explicit;
119
+ // SPACEFAST_ENV names a hosted environment only on a deployed service, where
120
+ // NODE_ENV is production. `dev` is also the local stack's name, and a local
121
+ // stack must keep the local apex rather than allocate under hosted dev's.
70
122
  const nodeEnvironment = environment.NODE_ENV?.trim().toLowerCase();
71
- return (environment.SPACEFAST_VIEW_FAST_APEX?.trim().toLowerCase() ||
72
- (["prod", "production"].includes(nodeEnvironment ?? "")
73
- ? PROD_VIEW_FAST_APEX
74
- : DEV_VIEW_FAST_APEX));
123
+ if (!["prod", "production"].includes(nodeEnvironment ?? ""))
124
+ return DEV_VIEW_FAST_APEX;
125
+ return hostedEnvFor(environment.SPACEFAST_ENV) === "prod"
126
+ ? PROD_VIEW_FAST_APEX
127
+ : DEV_VIEW_FAST_APEX;
75
128
  }
76
129
  /** Infrastructure apex used for public managed/custom domains. */
77
130
  export const VIEW_FAST_APEX = resolveViewFastApex(typeof process === "undefined" ? {} : process.env);
78
- // Default space hostnames are `{hostLabel}.{VIEW_FAST_APEX}`; internal
79
- // management hostnames share the same apex under the reserved `wpc-manage-`
80
- // label prefix and are never public space URLs.
81
- /** Reserved first-label prefix for the management namespace; users may not register it. */
82
- export const MANAGEMENT_LABEL_PREFIX = "wpc-manage-";
131
+ // Default space hostnames are `{hostLabel}.{VIEW_FAST_APEX}`.
132
+ // --- Test-mode (simulated) apex -------------------------------------------------
133
+ /**
134
+ * The Spacefast-owned apex under which a LINKED TEST TENANT's simulated sites and
135
+ * hostname namespaces materialize (partner-operations spec §1). It is never
136
+ * served by real infrastructure — nothing points DNS at it and no cert is
137
+ * provisioned — so it exists purely as a collision-free namespace: a test
138
+ * tenant's hostnames are `{label}.{tenant}.{TEST_APEX}` (per-tenant, see
139
+ * `testApexForTenant`), which is also the marker the runtime-API transport uses
140
+ * to recognize a simulated box and short-circuit rather than reach a real one.
141
+ *
142
+ * Defaults to `test.{VIEW_FAST_APEX}` so every environment gets a distinct test
143
+ * apex for free; a deployment can override it with `SPACEFAST_TEST_APEX`.
144
+ */
145
+ export function resolveTestApex(environment) {
146
+ const explicit = environment.SPACEFAST_TEST_APEX?.trim().toLowerCase();
147
+ if (explicit)
148
+ return explicit;
149
+ return `test.${resolveViewFastApex(environment)}`;
150
+ }
151
+ /** The resolved test-mode apex for this process. */
152
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- isomorphic process guard: this registry is imported by browser bundles where `process` is undefined (same guard as VIEW_FAST_APEX above).
153
+ export const TEST_APEX = resolveTestApex(typeof process === "undefined" ? {} : process.env);
154
+ // A tenant id (`ten_<rand>`) as a DNS label: lowercase, `_`→`-`, non-label chars
155
+ // dropped. Every test tenant gets its OWN apex under `TEST_APEX` so the GLOBAL
156
+ // `hostname_namespace_apex_uq` never collides at the second test tenant, and so
157
+ // a test label can never steer a live space.
158
+ export function testApexForTenant(tenantId, testApex = TEST_APEX) {
159
+ const label = tenantId
160
+ .trim()
161
+ .toLowerCase()
162
+ .replaceAll("_", "-")
163
+ .replace(/[^a-z0-9-]/g, "")
164
+ .replace(/^-+|-+$/g, "");
165
+ return `${label || "tenant"}.${testApex}`;
166
+ }
167
+ /**
168
+ * Whether a hostname belongs to the test-mode apex (a simulated box).
169
+ *
170
+ * STRICTLY a suffix check — the apex ITSELF is never a simulated hostname. Every
171
+ * simulated hostname is `{label}.{tenant}.{TEST_APEX}` (two labels deep), while
172
+ * the bare apex is reachable as a LIVE space's hostname: `TEST_APEX` defaults to
173
+ * `test.{VIEW_FAST_APEX}`, so a live space slugged `test` is served at exactly
174
+ * `test.view.fast`. Matching the apex would make every Runtime-API call for that
175
+ * space short-circuit to a synthetic response — engine installs reporting
176
+ * `installed` and publishes reporting success while nothing ever reaches the box.
177
+ */
178
+ export function isTestApexHostname(hostname, testApex = TEST_APEX) {
179
+ const normalized = hostname.trim().toLowerCase().replace(/\.$/, "");
180
+ return normalized.endsWith(`.${testApex}`);
181
+ }
@@ -1,3 +1,2 @@
1
- // Public entry for the centralized domain/URL registry.
2
- // Consumers import from "@spacefast/common/config".
1
+ // Consumers import the domain/URL registry from "@spacefast/common/config".
3
2
  export * from "./domains.js";
@@ -4,20 +4,13 @@ export const abuseReportCategorySchema = z
4
4
  .enum(["malware", "phishing", "spam", "copyright", "illegal_content", "other"])
5
5
  .describe("Reported abuse category.");
6
6
  export const abuseReportCreateSchema = z.object({
7
- url: z.string().url().describe("Full URL of the content being reported."),
7
+ url: z.string().url().describe("URL of the reported content."),
8
8
  category: abuseReportCategorySchema,
9
- description: z
10
- .string()
11
- .trim()
12
- .min(10)
13
- .max(5000)
14
- .describe("What the reporter saw; 10-5000 characters."),
9
+ description: z.string().trim().min(10).max(5000).describe("What the reporter saw."),
15
10
  });
16
11
  export const abuseReportStatusSchema = z
17
12
  .enum(["new", "reviewing", "actioned", "closed"])
18
13
  .describe("Triage status of the report.");
19
- // Triage state machine: reports move forward through review; `closed` reports
20
- // can be reopened into `reviewing` (and only there) when new evidence lands.
21
14
  export const ABUSE_REPORT_STATUS_TRANSITIONS = {
22
15
  new: ["reviewing", "actioned", "closed"],
23
16
  reviewing: ["actioned", "closed"],
@@ -29,33 +22,25 @@ export const abuseReportSchema = z.object({
29
22
  status: abuseReportStatusSchema,
30
23
  category: abuseReportCategorySchema,
31
24
  url: z.string().describe("Reported URL."),
32
- spaceId: z
33
- .string()
34
- .nullable()
35
- .describe("Space the reported hostname resolved to, when resolution succeeds."),
25
+ spaceId: z.string().nullable().describe("Space the reported hostname resolved to."),
36
26
  createdAt: z.string().datetime().describe("When the report was filed."),
37
27
  });
38
- // Operator triage view: includes intake details the public receipt omits.
28
+ // Intake details the public receipt omits.
39
29
  export const superadminAbuseReportSchema = abuseReportSchema.extend({
40
- hostname: z.string().describe("Normalized hostname extracted from the reported URL."),
41
- tenantId: z.string().nullable().describe("Tenant owning the resolved space, when resolved."),
30
+ hostname: z.string().describe("Normalized hostname from the reported URL."),
31
+ tenantId: z.string().nullable().describe("Tenant owning the resolved space."),
42
32
  description: z.string().describe("Reporter-supplied description."),
43
33
  updatedAt: z.string().datetime().describe("Last triage update."),
44
34
  });
45
35
  export const superadminAbuseReportListQuerySchema = cursorListQuerySchema.extend({
46
- status: abuseReportStatusSchema.optional().describe("Filter the triage list by status."),
47
- spaceId: z
48
- .string()
49
- .trim()
50
- .min(1)
51
- .optional()
52
- .describe("Filter to reports that resolved to one Space."),
36
+ status: abuseReportStatusSchema.optional().describe("Filter by status."),
37
+ spaceId: z.string().trim().min(1).optional().describe("Filter to reports for this Space."),
53
38
  hostname: z
54
39
  .string()
55
40
  .trim()
56
41
  .min(1)
57
42
  .optional()
58
- .describe("Filter by the normalized reported hostname; matching is case-insensitive."),
43
+ .describe("Filter by normalized hostname, case-insensitively."),
59
44
  });
60
45
  export const superadminAbuseReportPatchSchema = z.object({
61
46
  status: abuseReportStatusSchema.describe("New triage status; must be a valid transition from the current status."),
@@ -0,0 +1,86 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The scopes any interactive authorization may grant — THE list every surface
4
+ * derives from. The device approval page, OAuth consent, the CLI receipt, and
5
+ * the server's approval validator all import this instead of keeping copies.
6
+ *
7
+ * This is the shared API set — NOT the granular `spaces:publish` /
8
+ * `builds:trigger` scopes, whose actions `publish:write` already covers. A
9
+ * person approving a device never sees two checkboxes that mean the same grant,
10
+ * and this set equals the hosted-MCP resource's scopes so the two converge.
11
+ */
12
+ export declare const APPROVABLE_SCOPES: readonly ["teams:read", "teams:create", "spaces:read", "spaces:write", "spaces:rename", "publish:write", "domains:read", "domains:write"];
13
+ export type ApprovableScope = (typeof APPROVABLE_SCOPES)[number];
14
+ /**
15
+ * Whether a scope, when approved, is bounded by the holder's team role. This is
16
+ * DERIVED from `isTenantAdmissionAction` — the same declaration the credential
17
+ * compiler mints from — so it can never disagree with what actually gets minted:
18
+ *
19
+ * - "team": grants at least one team-role action — authority OVER a team's
20
+ * resources, bounded by the approver's LIVE role there on every request.
21
+ * - "account": every action is tenant-admission (never role-bounded), so no
22
+ * team role gates it and any member can approve it (creating a
23
+ * not-yet-existing team).
24
+ *
25
+ * Drives grantability (`scopeGrantableByRole`); the compiler owns the resource.
26
+ */
27
+ export type ScopePlane = "team" | "account";
28
+ export declare const SCOPE_PLANES: Record<ApprovableScope, ScopePlane>;
29
+ export declare const ACCESS_PROFILE_IDS: readonly ["read", "agent"];
30
+ export type AccessProfileId = (typeof ACCESS_PROFILE_IDS)[number];
31
+ export declare const AccessProfileIdSchema: z.ZodEnum<{
32
+ read: "read";
33
+ agent: "agent";
34
+ }>;
35
+ export type AccessProfile = Readonly<{
36
+ id: AccessProfileId;
37
+ /** The ONE name every surface prints: CLI request line and receipt, approval
38
+ * page heading, consent screen, ledger row, email. */
39
+ label: string;
40
+ /** One sentence a person reads INSTEAD of the scope list. Reviewed copy —
41
+ * the derived scope list under it stays authoritative, so this can only ever
42
+ * be stale, never wrong about what is granted. */
43
+ blurb: string;
44
+ /** The negative space: notable things this profile deliberately can't do,
45
+ * shown under the blurb. Optional — omit when there's nothing worth calling
46
+ * out (a read-only profile speaks for itself). */
47
+ limits?: string;
48
+ scopes: readonly ApprovableScope[];
49
+ }>;
50
+ /**
51
+ * Named grant profiles. A profile is REQUEST INTENT — one word on device start
52
+ * with a server-owned meaning — never authority. Scopes are the grant
53
+ * vocabulary; actions are authority. The default is deliberately the full
54
+ * approvable set, matching what a `vercel login`/`gh auth login` grants:
55
+ * everything the credential's holder can do, capped per team by their live
56
+ * role at request time.
57
+ */
58
+ export declare const ACCESS_PROFILES: {
59
+ readonly read: {
60
+ readonly id: "read";
61
+ readonly label: "Read-only access";
62
+ readonly blurb: "Read your teams, sites, and domains. Changes nothing.";
63
+ readonly scopes: readonly ["teams:read", "spaces:read", "domains:read"];
64
+ };
65
+ readonly agent: {
66
+ readonly id: "agent";
67
+ readonly label: "Agent access";
68
+ readonly blurb: "Create and publish sites, manage domains and DNS, create teams, and use Spacefast tools.";
69
+ readonly limits: "It can't delete sites, manage team members, change billing, or mint credentials.";
70
+ readonly scopes: readonly ["teams:read", "teams:create", "spaces:read", "spaces:write", "spaces:rename", "publish:write", "domains:read", "domains:write"];
71
+ };
72
+ };
73
+ export declare const DEFAULT_ACCESS_PROFILE_ID: AccessProfileId;
74
+ /** Parse an untyped scope list (wire, storage) into approvable scopes, in
75
+ * checklist order, deduplicated. Unknown entries grant nothing and are dropped. */
76
+ export declare function approvableScopesFrom(scopes: readonly string[] | null | undefined): ApprovableScope[];
77
+ /** The profile a scope set IS — exact set equality — or null once edited.
78
+ * Profiles are pinned distinct by test, so this inverse is total. */
79
+ export declare function accessProfileForScopes(scopes: readonly string[] | null | undefined): AccessProfile | null;
80
+ /**
81
+ * What every surface prints for a granted scope set: a profile's label, or
82
+ * `Custom (Agent access, minus …)` when the set is a strict subset of a
83
+ * profile, or plain `Custom`. Derived, never stored — a profile redefinition
84
+ * can never make an old receipt lie about its scopes.
85
+ */
86
+ export declare function accessGrantLabel(scopes: readonly string[] | null | undefined, scopeLabel?: (scope: ApprovableScope) => string): string;
@@ -0,0 +1,89 @@
1
+ import { z } from "zod";
2
+ import { OAUTH_SHARED_API_RESOURCE_SCOPES } from "./oauth-resources.js";
3
+ import { isTenantAdmissionAction, oauthScopeActions } from "./oauth-scope-actions.js";
4
+ /**
5
+ * The scopes any interactive authorization may grant — THE list every surface
6
+ * derives from. The device approval page, OAuth consent, the CLI receipt, and
7
+ * the server's approval validator all import this instead of keeping copies.
8
+ *
9
+ * This is the shared API set — NOT the granular `spaces:publish` /
10
+ * `builds:trigger` scopes, whose actions `publish:write` already covers. A
11
+ * person approving a device never sees two checkboxes that mean the same grant,
12
+ * and this set equals the hosted-MCP resource's scopes so the two converge.
13
+ */
14
+ export const APPROVABLE_SCOPES = [...OAUTH_SHARED_API_RESOURCE_SCOPES];
15
+ export const SCOPE_PLANES =
16
+ // SAFETY: mapped over every APPROVABLE_SCOPES entry, so the record is total.
17
+ Object.fromEntries(APPROVABLE_SCOPES.map((scope) => [
18
+ scope,
19
+ oauthScopeActions([scope]).every(isTenantAdmissionAction) ? "account" : "team",
20
+ ]));
21
+ export const ACCESS_PROFILE_IDS = ["read", "agent"];
22
+ export const AccessProfileIdSchema = z.enum(ACCESS_PROFILE_IDS);
23
+ /**
24
+ * Named grant profiles. A profile is REQUEST INTENT — one word on device start
25
+ * with a server-owned meaning — never authority. Scopes are the grant
26
+ * vocabulary; actions are authority. The default is deliberately the full
27
+ * approvable set, matching what a `vercel login`/`gh auth login` grants:
28
+ * everything the credential's holder can do, capped per team by their live
29
+ * role at request time.
30
+ */
31
+ export const ACCESS_PROFILES = {
32
+ // Read-only. It can open a hosted MCP session and invoke tools (each tool call
33
+ // is re-gated by its own API scope, so only reads succeed), and it can observe
34
+ // the MCP console. It cannot MANAGE the console — closing sessions and
35
+ // approving paused executions are gated on `spaces:write` (see mcp/service.ts)
36
+ // — so it still changes nothing.
37
+ read: {
38
+ id: "read",
39
+ label: "Read-only access",
40
+ blurb: "Read your teams, sites, and domains. Changes nothing.",
41
+ scopes: ["teams:read", "spaces:read", "domains:read"],
42
+ },
43
+ agent: {
44
+ id: "agent",
45
+ label: "Agent access",
46
+ blurb: "Create and publish sites, manage domains and DNS, create teams, and use Spacefast tools.",
47
+ limits: "It can't delete sites, manage team members, change billing, or mint credentials.",
48
+ scopes: [...APPROVABLE_SCOPES],
49
+ },
50
+ };
51
+ export const DEFAULT_ACCESS_PROFILE_ID = "agent";
52
+ function isApprovableScope(scope) {
53
+ return APPROVABLE_SCOPES.some((known) => known === scope);
54
+ }
55
+ /** Parse an untyped scope list (wire, storage) into approvable scopes, in
56
+ * checklist order, deduplicated. Unknown entries grant nothing and are dropped. */
57
+ export function approvableScopesFrom(scopes) {
58
+ const requested = new Set(scopes ?? []);
59
+ return APPROVABLE_SCOPES.filter((scope) => requested.has(scope));
60
+ }
61
+ /** The profile a scope set IS — exact set equality — or null once edited.
62
+ * Profiles are pinned distinct by test, so this inverse is total. */
63
+ export function accessProfileForScopes(scopes) {
64
+ const granted = new Set(approvableScopesFrom(scopes));
65
+ return (Object.values(ACCESS_PROFILES).find((profile) => profile.scopes.length === granted.size &&
66
+ profile.scopes.every((scope) => granted.has(scope))) ?? null);
67
+ }
68
+ /**
69
+ * What every surface prints for a granted scope set: a profile's label, or
70
+ * `Custom (Agent access, minus …)` when the set is a strict subset of a
71
+ * profile, or plain `Custom`. Derived, never stored — a profile redefinition
72
+ * can never make an old receipt lie about its scopes.
73
+ */
74
+ export function accessGrantLabel(scopes, scopeLabel = (scope) => scope) {
75
+ const granted = approvableScopesFrom(scopes);
76
+ const exact = accessProfileForScopes(granted);
77
+ if (exact) {
78
+ return exact.label;
79
+ }
80
+ const grantedSet = new Set(granted);
81
+ // Narrowest containing profile names the diff; `read` precedes `agent`.
82
+ const profiles = Object.values(ACCESS_PROFILES);
83
+ const superset = profiles.find((profile) => granted.length > 0 && granted.every((scope) => profile.scopes.includes(scope)));
84
+ if (!superset) {
85
+ return "Custom";
86
+ }
87
+ const missing = superset.scopes.filter((scope) => !grantedSet.has(scope));
88
+ return `Custom (${superset.label}, minus ${missing.map((scope) => scopeLabel(scope)).join(", ")})`;
89
+ }