@superblocksteam/shared 0.9607.2 → 0.9608.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/dist/database-lifecycle/appDatabaseCompatibility.d.ts +49 -0
  2. package/dist/database-lifecycle/appDatabaseCompatibility.d.ts.map +1 -0
  3. package/dist/database-lifecycle/appDatabaseCompatibility.js +32 -0
  4. package/dist/database-lifecycle/appDatabaseCompatibility.js.map +1 -0
  5. package/dist/database-lifecycle/appDatabaseCompatibility.test.d.ts +2 -0
  6. package/dist/database-lifecycle/appDatabaseCompatibility.test.d.ts.map +1 -0
  7. package/dist/database-lifecycle/appDatabaseCompatibility.test.js +47 -0
  8. package/dist/database-lifecycle/appDatabaseCompatibility.test.js.map +1 -0
  9. package/dist/database-lifecycle/destructiveMigrations.d.ts +12 -0
  10. package/dist/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
  11. package/dist/database-lifecycle/destructiveMigrations.js +114 -0
  12. package/dist/database-lifecycle/destructiveMigrations.js.map +1 -1
  13. package/dist/database-lifecycle/destructiveMigrations.test.js +79 -0
  14. package/dist/database-lifecycle/destructiveMigrations.test.js.map +1 -1
  15. package/dist/database-lifecycle/index.d.ts +46 -2
  16. package/dist/database-lifecycle/index.d.ts.map +1 -1
  17. package/dist/database-lifecycle/index.js +127 -1
  18. package/dist/database-lifecycle/index.js.map +1 -1
  19. package/dist/database-lifecycle/providerDiagnostics.test.d.ts +2 -0
  20. package/dist/database-lifecycle/providerDiagnostics.test.d.ts.map +1 -0
  21. package/dist/database-lifecycle/providerDiagnostics.test.js +119 -0
  22. package/dist/database-lifecycle/providerDiagnostics.test.js.map +1 -0
  23. package/dist/environment-compatibility/appEnvironmentCompatibility.d.ts +65 -0
  24. package/dist/environment-compatibility/appEnvironmentCompatibility.d.ts.map +1 -0
  25. package/dist/environment-compatibility/appEnvironmentCompatibility.js +87 -0
  26. package/dist/environment-compatibility/appEnvironmentCompatibility.js.map +1 -0
  27. package/dist/environment-compatibility/appEnvironmentCompatibility.test.d.ts +2 -0
  28. package/dist/environment-compatibility/appEnvironmentCompatibility.test.d.ts.map +1 -0
  29. package/dist/environment-compatibility/appEnvironmentCompatibility.test.js +285 -0
  30. package/dist/environment-compatibility/appEnvironmentCompatibility.test.js.map +1 -0
  31. package/dist/errors/http.d.ts +15 -4
  32. package/dist/errors/http.d.ts.map +1 -1
  33. package/dist/errors/http.js +25 -7
  34. package/dist/errors/http.js.map +1 -1
  35. package/dist/errors/http.test.d.ts +2 -0
  36. package/dist/errors/http.test.d.ts.map +1 -0
  37. package/dist/errors/http.test.js +28 -0
  38. package/dist/errors/http.test.js.map +1 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/plugins/templates/restApiIntegration.d.ts.map +1 -1
  44. package/dist/plugins/templates/restApiIntegration.js +2 -0
  45. package/dist/plugins/templates/restApiIntegration.js.map +1 -1
  46. package/dist/plugins/templates/shared/auth.d.ts +3 -1
  47. package/dist/plugins/templates/shared/auth.d.ts.map +1 -1
  48. package/dist/plugins/templates/shared/auth.js +129 -17
  49. package/dist/plugins/templates/shared/auth.js.map +1 -1
  50. package/dist/plugins/templates/shared/auth.test.d.ts +2 -0
  51. package/dist/plugins/templates/shared/auth.test.d.ts.map +1 -0
  52. package/dist/plugins/templates/shared/auth.test.js +116 -0
  53. package/dist/plugins/templates/shared/auth.test.js.map +1 -0
  54. package/dist/plugins/templates/shared/restapiintegration.d.ts +2 -0
  55. package/dist/plugins/templates/shared/restapiintegration.d.ts.map +1 -1
  56. package/dist/plugins/templates/shared/restapiintegration.js +3 -1
  57. package/dist/plugins/templates/shared/restapiintegration.js.map +1 -1
  58. package/dist/socket/dist-entry.test.d.ts +2 -0
  59. package/dist/socket/dist-entry.test.d.ts.map +1 -0
  60. package/dist/socket/dist-entry.test.js +34 -0
  61. package/dist/socket/dist-entry.test.js.map +1 -0
  62. package/dist/socket/heartbeat.d.ts +258 -0
  63. package/dist/socket/heartbeat.d.ts.map +1 -0
  64. package/dist/socket/heartbeat.js +307 -0
  65. package/dist/socket/heartbeat.js.map +1 -0
  66. package/dist/socket/heartbeat.test.d.ts +2 -0
  67. package/dist/socket/heartbeat.test.d.ts.map +1 -0
  68. package/dist/socket/heartbeat.test.js +431 -0
  69. package/dist/socket/heartbeat.test.js.map +1 -0
  70. package/dist/socket/index.d.ts +1 -0
  71. package/dist/socket/index.d.ts.map +1 -1
  72. package/dist/socket/index.js +1 -0
  73. package/dist/socket/index.js.map +1 -1
  74. package/dist/socket/util.d.ts.map +1 -1
  75. package/dist/socket/util.js +15 -1
  76. package/dist/socket/util.js.map +1 -1
  77. package/dist/socket/util.test.js +12 -2
  78. package/dist/socket/util.test.js.map +1 -1
  79. package/dist/types/ai/index.d.ts +3 -0
  80. package/dist/types/ai/index.d.ts.map +1 -1
  81. package/dist/types/ai/index.js +3 -0
  82. package/dist/types/ai/index.js.map +1 -1
  83. package/dist/types/application/index.d.ts +12 -0
  84. package/dist/types/application/index.d.ts.map +1 -1
  85. package/dist/types/application/index.js.map +1 -1
  86. package/dist/types/codeSecurity/index.d.ts +55 -1
  87. package/dist/types/codeSecurity/index.d.ts.map +1 -1
  88. package/dist/types/codeSecurity/index.js +21 -1
  89. package/dist/types/codeSecurity/index.js.map +1 -1
  90. package/dist/types/codeSecurity/index.test.d.ts +2 -0
  91. package/dist/types/codeSecurity/index.test.d.ts.map +1 -0
  92. package/dist/types/codeSecurity/index.test.js +19 -0
  93. package/dist/types/codeSecurity/index.test.js.map +1 -0
  94. package/dist/types/datasource/auth.d.ts +12 -1
  95. package/dist/types/datasource/auth.d.ts.map +1 -1
  96. package/dist/types/datasource/auth.js +20 -2
  97. package/dist/types/datasource/auth.js.map +1 -1
  98. package/dist/types/datasource/auth.test.js +3 -1
  99. package/dist/types/datasource/auth.test.js.map +1 -1
  100. package/dist/types/group/index.d.ts +1 -3
  101. package/dist/types/group/index.d.ts.map +1 -1
  102. package/dist/types/group/index.js +1 -3
  103. package/dist/types/group/index.js.map +1 -1
  104. package/dist/types/integration/index.d.ts +11 -1
  105. package/dist/types/integration/index.d.ts.map +1 -1
  106. package/dist/types/integration/index.js +11 -1
  107. package/dist/types/integration/index.js.map +1 -1
  108. package/dist/types/plugin/form.d.ts +32 -0
  109. package/dist/types/plugin/form.d.ts.map +1 -1
  110. package/dist/types/plugin/form.js +73 -1
  111. package/dist/types/plugin/form.js.map +1 -1
  112. package/dist/types/plugin/form.test.js +128 -0
  113. package/dist/types/plugin/form.test.js.map +1 -1
  114. package/dist/types/profile/index.d.ts +16 -1
  115. package/dist/types/profile/index.d.ts.map +1 -1
  116. package/dist/types/profile/index.js +7 -1
  117. package/dist/types/profile/index.js.map +1 -1
  118. package/dist/types/rbac/index.d.ts +2 -0
  119. package/dist/types/rbac/index.d.ts.map +1 -1
  120. package/dist/types/rbac/index.js +2 -0
  121. package/dist/types/rbac/index.js.map +1 -1
  122. package/dist/types/response/GetRecentApplicationsResponseBody.d.ts +4 -0
  123. package/dist/types/response/GetRecentApplicationsResponseBody.d.ts.map +1 -0
  124. package/dist/types/response/GetRecentApplicationsResponseBody.js +3 -0
  125. package/dist/types/response/GetRecentApplicationsResponseBody.js.map +1 -0
  126. package/dist/types/response/index.d.ts +1 -0
  127. package/dist/types/response/index.d.ts.map +1 -1
  128. package/dist/types/response/index.js +1 -0
  129. package/dist/types/response/index.js.map +1 -1
  130. package/dist-esm/database-lifecycle/appDatabaseCompatibility.d.ts +49 -0
  131. package/dist-esm/database-lifecycle/appDatabaseCompatibility.d.ts.map +1 -0
  132. package/dist-esm/database-lifecycle/appDatabaseCompatibility.js +28 -0
  133. package/dist-esm/database-lifecycle/appDatabaseCompatibility.js.map +1 -0
  134. package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.d.ts +2 -0
  135. package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.d.ts.map +1 -0
  136. package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.js +45 -0
  137. package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.js.map +1 -0
  138. package/dist-esm/database-lifecycle/destructiveMigrations.d.ts +12 -0
  139. package/dist-esm/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
  140. package/dist-esm/database-lifecycle/destructiveMigrations.js +113 -0
  141. package/dist-esm/database-lifecycle/destructiveMigrations.js.map +1 -1
  142. package/dist-esm/database-lifecycle/destructiveMigrations.test.js +80 -1
  143. package/dist-esm/database-lifecycle/destructiveMigrations.test.js.map +1 -1
  144. package/dist-esm/database-lifecycle/index.d.ts +46 -2
  145. package/dist-esm/database-lifecycle/index.d.ts.map +1 -1
  146. package/dist-esm/database-lifecycle/index.js +123 -1
  147. package/dist-esm/database-lifecycle/index.js.map +1 -1
  148. package/dist-esm/database-lifecycle/providerDiagnostics.test.d.ts +2 -0
  149. package/dist-esm/database-lifecycle/providerDiagnostics.test.d.ts.map +1 -0
  150. package/dist-esm/database-lifecycle/providerDiagnostics.test.js +117 -0
  151. package/dist-esm/database-lifecycle/providerDiagnostics.test.js.map +1 -0
  152. package/dist-esm/environment-compatibility/appEnvironmentCompatibility.d.ts +65 -0
  153. package/dist-esm/environment-compatibility/appEnvironmentCompatibility.d.ts.map +1 -0
  154. package/dist-esm/environment-compatibility/appEnvironmentCompatibility.js +79 -0
  155. package/dist-esm/environment-compatibility/appEnvironmentCompatibility.js.map +1 -0
  156. package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.d.ts +2 -0
  157. package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.d.ts.map +1 -0
  158. package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.js +283 -0
  159. package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.js.map +1 -0
  160. package/dist-esm/errors/http.d.ts +15 -4
  161. package/dist-esm/errors/http.d.ts.map +1 -1
  162. package/dist-esm/errors/http.js +22 -6
  163. package/dist-esm/errors/http.js.map +1 -1
  164. package/dist-esm/errors/http.test.d.ts +2 -0
  165. package/dist-esm/errors/http.test.d.ts.map +1 -0
  166. package/dist-esm/errors/http.test.js +26 -0
  167. package/dist-esm/errors/http.test.js.map +1 -0
  168. package/dist-esm/index.d.ts +2 -0
  169. package/dist-esm/index.d.ts.map +1 -1
  170. package/dist-esm/index.js +2 -0
  171. package/dist-esm/index.js.map +1 -1
  172. package/dist-esm/plugins/templates/restApiIntegration.d.ts.map +1 -1
  173. package/dist-esm/plugins/templates/restApiIntegration.js +2 -0
  174. package/dist-esm/plugins/templates/restApiIntegration.js.map +1 -1
  175. package/dist-esm/plugins/templates/shared/auth.d.ts +3 -1
  176. package/dist-esm/plugins/templates/shared/auth.d.ts.map +1 -1
  177. package/dist-esm/plugins/templates/shared/auth.js +129 -17
  178. package/dist-esm/plugins/templates/shared/auth.js.map +1 -1
  179. package/dist-esm/plugins/templates/shared/auth.test.d.ts +2 -0
  180. package/dist-esm/plugins/templates/shared/auth.test.d.ts.map +1 -0
  181. package/dist-esm/plugins/templates/shared/auth.test.js +114 -0
  182. package/dist-esm/plugins/templates/shared/auth.test.js.map +1 -0
  183. package/dist-esm/plugins/templates/shared/restapiintegration.d.ts +2 -0
  184. package/dist-esm/plugins/templates/shared/restapiintegration.d.ts.map +1 -1
  185. package/dist-esm/plugins/templates/shared/restapiintegration.js +3 -1
  186. package/dist-esm/plugins/templates/shared/restapiintegration.js.map +1 -1
  187. package/dist-esm/socket/dist-entry.test.d.ts +2 -0
  188. package/dist-esm/socket/dist-entry.test.d.ts.map +1 -0
  189. package/dist-esm/socket/dist-entry.test.js +29 -0
  190. package/dist-esm/socket/dist-entry.test.js.map +1 -0
  191. package/dist-esm/socket/heartbeat.d.ts +258 -0
  192. package/dist-esm/socket/heartbeat.d.ts.map +1 -0
  193. package/dist-esm/socket/heartbeat.js +303 -0
  194. package/dist-esm/socket/heartbeat.js.map +1 -0
  195. package/dist-esm/socket/heartbeat.test.d.ts +2 -0
  196. package/dist-esm/socket/heartbeat.test.d.ts.map +1 -0
  197. package/dist-esm/socket/heartbeat.test.js +429 -0
  198. package/dist-esm/socket/heartbeat.test.js.map +1 -0
  199. package/dist-esm/socket/index.d.ts +1 -0
  200. package/dist-esm/socket/index.d.ts.map +1 -1
  201. package/dist-esm/socket/index.js +1 -0
  202. package/dist-esm/socket/index.js.map +1 -1
  203. package/dist-esm/socket/util.d.ts.map +1 -1
  204. package/dist-esm/socket/util.js +15 -1
  205. package/dist-esm/socket/util.js.map +1 -1
  206. package/dist-esm/socket/util.test.js +12 -2
  207. package/dist-esm/socket/util.test.js.map +1 -1
  208. package/dist-esm/types/ai/index.d.ts +3 -0
  209. package/dist-esm/types/ai/index.d.ts.map +1 -1
  210. package/dist-esm/types/ai/index.js +3 -0
  211. package/dist-esm/types/ai/index.js.map +1 -1
  212. package/dist-esm/types/application/index.d.ts +12 -0
  213. package/dist-esm/types/application/index.d.ts.map +1 -1
  214. package/dist-esm/types/application/index.js.map +1 -1
  215. package/dist-esm/types/codeSecurity/index.d.ts +55 -1
  216. package/dist-esm/types/codeSecurity/index.d.ts.map +1 -1
  217. package/dist-esm/types/codeSecurity/index.js +17 -1
  218. package/dist-esm/types/codeSecurity/index.js.map +1 -1
  219. package/dist-esm/types/codeSecurity/index.test.d.ts +2 -0
  220. package/dist-esm/types/codeSecurity/index.test.d.ts.map +1 -0
  221. package/dist-esm/types/codeSecurity/index.test.js +17 -0
  222. package/dist-esm/types/codeSecurity/index.test.js.map +1 -0
  223. package/dist-esm/types/datasource/auth.d.ts +12 -1
  224. package/dist-esm/types/datasource/auth.d.ts.map +1 -1
  225. package/dist-esm/types/datasource/auth.js +19 -2
  226. package/dist-esm/types/datasource/auth.js.map +1 -1
  227. package/dist-esm/types/datasource/auth.test.js +3 -1
  228. package/dist-esm/types/datasource/auth.test.js.map +1 -1
  229. package/dist-esm/types/group/index.d.ts +1 -3
  230. package/dist-esm/types/group/index.d.ts.map +1 -1
  231. package/dist-esm/types/group/index.js +1 -3
  232. package/dist-esm/types/group/index.js.map +1 -1
  233. package/dist-esm/types/integration/index.d.ts +11 -1
  234. package/dist-esm/types/integration/index.d.ts.map +1 -1
  235. package/dist-esm/types/integration/index.js +11 -1
  236. package/dist-esm/types/integration/index.js.map +1 -1
  237. package/dist-esm/types/plugin/form.d.ts +32 -0
  238. package/dist-esm/types/plugin/form.d.ts.map +1 -1
  239. package/dist-esm/types/plugin/form.js +68 -0
  240. package/dist-esm/types/plugin/form.js.map +1 -1
  241. package/dist-esm/types/plugin/form.test.js +129 -1
  242. package/dist-esm/types/plugin/form.test.js.map +1 -1
  243. package/dist-esm/types/profile/index.d.ts +16 -1
  244. package/dist-esm/types/profile/index.d.ts.map +1 -1
  245. package/dist-esm/types/profile/index.js +7 -1
  246. package/dist-esm/types/profile/index.js.map +1 -1
  247. package/dist-esm/types/rbac/index.d.ts +2 -0
  248. package/dist-esm/types/rbac/index.d.ts.map +1 -1
  249. package/dist-esm/types/rbac/index.js +2 -0
  250. package/dist-esm/types/rbac/index.js.map +1 -1
  251. package/dist-esm/types/response/GetRecentApplicationsResponseBody.d.ts +4 -0
  252. package/dist-esm/types/response/GetRecentApplicationsResponseBody.d.ts.map +1 -0
  253. package/dist-esm/types/response/GetRecentApplicationsResponseBody.js +2 -0
  254. package/dist-esm/types/response/GetRecentApplicationsResponseBody.js.map +1 -0
  255. package/dist-esm/types/response/index.d.ts +1 -0
  256. package/dist-esm/types/response/index.d.ts.map +1 -1
  257. package/dist-esm/types/response/index.js +1 -0
  258. package/dist-esm/types/response/index.js.map +1 -1
  259. package/package.json +1 -1
  260. package/src/database-lifecycle/appDatabaseCompatibility.test.ts +55 -0
  261. package/src/database-lifecycle/appDatabaseCompatibility.ts +62 -0
  262. package/src/database-lifecycle/destructiveMigrations.test.ts +108 -1
  263. package/src/database-lifecycle/destructiveMigrations.ts +129 -0
  264. package/src/database-lifecycle/index.ts +173 -0
  265. package/src/database-lifecycle/providerDiagnostics.test.ts +134 -0
  266. package/src/environment-compatibility/appEnvironmentCompatibility.test.ts +326 -0
  267. package/src/environment-compatibility/appEnvironmentCompatibility.ts +173 -0
  268. package/src/errors/http.test.ts +32 -0
  269. package/src/errors/http.ts +24 -6
  270. package/src/index.ts +2 -0
  271. package/src/plugins/templates/restApiIntegration.ts +2 -0
  272. package/src/plugins/templates/shared/auth.test.ts +162 -0
  273. package/src/plugins/templates/shared/auth.ts +135 -18
  274. package/src/plugins/templates/shared/restapiintegration.ts +3 -1
  275. package/src/socket/dist-entry.test.ts +29 -0
  276. package/src/socket/heartbeat.test.ts +538 -0
  277. package/src/socket/heartbeat.ts +469 -0
  278. package/src/socket/index.ts +1 -0
  279. package/src/socket/util.test.ts +14 -2
  280. package/src/socket/util.ts +15 -1
  281. package/src/types/ai/index.ts +3 -0
  282. package/src/types/application/index.ts +14 -0
  283. package/src/types/codeSecurity/index.test.ts +21 -0
  284. package/src/types/codeSecurity/index.ts +83 -1
  285. package/src/types/datasource/auth.test.ts +3 -1
  286. package/src/types/datasource/auth.ts +24 -3
  287. package/src/types/group/index.ts +1 -3
  288. package/src/types/integration/index.ts +14 -0
  289. package/src/types/plugin/form.test.ts +174 -1
  290. package/src/types/plugin/form.ts +100 -2
  291. package/src/types/profile/index.ts +19 -1
  292. package/src/types/rbac/index.ts +2 -0
  293. package/src/types/response/GetRecentApplicationsResponseBody.ts +4 -0
  294. package/src/types/response/index.ts +1 -0
@@ -0,0 +1,134 @@
1
+ import { describe, expect, test } from 'vitest';
2
+
3
+ import { inspectLifecycleProviderDiagnosticCandidate, sanitizeLifecycleProviderDiagnostics } from './index.js';
4
+
5
+ describe('lifecycle provider diagnostic sanitization', () => {
6
+ test('derives canonical AWS fields and reports a stripped request ID', () => {
7
+ expect(
8
+ inspectLifecycleProviderDiagnosticCandidate({
9
+ errorCode: 'AccessDenied',
10
+ nextAction: 'untrusted-action',
11
+ operation: 'CreateDBCluster',
12
+ phase: 'future-phase',
13
+ provider: 'aws',
14
+ requestId: 'not-a-request-id',
15
+ retryable: false,
16
+ service: 'rds',
17
+ summary: 'provider-authored text'
18
+ })
19
+ ).toEqual({
20
+ diagnostic: {
21
+ errorCode: 'AccessDenied',
22
+ nextAction: 'review_aws_operation',
23
+ operation: 'CreateDBCluster',
24
+ phase: 'unknown',
25
+ provider: 'aws',
26
+ retryable: false,
27
+ service: 'rds'
28
+ },
29
+ provider: 'aws',
30
+ sanitizedFields: ['requestId']
31
+ });
32
+ });
33
+
34
+ test.each([
35
+ ['rds', 'CreateDBCluster', 'AccessDenied', '123e4567-e89b-12d3-a456-426614174000'],
36
+ ['s3', 'CreateBucket', 'AccessDenied', 'A1B2C3D4E5F6G7H8'],
37
+ ['dynamodb', 'CreateTable', 'AccessDeniedException', 'A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6']
38
+ ])('preserves a valid %s request ID', (service, operation, errorCode, requestId) => {
39
+ expect(
40
+ inspectLifecycleProviderDiagnosticCandidate({
41
+ errorCode,
42
+ operation,
43
+ phase: 'apply',
44
+ provider: 'aws',
45
+ requestId,
46
+ retryable: false,
47
+ service
48
+ })
49
+ ).toEqual({
50
+ diagnostic: {
51
+ errorCode,
52
+ nextAction: 'review_aws_operation',
53
+ operation,
54
+ phase: 'apply',
55
+ provider: 'aws',
56
+ requestId,
57
+ retryable: false,
58
+ service
59
+ },
60
+ provider: 'aws'
61
+ });
62
+ });
63
+
64
+ test('does not apply service-specific request ID formats to unrelated services', () => {
65
+ expect(
66
+ inspectLifecycleProviderDiagnosticCandidate({
67
+ errorCode: 'AccessDenied',
68
+ operation: 'CreateDBCluster',
69
+ phase: 'apply',
70
+ provider: 'aws',
71
+ requestId: 'A1B2C3D4E5F6G7H8',
72
+ retryable: false,
73
+ service: 'rds'
74
+ })
75
+ ).toEqual({
76
+ diagnostic: {
77
+ errorCode: 'AccessDenied',
78
+ nextAction: 'review_aws_operation',
79
+ operation: 'CreateDBCluster',
80
+ phase: 'apply',
81
+ provider: 'aws',
82
+ retryable: false,
83
+ service: 'rds'
84
+ },
85
+ provider: 'aws',
86
+ sanitizedFields: ['requestId']
87
+ });
88
+ });
89
+
90
+ test('reports rejected provider fields without retaining input text', () => {
91
+ expect(
92
+ inspectLifecycleProviderDiagnosticCandidate({
93
+ errorCode: 'Access Denied',
94
+ operation: 'Create DB Cluster',
95
+ provider: 'aws',
96
+ retryable: 'false',
97
+ service: 'RDS'
98
+ })
99
+ ).toEqual({
100
+ provider: 'aws',
101
+ rejectedFields: ['errorCode', 'operation', 'retryable', 'service']
102
+ });
103
+ });
104
+
105
+ test('applies the persistence cap after validation', () => {
106
+ const valid = {
107
+ nextAction: 'ignored',
108
+ phase: 'apply',
109
+ provider: 'postgresql',
110
+ retryable: false,
111
+ sqlState: '28P01'
112
+ };
113
+
114
+ expect(
115
+ sanitizeLifecycleProviderDiagnostics([
116
+ { provider: 'postgresql', sqlState: 'invalid' },
117
+ valid,
118
+ { ...valid, sqlState: '42P07' },
119
+ { ...valid, sqlState: '08006' },
120
+ { ...valid, sqlState: '42501' }
121
+ ])
122
+ ).toEqual([
123
+ { nextAction: 'review_database_operation', phase: 'apply', provider: 'postgresql', retryable: false, sqlState: '28P01' },
124
+ { nextAction: 'review_database_operation', phase: 'apply', provider: 'postgresql', retryable: false, sqlState: '42P07' },
125
+ { nextAction: 'review_database_operation', phase: 'apply', provider: 'postgresql', retryable: false, sqlState: '08006' }
126
+ ]);
127
+ });
128
+
129
+ test('treats non-array payloads and arrays without valid entries as absent', () => {
130
+ expect(sanitizeLifecycleProviderDiagnostics(null)).toBeUndefined();
131
+ expect(sanitizeLifecycleProviderDiagnostics({ provider: 'aws' })).toBeUndefined();
132
+ expect(sanitizeLifecycleProviderDiagnostics([{ provider: 'unknown' }])).toBeUndefined();
133
+ });
134
+ });
@@ -0,0 +1,326 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { AppDatabaseCompatibility } from '../database-lifecycle/appDatabaseCompatibility.js';
4
+ import { ViewMode } from '../types/event/index.js';
5
+ import {
6
+ getAppDatabaseRemediationActions,
7
+ getIntegrationRemediationActions,
8
+ rollUpAppEnvironmentCompatibility,
9
+ summarizeIntegrationConfigurations,
10
+ summarizeIntegrationCoverage
11
+ } from './appEnvironmentCompatibility.js';
12
+
13
+ const compatibleAppDatabase: AppDatabaseCompatibility = {
14
+ applicationId: 'application-id',
15
+ hasAppDatabase: true,
16
+ needsAttention: false,
17
+ overallState: 'compatible',
18
+ tags: []
19
+ };
20
+
21
+ describe('app Environment compatibility contract', () => {
22
+ it('summarizes configured integration data tags and default fallback', () => {
23
+ expect(
24
+ summarizeIntegrationConfigurations([
25
+ { isDefault: false, profileNames: ['staging', 'production'] },
26
+ { isDefault: false, profileNames: ['staging'] },
27
+ { isDefault: true }
28
+ ])
29
+ ).toEqual({
30
+ hasDefaultConfiguration: true,
31
+ supportedProfileKeys: ['production', 'staging']
32
+ });
33
+ expect(summarizeIntegrationConfigurations([])).toEqual({
34
+ hasDefaultConfiguration: false,
35
+ supportedProfileKeys: []
36
+ });
37
+ });
38
+
39
+ it('marks integration gaps as warnings without blocking', () => {
40
+ expect(
41
+ rollUpAppEnvironmentCompatibility({
42
+ appDatabase: compatibleAppDatabase,
43
+ integrations: [
44
+ {
45
+ integrationId: 'integration-id',
46
+ integrationName: 'Warehouse',
47
+ mismatchedAppProfileKeys: ['production'],
48
+ pluginId: 'postgres',
49
+ supportedProfileKeys: [],
50
+ tags: [
51
+ {
52
+ availableModes: [ViewMode.DEPLOYED],
53
+ profileId: 'profile-id',
54
+ profileKey: 'production',
55
+ remediationActions: ['configure_integration', 'change_app_environment'],
56
+ state: 'none'
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ })
62
+ ).toEqual({
63
+ isBlocking: false,
64
+ needsAttention: true,
65
+ overallState: 'warning',
66
+ remediationActions: ['change_app_environment', 'configure_integration']
67
+ });
68
+ });
69
+
70
+ it('keeps App Database incompatibility blocking when integrations are compatible', () => {
71
+ expect(
72
+ rollUpAppEnvironmentCompatibility({
73
+ appDatabase: {
74
+ ...compatibleAppDatabase,
75
+ needsAttention: true,
76
+ overallState: 'incompatible'
77
+ },
78
+ integrations: []
79
+ })
80
+ ).toEqual({
81
+ isBlocking: true,
82
+ needsAttention: true,
83
+ overallState: 'blocked',
84
+ remediationActions: []
85
+ });
86
+ });
87
+
88
+ it('does not block environment changes when the app has no App Database', () => {
89
+ expect(
90
+ rollUpAppEnvironmentCompatibility({
91
+ appDatabase: {
92
+ ...compatibleAppDatabase,
93
+ hasAppDatabase: false,
94
+ needsAttention: true,
95
+ overallState: 'incompatible'
96
+ },
97
+ appDatabaseRemediationActions: getAppDatabaseRemediationActions('incompatible', true),
98
+ integrations: []
99
+ })
100
+ ).toEqual({
101
+ isBlocking: false,
102
+ needsAttention: false,
103
+ overallState: 'compatible',
104
+ remediationActions: []
105
+ });
106
+ });
107
+
108
+ it('blocks a temporarily unavailable App Database, matching the server gate', () => {
109
+ expect(
110
+ rollUpAppEnvironmentCompatibility({
111
+ appDatabase: {
112
+ ...compatibleAppDatabase,
113
+ overallState: 'temporarily_unavailable'
114
+ },
115
+ appDatabaseRemediationActions: getAppDatabaseRemediationActions('temporarily_unavailable', true),
116
+ integrations: []
117
+ })
118
+ ).toEqual({
119
+ isBlocking: true,
120
+ needsAttention: true,
121
+ overallState: 'blocked',
122
+ remediationActions: ['retry_later']
123
+ });
124
+ });
125
+
126
+ it('keeps a temporarily unavailable integration tag a non-blocking warning', () => {
127
+ expect(
128
+ rollUpAppEnvironmentCompatibility({
129
+ appDatabase: compatibleAppDatabase,
130
+ integrations: [
131
+ {
132
+ integrationId: 'integration-id',
133
+ integrationName: 'Warehouse',
134
+ mismatchedAppProfileKeys: ['production'],
135
+ pluginId: 'postgres',
136
+ supportedProfileKeys: [],
137
+ tags: [
138
+ {
139
+ availableModes: [ViewMode.DEPLOYED],
140
+ profileId: 'profile-id',
141
+ profileKey: 'production',
142
+ remediationActions: ['retry_later'],
143
+ state: 'temporarily_unavailable'
144
+ }
145
+ ]
146
+ }
147
+ ]
148
+ })
149
+ ).toEqual({
150
+ isBlocking: false,
151
+ needsAttention: true,
152
+ overallState: 'warning',
153
+ remediationActions: ['retry_later']
154
+ });
155
+ });
156
+
157
+ it('does not surface remediations for organization tags the app does not use', () => {
158
+ expect(
159
+ rollUpAppEnvironmentCompatibility({
160
+ appDatabase: compatibleAppDatabase,
161
+ integrations: [
162
+ {
163
+ integrationId: 'integration-id',
164
+ integrationName: 'Warehouse',
165
+ mismatchedAppProfileKeys: [],
166
+ pluginId: 'postgres',
167
+ supportedProfileKeys: [],
168
+ tags: [
169
+ {
170
+ availableModes: [],
171
+ profileId: 'unused-id',
172
+ profileKey: 'unused',
173
+ remediationActions: ['change_app_environment', 'configure_integration'],
174
+ state: 'none'
175
+ }
176
+ ]
177
+ }
178
+ ]
179
+ })
180
+ ).toEqual({
181
+ isBlocking: false,
182
+ needsAttention: false,
183
+ overallState: 'compatible',
184
+ remediationActions: []
185
+ });
186
+ });
187
+
188
+ it('reports a temporary integration read failure as a non-blocking warning', () => {
189
+ expect(
190
+ rollUpAppEnvironmentCompatibility({
191
+ appDatabase: compatibleAppDatabase,
192
+ integrationReadState: 'temporarily_unavailable',
193
+ integrations: []
194
+ })
195
+ ).toEqual({
196
+ isBlocking: false,
197
+ needsAttention: true,
198
+ overallState: 'warning',
199
+ remediationActions: ['retry_later']
200
+ });
201
+ });
202
+
203
+ it('deduplicates and sorts server-provided remediation actions', () => {
204
+ expect(
205
+ rollUpAppEnvironmentCompatibility({
206
+ appDatabase: compatibleAppDatabase,
207
+ integrations: [
208
+ {
209
+ integrationId: 'integration-id',
210
+ integrationName: 'Warehouse',
211
+ mismatchedAppProfileKeys: ['production'],
212
+ pluginId: 'postgres',
213
+ supportedProfileKeys: [],
214
+ tags: [
215
+ {
216
+ availableModes: [ViewMode.DEPLOYED],
217
+ profileId: 'first-profile',
218
+ profileKey: 'production',
219
+ remediationActions: ['configure_integration', 'request_environment_access'],
220
+ state: 'none'
221
+ },
222
+ {
223
+ availableModes: [ViewMode.EDITOR],
224
+ profileId: 'second-profile',
225
+ profileKey: 'staging',
226
+ remediationActions: ['retry_later', 'configure_integration'],
227
+ state: 'temporarily_unavailable'
228
+ }
229
+ ]
230
+ }
231
+ ]
232
+ }).remediationActions
233
+ ).toEqual(['configure_integration', 'request_environment_access', 'retry_later']);
234
+ });
235
+
236
+ it('treats default-fallback tags as supported because the integration still works', () => {
237
+ expect(
238
+ summarizeIntegrationCoverage([
239
+ {
240
+ availableModes: [ViewMode.EDITOR],
241
+ profileId: 'staging-id',
242
+ profileKey: 'staging',
243
+ remediationActions: [],
244
+ state: 'exact'
245
+ },
246
+ {
247
+ availableModes: [ViewMode.DEPLOYED],
248
+ profileId: 'production-id',
249
+ profileKey: 'production',
250
+ remediationActions: [],
251
+ state: 'default_fallback'
252
+ },
253
+ {
254
+ availableModes: [],
255
+ profileId: 'prod-eu-id',
256
+ profileKey: 'prod-eu',
257
+ remediationActions: [],
258
+ state: 'exact'
259
+ },
260
+ {
261
+ availableModes: [ViewMode.PREVIEW],
262
+ profileId: 'preview-id',
263
+ profileKey: 'preview',
264
+ remediationActions: ['configure_integration'],
265
+ state: 'none'
266
+ },
267
+ {
268
+ availableModes: [],
269
+ profileId: 'unused-id',
270
+ profileKey: 'unused',
271
+ remediationActions: [],
272
+ state: 'none'
273
+ }
274
+ ])
275
+ ).toEqual({
276
+ mismatchedAppProfileKeys: ['preview'],
277
+ supportedProfileKeys: ['prod-eu', 'production', 'staging']
278
+ });
279
+ });
280
+
281
+ it('does not warn when an app tag uses the integration default configuration', () => {
282
+ expect(
283
+ rollUpAppEnvironmentCompatibility({
284
+ appDatabase: compatibleAppDatabase,
285
+ integrations: [
286
+ {
287
+ integrationId: 'integration-id',
288
+ integrationName: 'Warehouse',
289
+ mismatchedAppProfileKeys: [],
290
+ pluginId: 'postgres',
291
+ supportedProfileKeys: ['production'],
292
+ tags: [
293
+ {
294
+ availableModes: [ViewMode.DEPLOYED],
295
+ profileId: 'production-id',
296
+ profileKey: 'production',
297
+ remediationActions: [],
298
+ state: 'default_fallback'
299
+ }
300
+ ]
301
+ }
302
+ ]
303
+ })
304
+ ).toEqual({
305
+ isBlocking: false,
306
+ needsAttention: false,
307
+ overallState: 'compatible',
308
+ remediationActions: []
309
+ });
310
+ });
311
+
312
+ it('returns only failure-specific integration remediation actions', () => {
313
+ expect(getIntegrationRemediationActions('exact', true)).toEqual([]);
314
+ expect(getIntegrationRemediationActions('default_fallback', true)).toEqual([]);
315
+ expect(getIntegrationRemediationActions('none', true)).toEqual(['change_app_environment', 'configure_integration']);
316
+ expect(getIntegrationRemediationActions('none', false)).toEqual(['configure_integration', 'request_environment_access']);
317
+ expect(getIntegrationRemediationActions('temporarily_unavailable', true)).toEqual(['retry_later']);
318
+ });
319
+
320
+ it('returns only failure-specific App Database remediation actions', () => {
321
+ expect(getAppDatabaseRemediationActions('compatible', true)).toEqual([]);
322
+ expect(getAppDatabaseRemediationActions('incompatible', true)).toEqual(['change_app_environment', 'connect_lifecycle_agent']);
323
+ expect(getAppDatabaseRemediationActions('incompatible', false)).toEqual(['connect_lifecycle_agent', 'request_environment_access']);
324
+ expect(getAppDatabaseRemediationActions('temporarily_unavailable', true)).toEqual(['retry_later']);
325
+ });
326
+ });
@@ -0,0 +1,173 @@
1
+ import type { AppDatabaseCompatibility, AppDatabaseCompatibilityState } from '../database-lifecycle/appDatabaseCompatibility.js';
2
+ import type { ViewMode } from '../types/event/index.js';
3
+
4
+ export type AppEnvironmentRemediationAction =
5
+ | 'change_app_environment'
6
+ | 'configure_integration'
7
+ | 'connect_lifecycle_agent'
8
+ | 'request_environment_access'
9
+ | 'retry_later';
10
+ export const APP_ENVIRONMENT_REMEDIATION_ACTIONS: readonly AppEnvironmentRemediationAction[] = [
11
+ 'change_app_environment',
12
+ 'configure_integration',
13
+ 'connect_lifecycle_agent',
14
+ 'request_environment_access',
15
+ 'retry_later'
16
+ ];
17
+
18
+ export type IntegrationCompatibilityState = 'default_fallback' | 'exact' | 'none' | 'temporarily_unavailable';
19
+ export const INTEGRATION_COMPATIBILITY_STATES: readonly IntegrationCompatibilityState[] = [
20
+ 'default_fallback',
21
+ 'exact',
22
+ 'none',
23
+ 'temporarily_unavailable'
24
+ ];
25
+
26
+ export type IntegrationCompatibilityReadState = 'available' | 'temporarily_unavailable';
27
+ export const INTEGRATION_COMPATIBILITY_READ_STATES: readonly IntegrationCompatibilityReadState[] = ['available', 'temporarily_unavailable'];
28
+
29
+ export type AppEnvironmentCompatibilityState = 'blocked' | 'compatible' | 'warning';
30
+ export const APP_ENVIRONMENT_COMPATIBILITY_STATES: readonly AppEnvironmentCompatibilityState[] = ['blocked', 'compatible', 'warning'];
31
+
32
+ export type IntegrationTagCompatibility = {
33
+ availableModes: ViewMode[];
34
+ profileId: string;
35
+ profileKey: string;
36
+ remediationActions: AppEnvironmentRemediationAction[];
37
+ state: IntegrationCompatibilityState;
38
+ };
39
+
40
+ export type AppEnvironmentDataTag = {
41
+ availableModes: ViewMode[];
42
+ profileId: string;
43
+ profileKey: string;
44
+ };
45
+
46
+ export type OrganizationEnvironmentDataTag = {
47
+ displayName: string;
48
+ profileId: string;
49
+ profileKey: string;
50
+ };
51
+
52
+ export type IntegrationCompatibility = {
53
+ integrationId: string;
54
+ integrationName: string;
55
+ mismatchedAppProfileKeys: string[];
56
+ pluginId: string;
57
+ supportedProfileKeys: string[];
58
+ tags: IntegrationTagCompatibility[];
59
+ };
60
+
61
+ export type AppEnvironmentCompatibility = {
62
+ appDatabase: AppDatabaseCompatibility;
63
+ appDataTags: AppEnvironmentDataTag[];
64
+ applicationId: string;
65
+ canManageEnvironments: boolean;
66
+ integrationReadState: IntegrationCompatibilityReadState;
67
+ integrations: IntegrationCompatibility[];
68
+ isBlocking: boolean;
69
+ needsAttention: boolean;
70
+ organizationDataTags: OrganizationEnvironmentDataTag[];
71
+ overallState: AppEnvironmentCompatibilityState;
72
+ remediationActions: AppEnvironmentRemediationAction[];
73
+ };
74
+
75
+ export function summarizeIntegrationConfigurations(
76
+ configurations: readonly {
77
+ isDefault: boolean;
78
+ profileNames?: readonly string[];
79
+ }[]
80
+ ): {
81
+ hasDefaultConfiguration: boolean;
82
+ supportedProfileKeys: string[];
83
+ } {
84
+ return {
85
+ hasDefaultConfiguration: configurations.some((configuration) => configuration.isDefault),
86
+ supportedProfileKeys: [...new Set(configurations.flatMap((configuration) => configuration.profileNames ?? []))].sort()
87
+ };
88
+ }
89
+
90
+ export function summarizeIntegrationCoverage(
91
+ tags: readonly IntegrationTagCompatibility[]
92
+ ): Pick<IntegrationCompatibility, 'mismatchedAppProfileKeys' | 'supportedProfileKeys'> {
93
+ return {
94
+ mismatchedAppProfileKeys: tags
95
+ .filter((tag) => tag.availableModes.length > 0 && tag.state !== 'exact' && tag.state !== 'default_fallback')
96
+ .map((tag) => tag.profileKey)
97
+ .sort(),
98
+ supportedProfileKeys: tags
99
+ .filter((tag) => tag.state === 'exact' || tag.state === 'default_fallback')
100
+ .map((tag) => tag.profileKey)
101
+ .sort()
102
+ };
103
+ }
104
+
105
+ export function getIntegrationRemediationActions(
106
+ state: IntegrationCompatibilityState,
107
+ canManageEnvironments: boolean
108
+ ): AppEnvironmentRemediationAction[] {
109
+ if (state === 'exact' || state === 'default_fallback') {
110
+ return [];
111
+ }
112
+ if (state === 'temporarily_unavailable') {
113
+ return ['retry_later'];
114
+ }
115
+ return canManageEnvironments
116
+ ? ['change_app_environment', 'configure_integration']
117
+ : ['configure_integration', 'request_environment_access'];
118
+ }
119
+
120
+ export function getAppDatabaseRemediationActions(
121
+ state: AppDatabaseCompatibilityState,
122
+ canManageEnvironments: boolean
123
+ ): AppEnvironmentRemediationAction[] {
124
+ if (state === 'compatible') {
125
+ return [];
126
+ }
127
+ if (state === 'temporarily_unavailable') {
128
+ return ['retry_later'];
129
+ }
130
+ return canManageEnvironments
131
+ ? ['change_app_environment', 'connect_lifecycle_agent']
132
+ : ['connect_lifecycle_agent', 'request_environment_access'];
133
+ }
134
+
135
+ export function rollUpAppEnvironmentCompatibility({
136
+ appDatabase,
137
+ appDatabaseRemediationActions = [],
138
+ integrationReadState = 'available',
139
+ integrations
140
+ }: {
141
+ appDatabase: AppDatabaseCompatibility;
142
+ appDatabaseRemediationActions?: AppEnvironmentRemediationAction[];
143
+ integrationReadState?: IntegrationCompatibilityReadState;
144
+ integrations: readonly IntegrationCompatibility[];
145
+ }): Pick<AppEnvironmentCompatibility, 'isBlocking' | 'needsAttention' | 'overallState' | 'remediationActions'> {
146
+ const integrationNeedsAttention = integrations.some((integration) =>
147
+ integration.tags.some((tag) => tag.state === 'temporarily_unavailable' || (tag.availableModes.length > 0 && tag.state === 'none'))
148
+ );
149
+ // The settings write only refuses App Database apps. An unserved data tag on
150
+ // an ordinary app is not a block, so previewing it as one would stop a change
151
+ // the server would accept.
152
+ const appDatabaseBlocks = appDatabase.hasAppDatabase && appDatabase.overallState !== 'compatible';
153
+ const isBlocking = appDatabaseBlocks;
154
+ const needsAttention = appDatabaseBlocks || integrationNeedsAttention || integrationReadState === 'temporarily_unavailable';
155
+ const readRemediationActions: AppEnvironmentRemediationAction[] =
156
+ integrationReadState === 'temporarily_unavailable' ? ['retry_later'] : [];
157
+ const remediationActions = [
158
+ ...new Set([
159
+ ...(appDatabaseBlocks ? appDatabaseRemediationActions : []),
160
+ ...readRemediationActions,
161
+ ...integrations.flatMap((integration) =>
162
+ integration.tags.flatMap((tag) => (tag.availableModes.length > 0 ? tag.remediationActions : []))
163
+ )
164
+ ])
165
+ ].sort();
166
+
167
+ return {
168
+ isBlocking,
169
+ needsAttention,
170
+ overallState: isBlocking ? 'blocked' : needsAttention ? 'warning' : 'compatible',
171
+ remediationActions
172
+ };
173
+ }
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { BadGatewayError, GatewayTimeoutError, InternalServerError, RetryableHttpError, ServiceUnavailableError } from './http.js';
4
+
5
+ describe('GatewayTimeoutError', () => {
6
+ it('reports 504 so a caller can tell a slow upstream from a broken server', () => {
7
+ const error = new GatewayTimeoutError('upstream took too long');
8
+
9
+ expect(error.status).toBe(504);
10
+ expect(error.message).toBe('upstream took too long');
11
+ });
12
+ });
13
+
14
+ describe('retry hints', () => {
15
+ const retryableErrors: [number, (retryAfterSeconds?: number) => RetryableHttpError][] = [
16
+ [502, (retryAfterSeconds) => new BadGatewayError('upstream refused', retryAfterSeconds)],
17
+ [503, (retryAfterSeconds) => new ServiceUnavailableError('upstream at capacity', retryAfterSeconds)],
18
+ [504, (retryAfterSeconds) => new GatewayTimeoutError('upstream took too long', retryAfterSeconds)]
19
+ ];
20
+
21
+ it.each(retryableErrors)('a %i carries the seconds a client should wait', (status, build) => {
22
+ expect(build(7)).toMatchObject({ status, retryAfterSeconds: 7 });
23
+ });
24
+
25
+ it.each(retryableErrors)('a %i without a hint leaves the wait unspecified', (_status, build) => {
26
+ expect(build().retryAfterSeconds).toBeUndefined();
27
+ });
28
+
29
+ it('never invites a retry of an outright server fault', () => {
30
+ expect(new InternalServerError('it broke')).not.toBeInstanceOf(RetryableHttpError);
31
+ });
32
+ });