@hostwebhook/platform-contracts 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +64 -4
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +2 -0
  4. package/dist/operadores.d.ts +60 -0
  5. package/dist/operadores.js +123 -0
  6. package/dist/servicios/almacenes-vectoriales.d.ts +105 -0
  7. package/dist/servicios/almacenes-vectoriales.js +26 -0
  8. package/dist/servicios/aprobaciones-pendientes.d.ts +207 -0
  9. package/dist/servicios/aprobaciones-pendientes.js +51 -0
  10. package/dist/servicios/avisos-en-vivo.d.ts +106 -0
  11. package/dist/servicios/avisos-en-vivo.js +60 -0
  12. package/dist/servicios/canal-de-stream.d.ts +78 -0
  13. package/dist/servicios/canal-de-stream.js +5 -0
  14. package/dist/servicios/conversaciones-del-chat.d.ts +139 -0
  15. package/dist/servicios/conversaciones-del-chat.js +51 -0
  16. package/dist/servicios/corridas-programadas.d.ts +29 -0
  17. package/dist/servicios/corridas-programadas.js +5 -0
  18. package/dist/servicios/credencial/permiso-sobre-credencial.d.ts +61 -0
  19. package/dist/servicios/credencial/permiso-sobre-credencial.js +42 -0
  20. package/dist/servicios/credenciales-del-gateway.d.ts +722 -0
  21. package/dist/servicios/credenciales-del-gateway.js +114 -0
  22. package/dist/servicios/cuentas-de-atlassian.d.ts +22 -0
  23. package/dist/servicios/cuentas-de-atlassian.js +5 -0
  24. package/dist/servicios/descarga-de-drive.d.ts +74 -0
  25. package/dist/servicios/descarga-de-drive.js +41 -0
  26. package/dist/servicios/ejecucion-de-ia.d.ts +81 -0
  27. package/dist/servicios/ejecucion-de-ia.js +39 -0
  28. package/dist/servicios/enlaces-de-flujo.d.ts +59 -0
  29. package/dist/servicios/enlaces-de-flujo.js +27 -0
  30. package/dist/servicios/ficheros-del-gateway.d.ts +210 -0
  31. package/dist/servicios/ficheros-del-gateway.js +8 -0
  32. package/dist/servicios/formas-compartidas.d.ts +57 -0
  33. package/dist/servicios/formas-compartidas.js +22 -0
  34. package/dist/servicios/historial-de-corridas.d.ts +75 -0
  35. package/dist/servicios/historial-de-corridas.js +7 -0
  36. package/dist/servicios/index.d.ts +57 -0
  37. package/dist/servicios/index.js +44 -0
  38. package/dist/servicios/limites-del-plan.d.ts +82 -0
  39. package/dist/servicios/limites-del-plan.js +41 -0
  40. package/dist/servicios/motor-de-ejecucion.d.ts +197 -0
  41. package/dist/servicios/motor-de-ejecucion.js +11 -0
  42. package/dist/servicios/plantillas-de-origen.d.ts +25 -0
  43. package/dist/servicios/plantillas-de-origen.js +5 -0
  44. package/dist/servicios/posts-sociales.d.ts +69 -0
  45. package/dist/servicios/posts-sociales.js +23 -0
  46. package/dist/servicios/registro-de-entregas.d.ts +93 -0
  47. package/dist/servicios/registro-de-entregas.js +46 -0
  48. package/dist/servicios/registro-de-eventos.d.ts +135 -0
  49. package/dist/servicios/registro-de-eventos.js +62 -0
  50. package/dist/servicios/restauracion-de-nodos.d.ts +53 -0
  51. package/dist/servicios/restauracion-de-nodos.js +4 -0
  52. package/dist/servicios/salud-del-webhook.d.ts +26 -0
  53. package/dist/servicios/salud-del-webhook.js +6 -0
  54. package/dist/servicios/secretos-de-firma.d.ts +26 -0
  55. package/dist/servicios/secretos-de-firma.js +5 -0
  56. package/dist/servicios/telemetria.d.ts +114 -0
  57. package/dist/servicios/telemetria.js +60 -0
  58. package/dist/servicios/trazas-de-llm.d.ts +79 -0
  59. package/dist/servicios/trazas-de-llm.js +23 -0
  60. package/dist/servicios/tuneles.d.ts +106 -0
  61. package/dist/servicios/tuneles.js +87 -0
  62. package/dist/servicios/workspace/acceso-al-recurso.d.ts +39 -0
  63. package/dist/servicios/workspace/acceso-al-recurso.js +5 -0
  64. package/dist/servicios/zona-horaria-del-usuario.d.ts +40 -0
  65. package/dist/servicios/zona-horaria-del-usuario.js +7 -0
  66. package/package.json +9 -3
@@ -0,0 +1,722 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+ import type { CredentialType } from '@hostwebhook/node-types';
3
+ /**
4
+ * Lo que un nodo pregunta al gateway sobre una credencial.
5
+ *
6
+ * ## Esta costura NO se corta como las otras diecinueve
7
+ *
8
+ * En todas las anteriores bastaba con invertir la dependencia: declarar la
9
+ * superficie usada y atarla con un token. Aquí eso sería un error de diseño.
10
+ *
11
+ * Un `getDecrypted(id)` por token funcionaría perfectamente dentro del repo, y
12
+ * el día que la costura fuera HTTP significaría **hw-nodes pidiendo secretos
13
+ * descifrados por la red en cada ejecución, sin que nadie compruebe que ese
14
+ * nodo tenga algo que ver con esa credencial**. Es justo lo que el
15
+ * `hw-credentials` de la Fase 4 existe para evitar. Cortarla así sería trabajo
16
+ * que hay que rehacer.
17
+ *
18
+ * Así que este contrato se construye en la forma FINAL desde el principio:
19
+ *
20
+ * describir() metadatos. No trae el secreto, y no puede traerlo.
21
+ * material() el secreto, y SIEMPRE diciendo quién lo pide. (PR C)
22
+ * tokenDe…() tokens ya refrescados; el cliente se monta en el nodo. (PR C)
23
+ *
24
+ * Este PR trae sólo `describir`. Cada método llega cuando su implementación
25
+ * está atada — un contrato con métodos que nadie provee es una mentira que
26
+ * compila.
27
+ *
28
+ * ## Por qué `describir` no lleva quién pide, y `material` sí
29
+ *
30
+ * Porque no son la misma pregunta. `credentials.service.findOne` hace
31
+ * `.select('-encryptedData')`: devuelve el tipo, el nombre y los metadatos, y
32
+ * el secreto NO sale por ahí. Exigir atribución para leer el tipo de una
33
+ * credencial sería ruido en el registro de accesos, que existe para responder
34
+ * «quién leyó este secreto».
35
+ */
36
+ /**
37
+ * Los metadatos de una credencial. Medido dos veces, por caminos distintos:
38
+ * de los 53 sitios que llaman a `findOne`, lo único que se lee del documento
39
+ * es `type` (161 veces), `metadata` (25) y `name` (6).
40
+ *
41
+ * ⚠️ Los `accessToken`, `phoneNumberId` y `organizationId` que aparecen al
42
+ * barrer NO salen de aquí: son blobs ya descifrados, o documentos que devuelve
43
+ * `SlackOAuthService.findCredentialsByTeamId`. Se comprobó uno por uno.
44
+ */
45
+ export interface DescripcionDeCredencial {
46
+ id: string;
47
+ /**
48
+ * ⚠️ El tipo viene de `@hostwebhook/node-types`, no de la entidad. Es un
49
+ * paquete, así que los dos lados lo alcanzan y este contrato no toca
50
+ * Mongoose ni de refilón.
51
+ */
52
+ type: CredentialType;
53
+ name: string;
54
+ /**
55
+ * ⚠️ Los metadatos SIN pasar por `toJSON()`.
56
+ *
57
+ * El `toJSON` de la entidad los borra cuando la credencial está marcada
58
+ * como sensible — eso es para la respuesta HTTP, no para el motor. Quien
59
+ * los lee aquí es el enrutado por túnel y la zona horaria del calendario,
60
+ * que necesitan el valor real. Aplanar con `toJSON()` los dejaría vacíos
61
+ * para las credenciales sensibles, en silencio y sólo para algunas.
62
+ */
63
+ metadata: Record<string, unknown>;
64
+ }
65
+ /**
66
+ * Quién pide un secreto, y con qué derecho. Las dos formas son EXPLÍCITAS.
67
+ *
68
+ * ## Por qué es obligatorio y no un `context?` opcional
69
+ *
70
+ * Porque opcional es lo que hay hoy y no funciona. De las 59 lecturas de
71
+ * secretos en `nodes/` + `common/`:
72
+ *
73
+ * 19 dicen de verdad qué nodo fue
74
+ * 7 pasan `relatedEntity` con `id: ctx.entityId ?? ''` — el campo está
75
+ * y el id viene VACÍO, así que la fila del registro dice
76
+ * «un social_media_action, id ''», que no enlaza con nada
77
+ * 8 sólo un `purpose`
78
+ * 25 nada
79
+ *
80
+ * El registro de accesos del api#433 existe y funciona; dos tercios de las
81
+ * lecturas no lo alimentan. Ponerlo en el TIPO lo arregla de una vez y sin
82
+ * riesgo de ejecución: lo que falta no compila.
83
+ *
84
+ * ⚠️ El caso del id vacío no lo caza TypeScript —`'' `es un `string` válido—,
85
+ * así que el adaptador lo comprueba y lo apunta. Ver `comoAtribuir`.
86
+ */
87
+ /**
88
+ * Los tipos de entidad que pueden pedir una credencial.
89
+ *
90
+ * ## Por qué es una unión y no un `string`
91
+ *
92
+ * Porque con `string` la grafía se tuerce sola. El registro de accesos tenía
93
+ * las dos mezcladas —`telegramAction` y `discordAction` en camel;
94
+ * `ai_node`, `social_media_action`, `vector_store` y `voice_agent` en snake—
95
+ * y `relatedEntityType` se agrupa con
96
+ * `$group: { _id: { type: '$relatedEntityType', id: '$relatedEntityId' } }`.
97
+ * Dos grafías del mismo tipo son dos entidades distintas al mirar el registro.
98
+ *
99
+ * ⚠️ Se elige CAMEL porque es lo que casa con el `nodeType` real que registra
100
+ * cada nodo (`socialMediaAction`, `docsAction`, `sheetsAction`…). Snake era
101
+ * ad hoc. Con esto, una grafía nueva no compila hasta que se declara aquí, que
102
+ * es el momento de decidirla.
103
+ *
104
+ * ⚠️ Y sí, cambiar `ai_node` por `aiNode` parte la agrupación de las filas
105
+ * viejas. El `id` sigue siendo el mismo —que es la clave de verdad— y el
106
+ * registro es forense, no un índice: se prefiere que de hoy en adelante diga
107
+ * una sola cosa.
108
+ */
109
+ export type TipoDeEntidad = 'trigger' | 'webhook' | 'aiNode' | 'socialMediaAction' | 'socialPost' | 'calendarAction' | 'docsAction' | 'driveAction' | 'sheetsAction' | 'emailAction' | 'gmailAction' | 'approvalNode' | 'telegramAction' | 'whatsappAction' | 'discordAction' | 'slackAction' | 'mongoAction' | 'postgresAction' | 'httpAction' | 'githubAction' | 'notionAction' | 'jiraAction' | 'shopifyAction' | 'mailchimpAction' | 'firecrawlAction' | 'notificationAction' | 'googleAnalyticsAction' | 'googleContactsAction' | 'vectorStore' | 'voiceAgent';
110
+ /**
111
+ * Por qué esta lectura NO se puede comprobar contra el documento de hoy.
112
+ *
113
+ * ⚠️ Son excepciones NOMBRADAS, no agujeros. En los dos casos el documento
114
+ * correcto contra el que comprobar **ya no está en la base**, así que releerlo
115
+ * no arregla nada: daría `sin_vinculo` sobre una lectura perfectamente
116
+ * legítima. Y como van con nombre, se cuentan aparte en el registro y no se
117
+ * confunden nunca con un `ok`.
118
+ *
119
+ * `baja-de-suscripcion` Al cambiar o desconectar la credencial de un
120
+ * trigger, la baja se da con el documento ANTERIOR —a propósito, porque
121
+ * hay que hablar con el proveedor usando la credencial VIEJA— mientras la
122
+ * base ya tiene la nueva. Comprobar contra la base denegaría el 100% de
123
+ * las veces, y el `catch` de ese camino sólo hace `warn`: sería un fallo
124
+ * MUDO con el proveedor siguiendo empujando a un trigger muerto.
125
+ *
126
+ * `plan-congelado` La aprobación de social media guarda el plan con sus
127
+ * `credentialId` y lo despacha horas después. Si en medio alguien quita
128
+ * una cuenta del selector, el nodo ya no la referencia — pero la
129
+ * publicación que se aprobó sí la incluía.
130
+ *
131
+ * ⚠️ Añadir un motivo nuevo es abrir una puerta: hay un guardián que enumera
132
+ * quién puede usar cada uno, para que la lista no crezca sin que se vea.
133
+ */
134
+ export type MotivoSinComprobar = 'baja-de-suscripcion' | 'plan-congelado';
135
+ export type QuienPide =
136
+ /**
137
+ * Un nodo, trigger o entidad del dominio. Es el caso normal.
138
+ *
139
+ * `type` e `id` son los que ya usa `relatedEntity` del registro de accesos,
140
+ * para que las filas de antes y las de ahora se lean igual.
141
+ */
142
+ {
143
+ modo: 'nodo';
144
+ type: TipoDeEntidad;
145
+ id: string;
146
+ motivo?: MotivoSinComprobar;
147
+ /**
148
+ * Quién lo disparó, cuando lo disparó alguien.
149
+ *
150
+ * 🔥 No es redundante con `modo: 'usuario'`, y lo parecía. Los dos modos
151
+ * responden a preguntas distintas: `modo` dice contra QUÉ se comprueba el
152
+ * vínculo —un nodo o una persona—, y esto dice quién apretó el botón.
153
+ *
154
+ * Existe porque el gateway ya hacía las dos cosas a la vez y la unión no
155
+ * lo dejaba decir: `vector_store_embed` pasa `userId` Y
156
+ * `relatedEntity: {type:'vectorStore'}`, porque una persona pidió
157
+ * incrustar en un almacén concreto. Con la unión a secas había que elegir
158
+ * una de las dos, y elegir es perder una columna del registro sin que
159
+ * nada falle.
160
+ */
161
+ userId?: string;
162
+ /** Desde dónde. Ver `sourceIp` en el modo `usuario`. */
163
+ sourceIp?: string;
164
+ }
165
+ /**
166
+ * Una persona con sesión que eligió la credencial a mano.
167
+ *
168
+ * ⚠️ Es la excepción, y existe porque hay un caso que no puede ser `nodo`:
169
+ * `ai-nodes.controller` lista voces para una credencial que la persona
170
+ * acaba de elegir y cuyo nodo puede no existir todavía. Sin este modo, ese
171
+ * endpoint tendría que inventarse un nodo o quedarse sin atribuir — y
172
+ * inventárselo es peor, porque ensucia el registro con filas que apuntan a
173
+ * cosas que no existen.
174
+ *
175
+ * La lista de quién usa este modo la fija un guardián, para que no crezca
176
+ * sin que se vea.
177
+ */
178
+ | {
179
+ modo: 'usuario';
180
+ userId: string;
181
+ /**
182
+ * La IP desde la que se pidió, si el llamante la tiene.
183
+ *
184
+ * ⚠️ El registro de accesos SIEMPRE ha tenido esta columna y seis
185
+ * llamantes del gateway la rellenaban. El contrato no la llevaba, así que
186
+ * pasarlos a él la habría vaciado — sin error, sin test rojo, y sin que
187
+ * nadie mirara esa columna hasta el día que hiciera falta.
188
+ */
189
+ sourceIp?: string;
190
+ };
191
+ /** El secreto, con el tipo que dice cómo interpretarlo. */
192
+ export interface MaterialDeCredencial {
193
+ type: CredentialType;
194
+ /**
195
+ * Los metadatos, que ya viajan por `describir` y aquí ahorran un viaje.
196
+ *
197
+ * ⚠️ No es comodidad: la familia de bases de datos leía la MISMA metadata
198
+ * dos veces por despacho —una con `findOne`, para la base efectiva, y otra
199
+ * con `getTunnelIdForCredential`, que sólo mira `metadata.tunnelId`— además
200
+ * del `getDecrypted`. Tres consultas para una credencial. Con esto es una.
201
+ *
202
+ * ⚠️ Y llega SIN pasar por `toJSON()`, igual que en `describir`: ese
203
+ * transform borra `metadata` en las credenciales marcadas sensibles, que es
204
+ * justo de donde sale el `tunnelId` del enrutado. Es la tercera vez que esa
205
+ * decisión del adaptador resulta ser carga.
206
+ */
207
+ metadata: Record<string, unknown>;
208
+ /**
209
+ * ⚠️ Una cadena, siempre — es lo que devuelve `getDecrypted` hoy. Quien la
210
+ * necesita como objeto la parsea, que es lo que ya hace: las credenciales
211
+ * de cuenta de servicio de Google llegan como JSON en texto.
212
+ */
213
+ material: string;
214
+ }
215
+ /** Las siete redes que despachan por `common/social-providers`. */
216
+ export type RedSocial = 'bluesky' | 'twitter' | 'mastodon' | 'linkedin' | 'threads' | 'instagram' | 'facebook';
217
+ /** Una página o empresa de LinkedIn en nombre de la que se puede publicar. */
218
+ export interface OrganizacionDeLinkedIn {
219
+ urn: string;
220
+ name: string;
221
+ vanityName?: string;
222
+ }
223
+ /**
224
+ * Lo MÍNIMO con lo que se puede llamar a la API de cada red.
225
+ *
226
+ * ## ⚠️ Esto NO es el blob guardado, y la diferencia es de seguridad
227
+ *
228
+ * En Google el error fue que lo guardado era MÁS DÉBIL que lo utilizable: un
229
+ * refresh token con el que no se llama a nada. Aquí, en Meta, está invertido —
230
+ * **lo guardado es más fuerte**. El blob de `instagram_oauth2` y
231
+ * `facebook_oauth2` lleva dos tokens:
232
+ *
233
+ * `pageAccessToken` el que el proveedor usa, y el único que necesita
234
+ * `userAccessToken` 60 días, y es con el que se piden por `/me/accounts`
235
+ * los tokens de TODAS las páginas del usuario
236
+ *
237
+ * Los dos son `string`. Una firma `Promise<string>`, o un `{ token: string }`,
238
+ * **compila con el que no toca**, y el resultado no es una caída: es hw-nodes
239
+ * sosteniendo por la red, en cada publicación, la credencial que acuña tokens
240
+ * de página. Se comprobó que los proveedores declaran `userAccessToken` y no
241
+ * lo leen NUNCA.
242
+ *
243
+ * El tercer caso, más callado: el blob de `mastodon_oauth2` lleva el
244
+ * `clientId`/`clientSecret` de la app que HostWebhook registró **en esa
245
+ * instancia**. El proveedor tampoco los toca. Sacarlos por `material()` rompe
246
+ * «los client secrets se quedan en el gateway» sin que nada falle.
247
+ *
248
+ * ## Por qué es una unión discriminada y no una bolsa de opcionales
249
+ *
250
+ * Un `{ token: string; instanceUrl?: string; userId?: string }` compila para
251
+ * las siete y falla en ejecución, por red y de una en una: mastodon llamando a
252
+ * `https://undefined/api/v1/statuses`, threads a `/undefined/threads`. El
253
+ * identificador va en la RUTA, no en la cabecera, y mastodon ni siquiera tiene
254
+ * host fijo. Aquí, la red que se olvida de su campo no compila.
255
+ *
256
+ * ⚠️ Cada variante es la interfaz `Session` que el proveedor YA tenía por
257
+ * dentro. El estrechamiento no se inventa: se muda al gateway, que es donde
258
+ * está el blob.
259
+ */
260
+ export type SesionSocial =
261
+ /**
262
+ * ⚠️ Bluesky no manda un token: manda una app password DURADERA y de cuenta
263
+ * completa. Se llama como lo que es. El `createSession` lo hace el nodo
264
+ * porque el canje no necesita nada del gateway —no hay OAuth de bluesky en
265
+ * el repo, es app password— y porque el `did` sólo existe después.
266
+ */
267
+ {
268
+ red: 'bluesky';
269
+ identifier: string;
270
+ appPassword: string;
271
+ pdsUrl: string;
272
+ }
273
+ /** El ÚNICO que llega refrescado: el gateway lo rota y lo persiste. */
274
+ | {
275
+ red: 'twitter';
276
+ accessToken: string;
277
+ userId: string;
278
+ }
279
+ /** ⚠️ `instanceUrl` no es adorno: el fediverso no tiene host fijo. */
280
+ | {
281
+ red: 'mastodon';
282
+ accessToken: string;
283
+ instanceUrl: string;
284
+ userId: string;
285
+ fullHandle: string;
286
+ } | {
287
+ red: 'linkedin';
288
+ accessToken: string;
289
+ /** A quién se publica: persona o página. Sin esto no hay `ugcPosts`. */
290
+ authorUrn: string;
291
+ memberId: string;
292
+ memberName: string;
293
+ /** Derivado de `authorUrn` con `authorKindOf`. */
294
+ authorKind: 'person' | 'organization' | null;
295
+ scopes: readonly string[];
296
+ /** El nombre de la PÁGINA cuando se publica como página; si no, el del
297
+ * miembro. Con el del miembro a secas, los logs de una publicación
298
+ * como página decían el nombre de la persona. */
299
+ authorName: string;
300
+ }
301
+ /** `userId` va en la RUTA (`/{userId}/threads`), no en la cabecera. */
302
+ | {
303
+ red: 'threads';
304
+ accessToken: string;
305
+ userId: string;
306
+ username: string;
307
+ }
308
+ /** ⚠️ El token de PÁGINA, nunca el de usuario. Ver la cabecera. */
309
+ | {
310
+ red: 'instagram';
311
+ pageAccessToken: string;
312
+ igUserId: string;
313
+ igUsername: string;
314
+ }
315
+ /** ⚠️ Igual: el de página. */
316
+ | {
317
+ red: 'facebook';
318
+ pageAccessToken: string;
319
+ pageId: string;
320
+ pageName: string;
321
+ };
322
+ /** La sesión de UNA red, para que el proveedor no tenga que estrechar. */
323
+ export type SesionDe<R extends RedSocial> = Extract<SesionSocial, {
324
+ red: R;
325
+ }>;
326
+ /**
327
+ * Un canal de Discord, tal y como lo enseña el selector del trigger.
328
+ *
329
+ * ⚠️ Vive en el contrato y no en `credentials.service.ts` porque los dos lados
330
+ * lo necesitan: quien lo produce está en `hw-credentials` y quien lo consume en
331
+ * `nodes/`. Tenerlo en el servicio obligaba al consumidor a importar las 4.270
332
+ * líneas del dominio para nombrar diez campos.
333
+ *
334
+ * `type` es el entero de Discord (0=texto, 2=voz, 4=categoría, 5=anuncios,
335
+ * 10/11/12=hilos, 13=stage, 15=foro); el panel ramifica sobre él para filtrar
336
+ * y elegir icono.
337
+ */
338
+ export interface DiscordChannelSummary {
339
+ id: string;
340
+ name: string;
341
+ type: number;
342
+ parentId: string | null;
343
+ position: number;
344
+ topic: string | null;
345
+ guildId: string;
346
+ guildName: string;
347
+ guildIcon: string | null;
348
+ }
349
+ /**
350
+ * Los proveedores cuyo token se refresca con el `client_secret` de nuestra app.
351
+ *
352
+ * ⚠️ Una unión cerrada y no una cadena. Con `string`, un proveedor mal escrito
353
+ * llega hasta el gateway y vuelve como «no existe» en tiempo de ejecución; así
354
+ * no compila. Crece cuando se cierre slack y atlassian, que tienen más
355
+ * superficie que un refresco.
356
+ */
357
+ /**
358
+ * Los proveedores cuyo access token vive detrás del `client_secret` de nuestra
359
+ * app y hay que pedirlo, no descifrarlo.
360
+ *
361
+ * ⚠️ Distinto de `ProveedorConRefresco`: allí el llamante YA tiene un token y
362
+ * pide su sustituto; aquí no tiene ninguno y pide uno vivo. Son dos verbos
363
+ * distintos y por eso son dos uniones y dos métodos.
364
+ */
365
+ export type ProveedorConToken = 'slack' | 'atlassian';
366
+ export type ProveedorConRefresco = 'notion' | 'shopify';
367
+ export interface CredencialesDelGateway {
368
+ /**
369
+ * Los metadatos de una credencial de la organización.
370
+ *
371
+ * ⚠️ **Lanza `NotFoundException` si no existe**, igual que hoy. No devuelve
372
+ * `null`: hay un llamante —`common/discord-operations.service.ts`— que
373
+ * envuelve esta llamada en un `.catch(onFailureReturn(...))` a propósito.
374
+ * Convertir el fallo en `null` dejaría ese `catch` sin nada que atrapar y
375
+ * el resto de llamantes sin error donde hoy lo hay.
376
+ */
377
+ describir(id: string, orgId: string): Promise<DescripcionDeCredencial>;
378
+ /**
379
+ * El secreto, y el tipo con el que interpretarlo.
380
+ *
381
+ * ⚠️ Devuelve los DOS juntos a propósito. El patrón real, idéntico en 28
382
+ * ficheros, es `findOne` para el tipo → ramificar → descifrar. Devolverlos
383
+ * juntos quita un viaje de red por ejecución cuando esta costura sea HTTP.
384
+ *
385
+ * ⚠️ `tipoEsperado` NO es una comodidad: es dónde se comprueba.
386
+ *
387
+ * Muchos llamantes hacen hoy `findOne` → «¿es del tipo que espero?» → lanzar
388
+ * → y sólo entonces descifrar. Ese orden importa y se pierde si el contrato
389
+ * trae las dos cosas de un tirón: una credencial del tipo equivocado
390
+ * acabaría descifrada igual, con su fila en el registro de accesos, para
391
+ * luego tirarla. Pasándole el tipo, la comprobación se hace **en el
392
+ * gateway y antes de descifrar**, así que la que no toca no se descifra
393
+ * nunca. Es mejor que hoy, no igual.
394
+ */
395
+ material(id: string, orgId: string, quien: QuienPide, purpose: string, tipoEsperado?: CredentialType | readonly CredentialType[]): Promise<MaterialDeCredencial>;
396
+ /**
397
+ * Apunta que la credencial se usó. Para la columna «último uso» del panel.
398
+ *
399
+ * ⚠️ Devuelve una promesa que los llamantes NO esperan: todos hacen
400
+ * `.catch(…)` y siguen. Que fallar en apuntar el uso tumbe una publicación
401
+ * en Mastodon sería absurdo, y así está hoy.
402
+ */
403
+ marcarUsada(id: string): Promise<void>;
404
+ /**
405
+ * Los canales que el bot ve, para el selector del trigger de Discord.
406
+ *
407
+ * ⚠️ Esto es un LISTADO, no un secreto, y por eso no lleva `purpose`: no
408
+ * devuelve material que nadie pueda usar para autenticarse. Sí lleva `quien`,
409
+ * porque el token del bot con el que se pregunta SÍ es de alguien y la
410
+ * pregunta se hace en su nombre.
411
+ *
412
+ * 🔥 Y por eso no vale «que el nodo pida el token y liste él». Ese token abre
413
+ * el servidor entero; lo que el nodo necesita es la lista de canales. Sacar
414
+ * el secreto para derivar una lista es justo lo que este contrato existe para
415
+ * no hacer.
416
+ */
417
+ canalesDeDiscord(id: string, orgId: string, quien: QuienPide): Promise<DiscordChannelSummary[]>;
418
+ /**
419
+ * Un access token fresco de un proveedor cuyo `client_secret` vive en el
420
+ * gateway.
421
+ *
422
+ * 🔥 Por esto los cinco servicios de OAuth NO se mudan. Refrescar necesita el
423
+ * secreto de NUESTRA app, y ése no puede salir a `hw-nodes`: si saliera, cada
424
+ * nodo tendría con qué hacerse pasar por la aplicación entera. Lo que cruza
425
+ * es el token ya refrescado, que sirve para una cuenta y caduca.
426
+ *
427
+ * ⚠️ `staleToken` no es un detalle: es el token que ACABA de recibir un 401.
428
+ * Distinguirlo del guardado es lo que evita gastar una rotación cuando otro
429
+ * proceso ya refrescó — y en Shopify las rotaciones son finitas.
430
+ *
431
+ * Devuelve `null` cuando no hay nada que refrescar (una app custom, un token
432
+ * que no caduca, una credencial que ya no existe). `null` es una respuesta,
433
+ * no un fallo: el llamante sigue con el token que tenía.
434
+ */
435
+ refrescarTokenDeProveedor(proveedor: ProveedorConRefresco, params: {
436
+ credentialId: string;
437
+ orgId: string;
438
+ staleToken: string;
439
+ }): Promise<string | null>;
440
+ /**
441
+ * Apaga las credenciales de una tienda que desinstaló la app.
442
+ *
443
+ * ⚠️ La tienda sale de la CREDENCIAL, nunca de la cabecera del webhook: si
444
+ * no, la cabecera decidiría a quién se le apaga la conexión. Devuelve cuántas
445
+ * se marcaron, que es lo que el llamante apunta en telemetría.
446
+ */
447
+ marcarTiendaDesinstalada(shop: string, orgId: string): Promise<number>;
448
+ /**
449
+ * Un access token VIVO de Slack o Atlassian, refrescado si hacía falta.
450
+ *
451
+ * ⚠️ Lleva `quien` porque esto SÍ es una lectura: entrega al llamante una
452
+ * credencial usable, y esa entrega tiene que quedar en el registro de
453
+ * accesos.
454
+ *
455
+ * 🔥 Y hasta ahora no quedaba. Estos dos servicios se saltan `getDecrypted`
456
+ * a propósito —necesitan el documento crudo para guardar el token
457
+ * refrescado— y sólo sellaban `lastUsedAt`. O sea que cada resolución de
458
+ * token de Slack o Jira era INVISIBLE para el registro, con `quien` o sin él.
459
+ * El adaptador lo apunta ahora explícitamente.
460
+ */
461
+ tokenDeProveedor(proveedor: ProveedorConToken, id: string, orgId: string, quien: QuienPide): Promise<string>;
462
+ /**
463
+ * Los datos del espacio de Slack conectado.
464
+ *
465
+ * ⚠️ SIN `quien`, y no por descuido: no devuelve nada con lo que
466
+ * autenticarse. El id del equipo y el nombre del espacio son los mismos que
467
+ * el panel ya enseña en la tarjeta de la credencial.
468
+ */
469
+ espacioDeSlack(id: string, orgId: string): Promise<{
470
+ teamId: string;
471
+ teamName: string;
472
+ botUserId: string;
473
+ scopes: string[];
474
+ }>;
475
+ /**
476
+ * Un installation token de la GitHub App, ya acuñado.
477
+ *
478
+ * 🔥 La llave privada de una GitHub App es el secreto más gordo que tenemos:
479
+ * con ella se firma un JWT como la aplicación y se acuñan tokens para
480
+ * CUALQUIER instalación — todas las de todos los clientes. Lo que sale de
481
+ * aquí es un token de UNA instalación que caduca en una hora.
482
+ *
483
+ * ⚠️ Esto no existía y el api tenía la llave. Era una incoherencia con Slack
484
+ * y Atlassian, que sí pasaron por el contrato con este mismo razonamiento,
485
+ * y encima con el secreto más peligroso de los tres. Ver el PR que lo trajo.
486
+ *
487
+ * Y de paso el api deja de ver el blob descifrado: antes pedía `material` y
488
+ * se lo pasaba a `tokenFor`. Ahora no pide ni el secreto ni la llave.
489
+ */
490
+ tokenDeInstalacionDeGithub(id: string, orgId: string, quien: QuienPide): Promise<string>;
491
+ /**
492
+ * Las credenciales de una organización, para que el agente pueda nombrarlas.
493
+ *
494
+ * ⚠️ `id`, `name` y `type`, y NADA más. El llamante hacía
495
+ * `.select('name type')` contra el modelo — la barrera estaba en la consulta
496
+ * y no en el serializador, a propósito, para que no dependiera de que nadie
497
+ * rompiera un `toJSON`. Aquí la barrera es el tipo: `encryptedData` y
498
+ * `metadata` (host, correo, URL del servidor…) no existen en la respuesta.
499
+ */
500
+ credencialesDeLaOrganizacion(orgId: string): Promise<Array<{
501
+ id: string;
502
+ name: string;
503
+ type: string;
504
+ }>>;
505
+ /**
506
+ * Qué carpetas de credencial ve esta persona, y cuántas hay en total.
507
+ *
508
+ * ⚠️ Los dos números juntos porque el llamante necesita los dos y por una
509
+ * razón concreta: si ve TODAS, no hay que filtrar nada y se ahorra el filtro
510
+ * entero. Pedirlos por separado eran dos viajes y una carrera entre ellos.
511
+ *
512
+ * 🔥 Y quita un acceso CRUDO a la colección: `canvas` contaba con
513
+ * `connection.collection('credentialfolders')`. Eso es el gateway leyendo
514
+ * una tabla que ya no es suya.
515
+ */
516
+ carpetasDeCredencialAccesibles(userId: string, orgId: string): Promise<{
517
+ total: number;
518
+ accesibles: string[];
519
+ }>;
520
+ /**
521
+ * Las credenciales que el lienzo enseña, ya recortadas.
522
+ *
523
+ * 🔥 Esto NO entraba por un import, y por eso se me escapó tres veces: el
524
+ * lienzo barre un mapa de nombres de colección y `credentials` era una
525
+ * entrada más. Medir por imports no lo veía.
526
+ *
527
+ * ⚠️ Y lo importante no es de dónde salen los datos, es **quién decide qué se
528
+ * esconde**. Hoy lo decidía el gateway: aplicaba una proyección y luego
529
+ * recortaba `metadata` de las credenciales sensibles. Esa regla es del dueño
530
+ * del dato, y el dueño es este contrato. Dos sitios decidiendo qué es
531
+ * sensible es uno que se queda atrás.
532
+ *
533
+ * `carpetas` es la lista de carpetas que la persona puede abrir, o `null` si
534
+ * las ve todas. `folderId: null` —«Sin carpeta»— es visible para cualquiera.
535
+ */
536
+ credencialesParaElLienzo(orgId: string, carpetas: string[] | null): Promise<Array<Record<string, unknown>>>;
537
+ /**
538
+ * Refresca el token de LA APP de Atlassian, la del Marketplace.
539
+ *
540
+ * ⚠️ No es la credencial de nadie: `atlassian-privacy` tiene su propia
541
+ * colección, con el token que HostWebhook usa para las llamadas de app que
542
+ * Atlassian exige (borrado de datos personales). Sólo reusaba el intercambio
543
+ * OAuth, que necesita el `client_secret` de nuestra app.
544
+ *
545
+ * 🔥 Y por eso pasa por aquí, como Slack, Shopify y la GitHub App: lo que
546
+ * cruza es el token refrescado, que vale para una cuenta y caduca. El
547
+ * `client_secret` —que sirve para cualquiera— no sale de este servicio.
548
+ *
549
+ * Devuelve `null` si no hay nada que refrescar; el llamante sigue con lo que
550
+ * tenía en vez de quedarse sin token.
551
+ */
552
+ refrescarTokenDeAppDeAtlassian(oauth: Record<string, unknown>): Promise<Record<string, unknown> | null>;
553
+ /** El sitio de Atlassian conectado. Mismo criterio: metadatos, no secreto. */
554
+ sitioDeAtlassian(id: string, orgId: string): Promise<{
555
+ cloudId: string;
556
+ siteUrl: string;
557
+ siteName: string;
558
+ }>;
559
+ /** La URL de webhook entrante que Slack dio al conectar, si la hay. */
560
+ urlDeWebhookDeSlack(id: string, orgId: string): Promise<string | null>;
561
+ /**
562
+ * Los ids de las credenciales de un espacio de Slack.
563
+ *
564
+ * 🔥 Ids y organización, NO documentos. El método del gateway devuelve
565
+ * `CredentialDocument[]` —con el blob cifrado dentro— y los llamantes usan el
566
+ * `_id` y, en un sitio, el `organizationId`. Mandar el documento por la red
567
+ * habría sacado el secreto de todo un espacio para no usarlo.
568
+ *
569
+ * ⚠️ La primera versión devolvía sólo `string[]` y `tsc` la tumbó: un tercer
570
+ * llamante emite telemetría POR ORGANIZACIÓN. Medir dos usos y no el tercero
571
+ * es cómo un contrato nace ya corto.
572
+ *
573
+ * ⚠️ Y sin `orgId`, que es lo raro y es correcto: el evento entrante de Slack
574
+ * sólo trae `team_id`, y de ESO se deduce la organización. Es la única
575
+ * búsqueda del contrato que va al revés.
576
+ */
577
+ credencialesDeEquipoDeSlack(teamId: string): Promise<Array<{
578
+ id: string;
579
+ organizationId: string;
580
+ }>>;
581
+ /**
582
+ * Las credenciales de un tipo, para elegir cuenta.
583
+ *
584
+ * 🔥 Sólo `id` y `name`, que es lo único que el llamante usa. El método del
585
+ * gateway devuelve la página entera de documentos —con `encryptedData`
586
+ * dentro— y quien la pedía hacía `data.map((c) => ({ id, name }))` y tiraba
587
+ * el resto. Por la red eso habría sacado hasta 50 secretos para enseñar una
588
+ * lista de nombres.
589
+ *
590
+ * ⚠️ Sin `quien` y sin `purpose`: no descifra nada. Es la lista que el panel
591
+ * ya enseña.
592
+ */
593
+ credencialesDeTipo(orgId: string, tipo: CredentialType): Promise<Array<{
594
+ id: string;
595
+ name: string;
596
+ }>>;
597
+ /**
598
+ * La configuración con la que el AI Node habla con un servidor MCP.
599
+ *
600
+ * ⚠️ Éste SÍ es método del contrato y `configDeOllama` no, aunque se
601
+ * parezcan. Ollama es derivación pura del material —parsear y validar— y se
602
+ * hace del lado del nodo. Éste, cuando el blob trae `oauth`, delega en
603
+ * `McpOAuthService` para REFRESCAR el token, y eso necesita el secreto de la
604
+ * app, que no sale del gateway. Derivar no es refrescar: es la misma
605
+ * distinción que costó cara en Google.
606
+ */
607
+ configDeMcp(id: string, orgId: string, quien: QuienPide, purpose: string): Promise<{
608
+ url: string;
609
+ authHeader?: string;
610
+ }>;
611
+ /**
612
+ * La sesión mínima con la que un nodo social puede llamar a su API.
613
+ *
614
+ * ⚠️ `red` NO es una comodidad: es el `tipoEsperado` de `material()`. El
615
+ * gateway lo traduce al `CredentialType` que toca y comprueba ANTES de
616
+ * descifrar, así que la credencial equivocada no se descifra ni deja fila en
617
+ * el registro. Y es lo que estrecha el tipo de vuelta.
618
+ *
619
+ * ⚠️ El nombre dice «sesión» y no «token» a propósito. Sólo en X hay trabajo
620
+ * de refresco aquí dentro —2 h de vida, rotación con el `client_secret` de
621
+ * HostWebhook, y la rotación se PERSISTE—. En las otras seis esto devuelve
622
+ * lo guardado, estrechado: si el token está muerto, la primera señal es el
623
+ * 401 de la API. Threads y Meta los renuevan crones del gateway, fuera de
624
+ * aquí.
625
+ *
626
+ * ⚠️ NO cachear el resultado del lado del nodo. El cron de Meta reescribe el
627
+ * blob por debajo y `setAuthor`/`refreshOrganizations` de LinkedIn cambian
628
+ * el `authorUrn` que el despacho lee. Este contrato no tiene invalidación.
629
+ */
630
+ sesionSocial<R extends RedSocial>(red: R, id: string, orgId: string, quien: QuienPide, purpose: string): Promise<SesionDe<R>>;
631
+ /**
632
+ * Un access token de Google, ya refrescado.
633
+ *
634
+ * ⚠️ Esto reemplaza a `getGoogleAuthClient`, que devolvía un `OAuth2Client`
635
+ * VIVO. Ese objeto no cruza una red — pero tampoco hace falta que cruce: el
636
+ * método original son doce líneas que piden un token y hacen
637
+ * `new google.auth.OAuth2()`. La fábrica se queda del lado del nodo
638
+ * (`clienteDeGoogle`, aquí al lado) y lo que viaja es la cadena.
639
+ */
640
+ tokenDeGoogle(id: string, orgId: string, quien: QuienPide, purpose: string): Promise<string>;
641
+ }
642
+ /**
643
+ * Traduce `QuienPide` a lo que el registro de accesos entiende hoy.
644
+ *
645
+ * ⚠️ Y comprueba el id vacío, que es el agujero que el tipo no puede cerrar.
646
+ * Devuelve también si la atribución es utilizable, para que el adaptador lo
647
+ * apunte en vez de dejarlo pasar callando.
648
+ */
649
+ export declare function comoAtribuir(quien: QuienPide): {
650
+ relatedEntity?: {
651
+ type: string;
652
+ id: string;
653
+ };
654
+ userId?: string;
655
+ sourceIp?: string;
656
+ motivo?: MotivoSinComprobar;
657
+ sirve: boolean;
658
+ };
659
+ /** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
660
+ /**
661
+ * La credencial existe, pero no es del tipo que el llamante pide.
662
+ *
663
+ * ⚠️ Extiende `BadRequestException` A PROPÓSITO: mismo estado y mismo texto que
664
+ * lanzaba el adaptador, así que las familias que YA pasan `tipoEsperado` —mongo,
665
+ * postgres, las memorias del AI Node— no cambian ni una letra. Lo que añade es
666
+ * un `instanceof` con el que un llamante puede volver a poner SU mensaje.
667
+ *
668
+ * ## Por qué hacía falta
669
+ *
670
+ * `tipoEsperado` no es una comodidad: es dónde se comprueba, y comprobar en el
671
+ * gateway es lo que evita descifrar una credencial que se va a rechazar. Pero
672
+ * los helpers de Mailchimp y Shopify lanzaban un `ForbiddenException` con un
673
+ * mensaje accionable en inglés —«Connect a Shopify store from Settings →
674
+ * Credentials»— que SALE POR LA API al usuario. Pasar `tipoEsperado` cambiaba
675
+ * el mensaje y el código HTTP; no pasarlo obligaba a descifrar primero, que es
676
+ * peor que hoy. Con el `instanceof`, el llamante recupera su mensaje sin
677
+ * renunciar a la comprobación temprana.
678
+ *
679
+ * ⚠️ Lo que NUNCA hay que hacer es olfatear la prosa del mensaje con un regex:
680
+ * el texto del adaptador no es contrato, y el día que se reescriba, el `catch`
681
+ * deja de reconocerlo EN SILENCIO.
682
+ *
683
+ * ⚠️ Y `instanceof` no cruza HTTP. El día que esta costura sea red, la clase
684
+ * hay que rehidratarla desde el código de estado en el cliente del transporte.
685
+ * Es UN sitio y está a la vista; olfatear la prosa serían N escondidos.
686
+ */
687
+ /**
688
+ * 🔥 El discriminador que hace que la clase sobreviva al cable.
689
+ *
690
+ * `instanceof` no cruza HTTP, y el código de estado tampoco basta: esta
691
+ * excepción es un **400**, igual que la validación de entrada y media docena
692
+ * de errores más de hw-credentials. Rehidratar «por el 400» convertiría
693
+ * cualquier petición mal formada en «la credencial es de otro tipo».
694
+ *
695
+ * ⚠️ Y lo que NUNCA hay que hacer es olfatear la prosa del mensaje: el texto
696
+ * no es contrato, y el día que se reescriba el `catch` deja de reconocerlo EN
697
+ * SILENCIO. Por eso va un campo propio en el CUERPO.
698
+ */
699
+ export declare const MOTIVO_TIPO_EQUIVOCADO = "credencial-de-tipo-equivocado";
700
+ export declare class CredencialDeTipoEquivocado extends BadRequestException {
701
+ readonly credentialId: string;
702
+ readonly tipoReal: string;
703
+ readonly tiposEsperados: readonly string[];
704
+ constructor(credentialId: string, tipoReal: string, tiposEsperados: readonly string[]);
705
+ }
706
+ export declare const CREDENCIALES_DEL_GATEWAY = "CREDENCIALES_DEL_GATEWAY";
707
+ /**
708
+ * El túnel por el que hay que salir para esta credencial, si lo hay.
709
+ *
710
+ * ⚠️ Deliberadamente NO es un método del contrato. `getTunnelIdForCredential`
711
+ * lo era en el gateway, y al medirlo resultó ser `metadata.tunnelId` y nada
712
+ * más — y `describir()` ya trae `metadata`. Añadir un método habría sido una
713
+ * llamada de red por cada consulta para leer un campo que ya venía en la
714
+ * anterior.
715
+ *
716
+ * Vive aquí y no repetido en los cinco sitios que lo derivan porque «leer un
717
+ * campo de un mapa» es exactamente donde una copia se queda con el nombre
718
+ * viejo el día que el campo cambie.
719
+ */
720
+ export declare function tunelDe(cred: {
721
+ metadata?: Record<string, unknown>;
722
+ }): string | null;