@undefineds.co/xpod 0.4.29 → 0.4.30

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 (179) hide show
  1. package/dist/api/ApiServer.js +4 -0
  2. package/dist/api/ai-gateway/AiGatewayService.d.ts +55 -0
  3. package/dist/api/ai-gateway/AiGatewayService.js +145 -6
  4. package/dist/api/ai-gateway/auth/AiConnectionsInvocationKeyIssuer.js +5 -3
  5. package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +16 -49
  6. package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +6 -71
  7. package/dist/api/ai-gateway/auth/InvocationTokenAuthenticator.js +1 -0
  8. package/dist/api/ai-gateway/auth/InvocationTokenCodec.d.ts +4 -0
  9. package/dist/api/ai-gateway/auth/InvocationTokenCodec.js +8 -1
  10. package/dist/api/ai-gateway/connect/FileSessionImportAdapter.d.ts +3 -0
  11. package/dist/api/ai-gateway/connect/FileSessionImportAdapter.js +23 -3
  12. package/dist/api/ai-gateway/connect/SessionImportProfiles.d.ts +9 -1
  13. package/dist/api/ai-gateway/connect/SessionImportProfiles.js +4 -3
  14. package/dist/api/ai-gateway/connect/index.d.ts +40 -0
  15. package/dist/api/ai-gateway/connect/index.js +194 -4
  16. package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +1 -1
  17. package/dist/api/ai-gateway/pod/OwnerPodAccess.js +5 -0
  18. package/dist/api/ai-gateway/pod/PodBaseUrlResolver.d.ts +11 -2
  19. package/dist/api/ai-gateway/pod/PodBaseUrlResolver.js +72 -10
  20. package/dist/api/ai-gateway/quota/ProviderQuotaAdapter.js +1 -1
  21. package/dist/api/ai-gateway/routing/ModelRouter.d.ts +7 -0
  22. package/dist/api/ai-gateway/routing/ModelRouter.js +13 -10
  23. package/dist/api/ai-gateway/service-access/AiConnectionsServiceAccess.d.ts +1 -5
  24. package/dist/api/ai-gateway/service-access/AiConnectionsServiceAccess.js +8 -63
  25. package/dist/api/auth/AuthContext.d.ts +4 -0
  26. package/dist/api/auth/ClientCredentialsAuthenticator.js +11 -5
  27. package/dist/api/auth/SolidSessionFactory.d.ts +50 -4
  28. package/dist/api/auth/SolidSessionFactory.js +114 -4
  29. package/dist/api/chatkit/pod-store.d.ts +8 -1
  30. package/dist/api/chatkit/pod-store.js +54 -3
  31. package/dist/api/container/common.js +34 -60
  32. package/dist/api/container/index.js +0 -3
  33. package/dist/api/container/routes.js +1 -8
  34. package/dist/api/container/types.d.ts +2 -9
  35. package/dist/api/container/types.js +0 -5
  36. package/dist/api/handlers/AiGatewayManagementHandler.d.ts +4 -12
  37. package/dist/api/handlers/AiGatewayManagementHandler.js +18 -258
  38. package/dist/api/middleware/AuthMiddleware.js +29 -1
  39. package/dist/api/runs/PiAgentRuntimeDriver.js +6 -1
  40. package/dist/cli/index.js +2 -1
  41. package/dist/identity/oidc/PodOwnershipResolver.js +16 -6
  42. package/dist/runtime/ingress-port.d.ts +9 -2
  43. package/dist/runtime/ingress-port.js +7 -2
  44. package/dist/runtime/local-secret-file.d.ts +1 -1
  45. package/dist/runtime/runner/node/CommunitySolidServerCssRunner.d.ts +3 -1
  46. package/dist/runtime/runner/node/CommunitySolidServerCssRunner.js +31 -1
  47. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +3 -1
  48. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +96 -67
  49. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +2 -1
  50. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +2 -2
  51. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.d.ts +1 -2
  52. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.js +2 -2
  53. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +81 -23
  54. package/node_modules/@undefineds.co/ai-connections/dist/AiOfferingDetails.d.ts +2 -1
  55. package/node_modules/@undefineds.co/ai-connections/dist/AiOfferingDetails.js +2 -2
  56. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +2 -1
  57. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.js +2 -2
  58. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderHeader.js +1 -1
  59. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/fingerprint.d.ts +1 -0
  60. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/fingerprint.js +7 -0
  61. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/normalize.d.ts +1 -2
  62. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/normalize.js +0 -33
  63. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/request.d.ts +1 -1
  64. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/request.js +68 -22
  65. package/node_modules/@undefineds.co/ai-connections/dist/contract/client/types.d.ts +37 -10
  66. package/node_modules/@undefineds.co/ai-connections/dist/controller.js +5 -54
  67. package/node_modules/@undefineds.co/ai-connections/dist/error-wording.js +7 -2
  68. package/node_modules/@undefineds.co/ai-connections/dist/service-access-resources.cjs +405 -0
  69. package/node_modules/@undefineds.co/ai-connections/dist/service-access-resources.d.ts +14 -0
  70. package/node_modules/@undefineds.co/ai-connections/dist/service-access-resources.js +36 -0
  71. package/node_modules/@undefineds.co/ai-connections/dist/service-access.js +5 -26
  72. package/node_modules/@undefineds.co/ai-connections/package.json +10 -2
  73. package/node_modules/@undefineds.co/extension-sdk/dist/solid-permissions.js +131 -13
  74. package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +7 -1
  75. package/node_modules/@undefineds.co/extension-sdk/package.json +1 -1
  76. package/node_modules/@undefineds.co/pod-collections/dist/index.cjs +38 -5
  77. package/node_modules/@undefineds.co/pod-collections/dist/mapping.d.ts +8 -0
  78. package/node_modules/@undefineds.co/pod-collections/dist/mapping.js +34 -0
  79. package/node_modules/@undefineds.co/pod-collections/dist/mutations.d.ts +2 -1
  80. package/node_modules/@undefineds.co/pod-collections/dist/mutations.js +6 -4
  81. package/node_modules/@undefineds.co/pod-collections/dist/sync.js +4 -3
  82. package/node_modules/@undefineds.co/pod-collections/package.json +1 -1
  83. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.d.ts +1 -1
  84. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.js +2 -2
  85. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpViews.js +2 -2
  86. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.d.ts +3 -3
  87. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.js +5 -5
  88. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.d.ts +5 -5
  89. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.js +5 -5
  90. package/node_modules/@undefineds.co/shared-ui/dist/theme.css +32 -5
  91. package/node_modules/@undefineds.co/shared-ui/package.json +1 -1
  92. package/package.json +2 -2
  93. package/static/app/assets/main.css +1 -1
  94. package/static/app/assets/main.js +18 -18
  95. package/static/dashboard/assets/{AccountAuthBoundary-Dv52SZc6.js → AccountAuthBoundary-CuqXFEW4.js} +1 -1
  96. package/static/dashboard/assets/{DesktopOidcCallback-DaT_Rumf.js → DesktopOidcCallback-7i-yOvPW.js} +1 -1
  97. package/static/dashboard/assets/DevicePages-D1m_BNtX.js +2 -0
  98. package/static/dashboard/assets/ModelsPage-0qWGa5Y6.js +12 -0
  99. package/static/dashboard/assets/{PaneListHeader-DtvnRmFq.js → PaneListHeader-uWdPgJ0E.js} +1 -1
  100. package/static/dashboard/assets/{PodDeletionAuthorizationPanel-BJk_8rJq.js → PodDeletionAuthorizationPanel-C22bhZeD.js} +3 -3
  101. package/static/dashboard/assets/{PodPage-C4gzEspV.js → PodPage-Dyo3yCb-.js} +1 -1
  102. package/static/dashboard/assets/{SystemSettingsPage-HaNrP1H1.js → SystemSettingsPage-CeX-oG_a.js} +1 -1
  103. package/static/dashboard/assets/{TasksPage-BdO5K2L7.js → TasksPage-B0EoyRYT.js} +1 -1
  104. package/static/dashboard/assets/{WebDesktopEntry-Cj9_ZfJU.js → WebDesktopEntry-DT44RrTa.js} +1 -1
  105. package/static/dashboard/assets/XpodAiConnectionsPodStore-EXCqZPmb.js +3 -0
  106. package/static/dashboard/assets/XpodShellApp-BEzi4f1j.css +1 -0
  107. package/static/dashboard/assets/XpodShellApp-DAFsxamX.js +4 -0
  108. package/static/dashboard/assets/{account-session-DlcKNG7k.js → account-session-rRAOF7nF.js} +1 -1
  109. package/static/dashboard/assets/{button-wslF55uZ.js → button-WVnQUNG-.js} +1 -1
  110. package/static/dashboard/assets/{collection-runtime-uxxgEu13.js → collection-runtime-BAJQ8Qub.js} +1 -1
  111. package/static/dashboard/assets/{createLucideIcon-BJQl82Qk.js → createLucideIcon-BWelSi3L.js} +1 -1
  112. package/static/dashboard/assets/dashboard-hSOBxPcP.css +1 -0
  113. package/static/dashboard/assets/{dashboard-Or0gnkou.js → dashboard-vmne1ZE0.js} +2 -2
  114. package/static/dashboard/assets/{index-BqFeOML9.js → index-HDQQ8asz.js} +1 -1
  115. package/static/dashboard/assets/{index-DgKn1twH.js → index-gtv-MAHl.js} +1 -1
  116. package/static/dashboard/assets/{label-CS6Sx6Zb.js → label-tKuCTJm2.js} +1 -1
  117. package/static/dashboard/assets/{list-UyhlCFKk.js → list-CKq3KHke.js} +1 -1
  118. package/static/dashboard/assets/{nav-item-style-CW1Nnorq.js → nav-item-style-B2gI7c52.js} +1 -1
  119. package/static/dashboard/assets/pod-collections-host-BqOAkacM.js +8 -0
  120. package/static/dashboard/assets/{safe-continuation-ClWAhcma.js → safe-continuation-BsKjDpAA.js} +1 -1
  121. package/static/dashboard/assets/{section-header-CEIEcco-.js → section-header-DM0JU-wk.js} +1 -1
  122. package/static/dashboard/assets/{setting-row-CaYv3dfF.js → setting-row-XiH7UjWy.js} +1 -1
  123. package/static/dashboard/assets/{xpod-remembered-login-BUF9fin1.js → xpod-remembered-login-nv1g1sfC.js} +4 -4
  124. package/static/dashboard/dashboard.html +2 -2
  125. package/static/settings/assets/{AccountAuthBoundary-Chqd38oU.js → AccountAuthBoundary-DAUR0omy.js} +1 -1
  126. package/static/settings/assets/{DesktopOidcCallback-VUrOSM_q.js → DesktopOidcCallback-BNZAZMWY.js} +1 -1
  127. package/static/settings/assets/DevicePages-BO9SrXRp.js +2 -0
  128. package/static/settings/assets/ModelsPage-DREEXdf3.js +12 -0
  129. package/static/settings/assets/{PaneListHeader-B5RkRqr4.js → PaneListHeader-CeUH0JjQ.js} +1 -1
  130. package/static/settings/assets/{PodDeletionAuthorizationPanel-DPM-5Izt.js → PodDeletionAuthorizationPanel-Ch4--l4b.js} +3 -3
  131. package/static/settings/assets/{PodPage-CxND6wwc.js → PodPage-CqJBj3GJ.js} +1 -1
  132. package/static/settings/assets/{SystemSettingsPage-C8EdMkW7.js → SystemSettingsPage-2LSjpqUa.js} +1 -1
  133. package/static/settings/assets/{TasksPage-DuqptEm8.js → TasksPage-DA5IRxlP.js} +1 -1
  134. package/static/settings/assets/{WebDesktopEntry-BRcyktkv.js → WebDesktopEntry-BVelF1q0.js} +1 -1
  135. package/static/settings/assets/XpodAiConnectionsPodStore-O5-2qBsx.js +3 -0
  136. package/static/settings/assets/XpodShellApp-BEzi4f1j.css +1 -0
  137. package/static/settings/assets/XpodShellApp-D1b3kqD0.js +4 -0
  138. package/static/settings/assets/XpodThemeProvider-hSOBxPcP.css +1 -0
  139. package/static/settings/assets/{XpodThemeProvider-DK3pIdRG.js → XpodThemeProvider-rUdAw66p.js} +2 -2
  140. package/static/settings/assets/{account-session-DJOZ03xr.js → account-session-BD1q8D_o.js} +1 -1
  141. package/static/settings/assets/auth-callback-PWS7okI8.js +1 -0
  142. package/static/settings/assets/{button-D3yW-2o1.js → button-ZJYFB218.js} +1 -1
  143. package/static/settings/assets/{collection-runtime-DfGo25mh.js → collection-runtime-C2FHCCXJ.js} +1 -1
  144. package/static/settings/assets/{createLucideIcon-CacwLsmB.js → createLucideIcon-B2wXvYTD.js} +1 -1
  145. package/static/settings/assets/{index-r5KRYsKC.js → index-JFAkaKZH.js} +1 -1
  146. package/static/settings/assets/{label-MJjUmvzG.js → label-BLrTAmGy.js} +1 -1
  147. package/static/settings/assets/{list-Ce-5k1MX.js → list-Cm96vvJO.js} +1 -1
  148. package/static/settings/assets/{nav-item-style-ZUbL8KOZ.js → nav-item-style-C2upI7sr.js} +1 -1
  149. package/static/settings/assets/pod-collections-host-BqOAkacM.js +8 -0
  150. package/static/settings/assets/{safe-continuation-vlBZFSlU.js → safe-continuation-CYaRj3rj.js} +1 -1
  151. package/static/settings/assets/{section-header-DGJdEvQw.js → section-header-BviL5H8E.js} +1 -1
  152. package/static/settings/assets/{setting-row-C7tuFiho.js → setting-row-BYikj0YO.js} +1 -1
  153. package/static/settings/assets/{settings-CyQPih1O.js → settings-CpoDG_Qr.js} +1 -1
  154. package/static/settings/assets/{xpod-remembered-login-D-TzzT5K.js → xpod-remembered-login-BJ4QUSXy.js} +4 -4
  155. package/static/settings/auth-callback.html +3 -3
  156. package/static/settings/settings.html +3 -3
  157. package/dist/api/ai-gateway/auth/GatewayApiKey.d.ts +0 -28
  158. package/dist/api/ai-gateway/auth/GatewayApiKey.js +0 -162
  159. package/dist/api/ai-gateway/auth/GatewayKeyLocatorCodec.d.ts +0 -26
  160. package/dist/api/ai-gateway/auth/GatewayKeyLocatorCodec.js +0 -97
  161. package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +0 -67
  162. package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +0 -340
  163. package/dist/runtime/gateway-locator-secret.d.ts +0 -5
  164. package/dist/runtime/gateway-locator-secret.js +0 -30
  165. package/static/dashboard/assets/DevicePages-HjclpIVm.js +0 -2
  166. package/static/dashboard/assets/ModelsPage-Be2TPd-x.js +0 -11
  167. package/static/dashboard/assets/XpodAiConnectionsPodStore-B7gmXBmC.js +0 -3
  168. package/static/dashboard/assets/XpodShellApp-Bwom2xL3.css +0 -1
  169. package/static/dashboard/assets/XpodShellApp-D_KY4nCW.js +0 -4
  170. package/static/dashboard/assets/dashboard-CbJ-JkrI.css +0 -1
  171. package/static/dashboard/assets/pod-collections-host-DjK2xf-b.js +0 -8
  172. package/static/settings/assets/DevicePages-C6qwvWwu.js +0 -2
  173. package/static/settings/assets/ModelsPage-Ry_dObRQ.js +0 -11
  174. package/static/settings/assets/XpodAiConnectionsPodStore-CyA_BJVC.js +0 -3
  175. package/static/settings/assets/XpodShellApp-Bwom2xL3.css +0 -1
  176. package/static/settings/assets/XpodShellApp-CsivHJl0.js +0 -4
  177. package/static/settings/assets/XpodThemeProvider-CbJ-JkrI.css +0 -1
  178. package/static/settings/assets/auth-callback-GIBYEz_7.js +0 -1
  179. package/static/settings/assets/pod-collections-host-DjK2xf-b.js +0 -8
@@ -2,21 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AI_CONNECTIONS_PROVIDER_DOCUMENT_IDS = exports.AI_CONNECTIONS_APPLET_ID = void 0;
4
4
  exports.createAiConnectionsServiceAccess = createAiConnectionsServiceAccess;
5
- exports.resolveGatewayAccessKeyResourceUrl = resolveGatewayAccessKeyResourceUrl;
6
- exports.resolveGatewayAccessKeySecretResourceUrl = resolveGatewayAccessKeySecretResourceUrl;
7
- exports.resolveGatewayAccessKeySparqlEndpoint = resolveGatewayAccessKeySparqlEndpoint;
8
- exports.isGatewayAccessKeySparqlEndpoint = isGatewayAccessKeySparqlEndpoint;
9
5
  const drizzle_solid_1 = require("@undefineds.co/drizzle-solid");
10
6
  const provider_catalog_1 = require("@undefineds.co/ai-connections/provider-catalog");
11
7
  Object.defineProperty(exports, "AI_CONNECTIONS_PROVIDER_DOCUMENT_IDS", { enumerable: true, get: function () { return provider_catalog_1.AI_CONNECTIONS_PROVIDER_DOCUMENT_IDS; } });
12
- const models_1 = require("@undefineds.co/models");
8
+ const service_access_resources_1 = require("@undefineds.co/ai-connections/service-access-resources");
13
9
  exports.AI_CONNECTIONS_APPLET_ID = 'co.undefineds.ai-connections';
14
- const declaredResourceBases = new WeakMap([
15
- [models_1.credentialResource, declaredResourceBase(models_1.credentialResource)],
16
- [models_1.aiProviderResource, declaredResourceBase(models_1.aiProviderResource)],
17
- [models_1.gatewayAccessKeyResource, declaredResourceBase(models_1.gatewayAccessKeyResource)],
18
- [models_1.quotaSnapshotResource, declaredResourceBase(models_1.quotaSnapshotResource)],
19
- ]);
20
10
  function createAiConnectionsServiceAccess(input) {
21
11
  return {
22
12
  appletId: exports.AI_CONNECTIONS_APPLET_ID,
@@ -24,62 +14,17 @@ function createAiConnectionsServiceAccess(input) {
24
14
  webId: input.serviceWebId,
25
15
  label: 'Xpod AI Connection',
26
16
  },
27
- resources: [
28
- ['providerCredentials', resourceUrl(input.ownerWebId, models_1.credentialResource, input.podBaseUrl)],
29
- ['providerDefinitions', resourceUrl(input.ownerWebId, models_1.aiProviderResource, input.podBaseUrl)],
30
- ['gatewayAccessKeys', resolveGatewayAccessKeyResourceUrl(input.ownerWebId, input.podBaseUrl)],
31
- ['gatewayAccessKeySecrets', resolveGatewayAccessKeySecretResourceUrl(input.ownerWebId, input.podBaseUrl), 'application/json'],
32
- ['quotaSnapshots', resourceUrl(input.ownerWebId, models_1.quotaSnapshotResource, input.podBaseUrl)],
33
- ...provider_catalog_1.AI_CONNECTIONS_PROVIDER_DOCUMENT_IDS.map((provider) => [
34
- `providerDocument:${provider}`,
35
- providerDocumentUrl(input.ownerWebId, provider, input.podBaseUrl),
36
- ]),
37
- ].map(([id, url, mediaType]) => ({
17
+ resources: service_access_resources_1.AI_CONNECTIONS_SERVICE_RESOURCE_IDS.map(id => ({
38
18
  id,
39
- url,
40
- mediaType: mediaType ?? 'text/turtle',
19
+ ...requiredResourceLocation(id, input.ownerWebId, input.podBaseUrl),
41
20
  access: { read: true, append: true, write: true },
42
21
  })),
43
22
  };
44
23
  }
45
- function resolveGatewayAccessKeyResourceUrl(ownerWebId, podBaseUrl) {
46
- return resourceUrl(ownerWebId, models_1.gatewayAccessKeyResource, podBaseUrl);
47
- }
48
- function resolveGatewayAccessKeySecretResourceUrl(ownerWebId, podBaseUrl) {
49
- const podRoot = `${(podBaseUrl ?? (0, drizzle_solid_1.resolvePodBaseUrl)(ownerWebId)).replace(/\/$/u, '')}/`;
50
- return new URL('.data/ai/gateway/access-key-secrets.json', podRoot).href;
51
- }
52
- function resolveGatewayAccessKeySparqlEndpoint(ownerWebId, podBaseUrl) {
53
- return `${resolveGatewayAccessKeyResourceUrl(ownerWebId, podBaseUrl).replace(/\/$/u, '')}/-/sparql`;
54
- }
55
- function isGatewayAccessKeySparqlEndpoint(ownerWebId, resourceUrlValue) {
56
- try {
57
- const resource = typeof resourceUrlValue === 'string' ? new URL(resourceUrlValue) : resourceUrlValue;
58
- const endpoint = new URL(resolveGatewayAccessKeySparqlEndpoint(ownerWebId));
59
- return resource.origin === endpoint.origin && resource.pathname === endpoint.pathname;
60
- }
61
- catch {
62
- return false;
63
- }
64
- }
65
- function resourceUrl(ownerWebId, resource, podBaseUrl) {
66
- const podRoot = `${(podBaseUrl ?? (0, drizzle_solid_1.resolvePodBaseUrl)(ownerWebId)).replace(/\/$/u, '')}/`;
67
- const resourcePath = declaredResourceBases.get(resource);
68
- if (!resourcePath) {
69
- throw new Error('AI Connection resource is missing an immutable declared base');
70
- }
71
- const documentPath = resource.buildId({ id: '__service_access__' }).split('#')[0];
72
- return new URL(`${resourcePath}/${documentPath}`.replace(/^\/+/u, ''), podRoot).href;
73
- }
74
- function providerDocumentUrl(ownerWebId, provider, podBaseUrl) {
75
- const podRoot = `${(podBaseUrl ?? (0, drizzle_solid_1.resolvePodBaseUrl)(ownerWebId)).replace(/\/$/u, '')}/`;
76
- return new URL(`settings/providers/${provider}.ttl`, podRoot).href;
77
- }
78
- function declaredResourceBase(resource) {
79
- const base = resource.config?.base?.replace(/^\/+|\/+$/gu, '');
80
- if (!base) {
81
- throw new Error('AI Connection resource is missing a declared base');
82
- }
83
- return base;
24
+ function requiredResourceLocation(id, ownerWebId, podBaseUrl) {
25
+ const location = (0, service_access_resources_1.resolveAiConnectionsServiceResource)(id, podBaseUrl ?? (0, drizzle_solid_1.resolvePodBaseUrl)(ownerWebId));
26
+ if (!location)
27
+ throw new Error('AI Connection resource is not declared');
28
+ return location;
84
29
  }
85
30
  //# sourceMappingURL=AiConnectionsServiceAccess.js.map
@@ -9,6 +9,10 @@
9
9
  */
10
10
  export interface SolidAuthContext {
11
11
  type: 'solid';
12
+ /** Untrusted current-Pod selection hint; only the shared storage adapter may verify it. */
13
+ requestedPodUrl?: string;
14
+ /** Trusted Pod capability scope, from authenticated signed claims or a verified key DB. */
15
+ authorizedPodUrl?: string;
12
16
  webId: string;
13
17
  accountId?: string;
14
18
  clientId?: string;
@@ -72,19 +72,25 @@ class ClientCredentialsAuthenticator {
72
72
  // Non sk- format is intentionally unsupported; Xpod does not keep an API key mirror table.
73
73
  return { success: false, error: 'Invalid client credentials wrapper: must start with sk-' };
74
74
  }
75
- // One exchange per credential: the factory keeps the token together with the DPoP key it
76
- // is bound to, so outbound Pod access reuses this session instead of exchanging again.
75
+ // Admission revalidates the wrapper against the issuer that owns it, on every request: a
76
+ // session cached for a previous request is not proof that this credential is still
77
+ // registered. The factory publishes the fresh session, so this same request's outbound Pod
78
+ // access reuses that token and its DPoP key instead of exchanging a second time.
77
79
  let session;
78
80
  try {
79
- session = await this.sessions.session({ clientId, clientSecret });
81
+ session = await this.sessions.admit({ clientId, clientSecret });
80
82
  }
81
83
  catch (error) {
82
84
  const status = error instanceof SolidSessionFactory_1.SolidSessionError ? error.status : undefined;
83
- const unavailable = status === undefined || status >= 500;
85
+ // The issuer being unreachable, failing, or rate limiting us is not a statement about the
86
+ // credential. Only a definitive refusal (the 400/401/403 the factory treats as such) is an
87
+ // invalid credential; everything else is retryable availability, so it is reported as 503
88
+ // rather than telling the caller its key is dead.
89
+ const unavailable = status === undefined || status >= 500 || status === 429;
84
90
  this.logger.warn(`Client credentials exchange failed for ${clientId.slice(0, 8)}...: ${String(error)}`);
85
91
  return unavailable
86
92
  ? { success: false, error: 'Token exchange temporarily unavailable', category: 'service_unavailable', statusCode: 503, cause: error }
87
- : { success: false, error: `Token exchange failed: ${status ?? 'unknown'}`, cause: error };
93
+ : { success: false, error: `Token exchange failed: ${status ?? 'unknown'}`, category: 'invalid_credentials', statusCode: 401, cause: error };
88
94
  }
89
95
  if (!session.webId) {
90
96
  return { success: false, error: 'Could not determine webId from token response' };
@@ -34,11 +34,14 @@ export interface SolidSessionFactoryOptions {
34
34
  maxEntries?: number;
35
35
  }
36
36
  /**
37
- * Exchanges CSS client credentials for Solid tokens, once per credential.
37
+ * Exchanges CSS client credentials for Solid tokens.
38
38
  *
39
- * Inbound authentication and outbound Pod access share this cache, so one request that arrives
40
- * with an `sk-` wrapper and then reads the Pod performs a single token exchange and keeps the
41
- * key the token is bound to. Cache identity is the issuer, the complete credential and its
39
+ * Inbound admission is a fresh exchange with the issuer on every request: a session in this cache
40
+ * proves only that some earlier exchange succeeded, never that the credential is still
41
+ * registered. The cache serves the outbound side - one request that arrives with an `sk-` wrapper
42
+ * and then reads the Pod reuses the token its own admission just obtained instead of exchanging
43
+ * the same credential a second time, and repeated Pod work keeps a live token and the key it is
44
+ * bound to until it is spent. Cache identity is the issuer, the complete credential and its
42
45
  * version - never just the owner or the client id, which are public identification rather than
43
46
  * authentication evidence.
44
47
  */
@@ -53,9 +56,40 @@ export declare class SolidSessionFactory {
53
56
  /** Cache keys per client id, so a revoked credential can be forgotten without its secret. */
54
57
  private readonly keysByClientId;
55
58
  private readonly clientIdByKey;
59
+ /**
60
+ * Monotonic per-client invalidation counter. `admit` reads it before its exchange and only
61
+ * publishes the result while it is unchanged, so a success that resolves after a revocation
62
+ * cannot hand the outbound cache authority the revocation just took away. It is not a
63
+ * revocation record and never admits anything: every request still proves its own credential
64
+ * with a fresh exchange.
65
+ */
66
+ private readonly invalidationSeq;
67
+ /** In-flight `admit` calls per client id, so a counter is kept only while it guards one. */
68
+ private readonly admissionsInFlight;
56
69
  constructor(options: SolidSessionFactoryOptions);
57
70
  /** A usable session for this credential, exchanging it when the cached one is spent. */
58
71
  session(credential: SolidClientCredential): Promise<SolidSession>;
72
+ /**
73
+ * Admit an inbound request that presents these client credentials.
74
+ *
75
+ * Admission is always a fresh exchange with the issuer that owns the credential. A cached token
76
+ * proves that some earlier exchange succeeded; it is not evidence that the credential is still
77
+ * registered, so it must never stand in for present authority. A credential deleted from its
78
+ * Account has to stop opening the gateway on the very next request, not whenever the last token
79
+ * it obtained happens to expire.
80
+ *
81
+ * The fresh session is published to the cache, so the rest of this request - reaching the
82
+ * owner's Pod - finds that same token and the DPoP key it is bound to instead of exchanging the
83
+ * same credential a second time. Concurrent inbound requests deliberately do not share each
84
+ * other's exchange, for the same reason they do not share a cached token: each has to prove the
85
+ * credential itself.
86
+ *
87
+ * The publishing is conditional on the credential not having been invalidated while the
88
+ * exchange was in flight. A success the issuer returned before a revocation is not present
89
+ * authority, so it is handed to this caller - already-authorized work is not torn down - but it
90
+ * is not written into the cache that later Pod access reads.
91
+ */
92
+ admit(credential: SolidClientCredential): Promise<SolidSession>;
59
93
  /**
60
94
  * Forget every cached session for one client.
61
95
  *
@@ -64,6 +98,18 @@ export declare class SolidSessionFactory {
64
98
  * authenticating the wrapper until it expires, and a deleted API Key looks like it still works.
65
99
  */
66
100
  invalidateClientCredential(clientId: string): void;
101
+ /**
102
+ * Mark a credential as definitively invalidated without deleting anything yet: the counter makes
103
+ * an in-flight exchange that resolves later decline to publish its result. Removed as soon as no
104
+ * admission for the client is still in flight, so it carries no state past its purpose.
105
+ */
106
+ private bumpInvalidation;
107
+ private releaseAdmission;
108
+ /**
109
+ * Drop the counter once no admission can still be racing it. While one is in flight the counter
110
+ * has to survive so that admission declines to republish; afterwards it is dead weight.
111
+ */
112
+ private pruneInvalidation;
67
113
  private remember;
68
114
  /** Drop one cache entry and its client-id index, whichever path removed it. */
69
115
  private forgetKey;
@@ -10,11 +10,14 @@ const TOKEN_EXPIRY_SKEW_MS = 30_000;
10
10
  const DEFAULT_TOKEN_LIFETIME_SECONDS = 300;
11
11
  const DEFAULT_MAX_ENTRIES = 256;
12
12
  /**
13
- * Exchanges CSS client credentials for Solid tokens, once per credential.
13
+ * Exchanges CSS client credentials for Solid tokens.
14
14
  *
15
- * Inbound authentication and outbound Pod access share this cache, so one request that arrives
16
- * with an `sk-` wrapper and then reads the Pod performs a single token exchange and keeps the
17
- * key the token is bound to. Cache identity is the issuer, the complete credential and its
15
+ * Inbound admission is a fresh exchange with the issuer on every request: a session in this cache
16
+ * proves only that some earlier exchange succeeded, never that the credential is still
17
+ * registered. The cache serves the outbound side - one request that arrives with an `sk-` wrapper
18
+ * and then reads the Pod reuses the token its own admission just obtained instead of exchanging
19
+ * the same credential a second time, and repeated Pod work keeps a live token and the key it is
20
+ * bound to until it is spent. Cache identity is the issuer, the complete credential and its
18
21
  * version - never just the owner or the client id, which are public identification rather than
19
22
  * authentication evidence.
20
23
  */
@@ -26,6 +29,16 @@ class SolidSessionFactory {
26
29
  /** Cache keys per client id, so a revoked credential can be forgotten without its secret. */
27
30
  this.keysByClientId = new Map();
28
31
  this.clientIdByKey = new Map();
32
+ /**
33
+ * Monotonic per-client invalidation counter. `admit` reads it before its exchange and only
34
+ * publishes the result while it is unchanged, so a success that resolves after a revocation
35
+ * cannot hand the outbound cache authority the revocation just took away. It is not a
36
+ * revocation record and never admits anything: every request still proves its own credential
37
+ * with a fresh exchange.
38
+ */
39
+ this.invalidationSeq = new Map();
40
+ /** In-flight `admit` calls per client id, so a counter is kept only while it guards one. */
41
+ this.admissionsInFlight = new Map();
29
42
  this.route = (0, TokenEndpointRoute_1.resolveTokenEndpointRoute)(options.tokenEndpoint, options.publicBaseUrl);
30
43
  this.fetchImpl = options.fetch ?? fetch;
31
44
  this.now = options.now ?? Date.now;
@@ -58,6 +71,59 @@ class SolidSessionFactory {
58
71
  this.pending.set(key, pending);
59
72
  return promise;
60
73
  }
74
+ /**
75
+ * Admit an inbound request that presents these client credentials.
76
+ *
77
+ * Admission is always a fresh exchange with the issuer that owns the credential. A cached token
78
+ * proves that some earlier exchange succeeded; it is not evidence that the credential is still
79
+ * registered, so it must never stand in for present authority. A credential deleted from its
80
+ * Account has to stop opening the gateway on the very next request, not whenever the last token
81
+ * it obtained happens to expire.
82
+ *
83
+ * The fresh session is published to the cache, so the rest of this request - reaching the
84
+ * owner's Pod - finds that same token and the DPoP key it is bound to instead of exchanging the
85
+ * same credential a second time. Concurrent inbound requests deliberately do not share each
86
+ * other's exchange, for the same reason they do not share a cached token: each has to prove the
87
+ * credential itself.
88
+ *
89
+ * The publishing is conditional on the credential not having been invalidated while the
90
+ * exchange was in flight. A success the issuer returned before a revocation is not present
91
+ * authority, so it is handed to this caller - already-authorized work is not torn down - but it
92
+ * is not written into the cache that later Pod access reads.
93
+ */
94
+ async admit(credential) {
95
+ const key = cacheKey(this.route, credential);
96
+ const clientId = credential.clientId;
97
+ const capturedSeq = this.invalidationSeq.get(clientId) ?? 0;
98
+ this.admissionsInFlight.set(clientId, (this.admissionsInFlight.get(clientId) ?? 0) + 1);
99
+ let session;
100
+ try {
101
+ session = await this.exchange(credential);
102
+ }
103
+ catch (error) {
104
+ // A definitive refusal means this credential is gone, so the session cached under it is
105
+ // stale evidence and goes with it. An outage says nothing about the credential, so the
106
+ // cache is left alone and the caller hears 503 instead of 401.
107
+ try {
108
+ if (isCredentialRefusal(error)) {
109
+ this.invalidate(credential);
110
+ }
111
+ }
112
+ finally {
113
+ this.releaseAdmission(clientId);
114
+ }
115
+ throw error;
116
+ }
117
+ try {
118
+ if ((this.invalidationSeq.get(clientId) ?? 0) === capturedSeq) {
119
+ this.remember(key, clientId, session);
120
+ }
121
+ }
122
+ finally {
123
+ this.releaseAdmission(clientId);
124
+ }
125
+ return session;
126
+ }
61
127
  /**
62
128
  * Forget every cached session for one client.
63
129
  *
@@ -66,6 +132,7 @@ class SolidSessionFactory {
66
132
  * authenticating the wrapper until it expires, and a deleted API Key looks like it still works.
67
133
  */
68
134
  invalidateClientCredential(clientId) {
135
+ this.bumpInvalidation(clientId);
69
136
  const keys = this.keysByClientId.get(clientId);
70
137
  for (const key of keys ?? []) {
71
138
  this.forgetKey(key);
@@ -75,6 +142,35 @@ class SolidSessionFactory {
75
142
  this.pending.delete(key);
76
143
  }
77
144
  }
145
+ this.pruneInvalidation(clientId);
146
+ }
147
+ /**
148
+ * Mark a credential as definitively invalidated without deleting anything yet: the counter makes
149
+ * an in-flight exchange that resolves later decline to publish its result. Removed as soon as no
150
+ * admission for the client is still in flight, so it carries no state past its purpose.
151
+ */
152
+ bumpInvalidation(clientId) {
153
+ this.invalidationSeq.set(clientId, (this.invalidationSeq.get(clientId) ?? 0) + 1);
154
+ }
155
+ releaseAdmission(clientId) {
156
+ const remaining = (this.admissionsInFlight.get(clientId) ?? 1) - 1;
157
+ if (remaining > 0) {
158
+ this.admissionsInFlight.set(clientId, remaining);
159
+ }
160
+ else {
161
+ this.admissionsInFlight.delete(clientId);
162
+ }
163
+ this.pruneInvalidation(clientId);
164
+ }
165
+ /**
166
+ * Drop the counter once no admission can still be racing it. While one is in flight the counter
167
+ * has to survive so that admission declines to republish; afterwards it is dead weight.
168
+ */
169
+ pruneInvalidation(clientId) {
170
+ if (this.admissionsInFlight.has(clientId)) {
171
+ return;
172
+ }
173
+ this.invalidationSeq.delete(clientId);
78
174
  }
79
175
  remember(key, clientId, session) {
80
176
  this.sessions.set(key, session);
@@ -113,8 +209,10 @@ class SolidSessionFactory {
113
209
  if (expectedSession && this.sessions.get(key) !== expectedSession) {
114
210
  return;
115
211
  }
212
+ this.bumpInvalidation(credential.clientId);
116
213
  this.pending.delete(key);
117
214
  this.forgetKey(key);
215
+ this.pruneInvalidation(credential.clientId);
118
216
  }
119
217
  async exchange(credential) {
120
218
  const dpopKey = await (0, solid_client_authn_core_1.generateDpopKeyPair)();
@@ -172,6 +270,18 @@ function cacheKey(route, credential) {
172
270
  function fingerprintSecret(secret) {
173
271
  return (0, node_crypto_1.createHash)('sha256').update(secret).digest('hex');
174
272
  }
273
+ /**
274
+ * Whether the issuer definitively refused the credential itself, rather than failing to answer.
275
+ *
276
+ * Only 400, 401 and 403 mean "this credential is not registered any more". Everything else is not
277
+ * evidence about the credential: a 5xx, a 429, a network error or an unparseable response is an
278
+ * outage or a rate limit of the issuer, and reporting that as a revoked key would both mislead the
279
+ * caller and throw away a session that is still perfectly good.
280
+ */
281
+ function isCredentialRefusal(error) {
282
+ const status = error instanceof SolidSessionError ? error.status : undefined;
283
+ return status === 400 || status === 401 || status === 403;
284
+ }
175
285
  function pruneOldest(cache, limit, onEvict) {
176
286
  while (cache.size > limit) {
177
287
  const oldest = cache.keys().next();
@@ -5,7 +5,7 @@ import { type RunListOptions, type RunRecordData, type RunStepRecordData, type R
5
5
  import { type TaskListOptions, type TaskRecordData, type TaskStore } from '../tasks/store';
6
6
  import { type TaskAuthBindingRepository } from '../tasks/TaskAuthBinding';
7
7
  import { type PodAccessFetchProvider } from '../ai-gateway/pod/OwnerPodAccess';
8
- import type { PodBaseUrlResolver } from '../ai-gateway/pod/PodBaseUrlResolver';
8
+ import { type PodBaseUrlResolver } from '../ai-gateway/pod/PodBaseUrlResolver';
9
9
  import { type AiCredentialSecretDecoder } from '../../ai/service/AiCredentialSecret';
10
10
  import { CredentialStatus } from '../../credential/schema/types';
11
11
  import { type ServerGroupReconcilerService } from '../reconciler';
@@ -59,6 +59,13 @@ export declare class PodChatKitStore implements ChatKitStore<StoreContext>, RunS
59
59
  */
60
60
  private getDb;
61
61
  private openDb;
62
+ /**
63
+ * Resolve the request's authoritative Pod root, or `undefined` when this deployment has no
64
+ * binding resolver to consult. Reporting a missing binding is left to the caller so the
65
+ * credential path can be classified first.
66
+ */
67
+ private resolveContextPodBaseUrl;
68
+ private requireContextPodBaseUrl;
62
69
  /** Stable identity of the credential binding behind a cached database, without secrets. */
63
70
  private podCredentialBinding;
64
71
  /**
@@ -28,6 +28,7 @@ const store_2 = require("../tasks/store");
28
28
  const TaskAuthBinding_1 = require("../tasks/TaskAuthBinding");
29
29
  const CallerPodAccess_1 = require("../ai-gateway/auth/CallerPodAccess");
30
30
  const OwnerPodAccess_1 = require("../ai-gateway/pod/OwnerPodAccess");
31
+ const PodBaseUrlResolver_1 = require("../ai-gateway/pod/PodBaseUrlResolver");
31
32
  const AuthContext_1 = require("../auth/AuthContext");
32
33
  const provider_1 = require("../../ai/schema/provider");
33
34
  const model_1 = require("../../ai/schema/model");
@@ -114,6 +115,13 @@ class PodChatKitStore {
114
115
  if (expectedAuth && expectedAuth !== this.podCredentialBinding(context)) {
115
116
  throw new Error('Authoritative Pod credential binding changed on cached context');
116
117
  }
118
+ const auth = context.auth;
119
+ if (auth?.type === 'solid' && (auth.requestedPodUrl || auth.authorizedPodUrl)) {
120
+ const selectedRoot = await this.requireContextPodBaseUrl(context, auth);
121
+ if (!boundRoot || selectedRoot !== boundRoot) {
122
+ throw new Error('Authoritative Pod storage binding changed on cached context');
123
+ }
124
+ }
117
125
  this.logger.debug('Using cached db from context');
118
126
  return cachedDb;
119
127
  }
@@ -123,8 +131,12 @@ class PodChatKitStore {
123
131
  // it never outlives the request or crosses credentials.
124
132
  const inFlight = context._cachedDbPromise;
125
133
  if (inFlight) {
134
+ if (context._cachedDbPromiseAuth !== this.podCredentialBinding(context)) {
135
+ throw new Error('Authoritative Pod credential binding changed while opening database');
136
+ }
126
137
  return inFlight;
127
138
  }
139
+ context._cachedDbPromiseAuth = this.podCredentialBinding(context);
128
140
  const opening = this.openDb(context);
129
141
  context._cachedDbPromise = opening;
130
142
  try {
@@ -133,6 +145,7 @@ class PodChatKitStore {
133
145
  finally {
134
146
  if (context._cachedDbPromise === opening) {
135
147
  delete context._cachedDbPromise;
148
+ delete context._cachedDbPromiseAuth;
136
149
  }
137
150
  }
138
151
  }
@@ -142,6 +155,11 @@ class PodChatKitStore {
142
155
  this.logger.warn('No valid solid auth in context, cannot access Pod');
143
156
  throw new Error(CallerPodAccess_1.CALLER_POD_ACCESS_UNAVAILABLE);
144
157
  }
158
+ const credentialBinding = this.podCredentialBinding(context);
159
+ // A missing authoritative binding is a storage failure, but it must not decide the diagnosis
160
+ // for a caller whose credential path is already unusable: resolve it here, then reject only
161
+ // after Pod access has reported why no usable credential exists.
162
+ const podBaseUrl = await this.resolveContextPodBaseUrl(context, auth);
145
163
  // One credential path for every caller: the owner's own Pod key, exchanged for a
146
164
  // token this process can prove, or the caller's reusable token. Unattended work carries a
147
165
  // task-layer grant, which is forwarded so the Pod fetch is bound to that exact grant.
@@ -150,6 +168,7 @@ class PodChatKitStore {
150
168
  try {
151
169
  podFetch = await this.podAccess?.getPodFetch(auth.webId, {
152
170
  auth,
171
+ podBaseUrl,
153
172
  ...(taskCredential ? { taskCredential } : {}),
154
173
  });
155
174
  }
@@ -162,11 +181,12 @@ class PodChatKitStore {
162
181
  this.logger.warn(`No usable Pod credential for ${auth.webId}: ${reason}`);
163
182
  throw new Error(reason);
164
183
  }
165
- const podBaseUrl = this.readExplicitPodBaseUrl(context)
166
- ?? this.normalizePodBaseUrl(await this.podBaseUrlResolver?.(auth.webId));
167
184
  if (!podBaseUrl) {
168
185
  throw new Error('Authoritative Pod storage binding unavailable');
169
186
  }
187
+ if (credentialBinding !== this.podCredentialBinding(context)) {
188
+ throw new Error('Authoritative Pod credential binding changed while opening database');
189
+ }
170
190
  const db = (0, drizzle_solid_1.drizzle)({ fetch: podFetch, info: { webId: auth.webId, isLoggedIn: true } }, { schema, podUrl: podBaseUrl });
171
191
  this.logger.info(`Initializing tables for Pod: ${auth.webId}`);
172
192
  try {
@@ -176,13 +196,42 @@ class PodChatKitStore {
176
196
  catch (initError) {
177
197
  this.logger.error(`Failed to init tables: ${initError}`);
178
198
  }
199
+ if (credentialBinding !== this.podCredentialBinding(context)) {
200
+ throw new Error('Authoritative Pod credential binding changed while opening database');
201
+ }
179
202
  context._cachedDb = db;
180
203
  context._cachedFetch = podFetch;
181
204
  context._cachedWebId = auth.webId;
182
- context._cachedAuth = this.podCredentialBinding(context);
205
+ context._cachedAuth = credentialBinding;
183
206
  this.ensurePodBaseUrlCache(context, db);
184
207
  return db;
185
208
  }
209
+ /**
210
+ * Resolve the request's authoritative Pod root, or `undefined` when this deployment has no
211
+ * binding resolver to consult. Reporting a missing binding is left to the caller so the
212
+ * credential path can be classified first.
213
+ */
214
+ async resolveContextPodBaseUrl(context, auth) {
215
+ const explicitRoot = this.readExplicitPodBaseUrl(context);
216
+ // Explicit internal contexts already carry a verified binding. A request selection or
217
+ // capability must still intersect that binding through the shared ownership resolver.
218
+ if (explicitRoot && !auth.requestedPodUrl && !auth.authorizedPodUrl)
219
+ return explicitRoot;
220
+ if (!this.podBaseUrlResolver)
221
+ return undefined;
222
+ const selectedRoot = this.normalizePodBaseUrl(await (0, PodBaseUrlResolver_1.resolveOwnerPodBaseUrl)(auth.webId, this.podBaseUrlResolver, auth));
223
+ if (explicitRoot && selectedRoot !== explicitRoot) {
224
+ throw new Error('Authoritative Pod storage binding conflicts with request');
225
+ }
226
+ return selectedRoot;
227
+ }
228
+ async requireContextPodBaseUrl(context, auth) {
229
+ const root = await this.resolveContextPodBaseUrl(context, auth);
230
+ if (!root) {
231
+ throw new Error('Authoritative Pod storage binding unavailable');
232
+ }
233
+ return root;
234
+ }
186
235
  /** Stable identity of the credential binding behind a cached database, without secrets. */
187
236
  podCredentialBinding(context) {
188
237
  const auth = context.auth;
@@ -190,6 +239,8 @@ class PodChatKitStore {
190
239
  return JSON.stringify({
191
240
  webId: auth && (0, AuthContext_1.isSolidAuth)(auth) ? auth.webId : null,
192
241
  clientId: auth && (0, AuthContext_1.isSolidAuth)(auth) ? auth.clientId ?? null : null,
242
+ requestedPodUrl: auth && (0, AuthContext_1.isSolidAuth)(auth) ? auth.requestedPodUrl ?? null : null,
243
+ authorizedPodUrl: auth && (0, AuthContext_1.isSolidAuth)(auth) ? auth.authorizedPodUrl ?? null : null,
193
244
  taskCredential: task
194
245
  ? { ref: task.credentialRef ?? null, version: task.version ?? null, ownerGrant: task.ownerGrant ?? null }
195
246
  : null,