@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,106 @@
1
+ /**
2
+ * Lo que un nodo le manda decir al panel. Nada más.
3
+ *
4
+ * ## Por qué
5
+ *
6
+ * Costura de la Fase 2. `nodes/` y `common/` importaban `ingress/` 18 veces, y
7
+ * las 18 apuntaban a la MISMA clase:
8
+ *
9
+ * 10 `WebhookGateway` el gateway de Socket.IO
10
+ * 8 `WebhookGatewayModule` cableado, desaparece con la mudanza
11
+ *
12
+ * `WebhookGateway` es un `@WebSocketGateway` de 2000 líneas: mantiene el
13
+ * servidor de sockets, las salas por organización, la autenticación de cada
14
+ * conexión —con sus modelos de `Token`, `User` y `ApiKey`—, el túnel de
15
+ * desarrollo y las peticiones HTTP en espera. Nada de eso puede vivir en
16
+ * `hw-nodes`: quien tiene abiertos los sockets del panel es el gateway.
17
+ *
18
+ * Lo que el nodo necesita es mucho menos: **decir que algo ha pasado**.
19
+ *
20
+ * ## Lo que ya estaba bien
21
+ *
22
+ * Los diez métodos reciben datos planos, y no por casualidad: los llamantes
23
+ * ya hacen `.toJSON()` o pasan por `comoObjetoPlano(...)` antes de emitir.
24
+ * Aquí no hay ningún documento de Mongoose que aplanar — algo que en la
25
+ * costura de ficheros costó un adaptador entero.
26
+ *
27
+ * ⚠️ Por eso las cargas se declaran `object` y NO `Record<string, unknown>`,
28
+ * que fue lo primero que escribí. Una firma de índice hace el contrato MÁS
29
+ * ESTRICTO que la realidad: rechaza lo que devuelve `.toJSON()` de Mongoose
30
+ * —que no la tiene— y también una interfaz declarada como `PipelineStep`.
31
+ * Trece errores de compilación que no señalaban ningún dato mal formado,
32
+ * sólo un contrato mal escrito. Es la segunda vez en esta fase.
33
+ *
34
+ * ⚠️ Y una que el inventario evitó: un `grep` de `gateway.<algo>` sacaba
35
+ * `subscribe` y `unsubscribe` de más. Son de `DiscordGatewayService`, otra
36
+ * clase que en `discord.provider` también se llama `gateway`. Medir por el
37
+ * TIPO inyectado y no por el nombre de la propiedad es lo que los descarta.
38
+ *
39
+ * ## ⚠️ Nueve avisan, uno CONTESTA
40
+ *
41
+ * Nueve de los diez son difusión a la sala `org:<id>`: se mandan y no se
42
+ * espera nada. Si se pierde uno, el panel enseña un dato viejo hasta que
43
+ * alguien recargue — molesto y nada más.
44
+ *
45
+ * `resolveSyncWaiter` no. Ése completa una **petición HTTP que el gateway
46
+ * tiene retenida** (el modo síncrono de un webhook): al otro lado hay alguien
47
+ * esperando una respuesta. Si ese mensaje se pierde, el que llamó se queda
48
+ * colgado hasta que salte el 408 de `waitForSync`.
49
+ *
50
+ * Va en el mismo contrato porque hoy es la misma inyección y su único
51
+ * llamante es el motor, pero quien implemente esto por red tiene que tratarlo
52
+ * distinto: los nueve pueden ser «dispara y olvida», éste no.
53
+ */
54
+ /** Un aviso en vivo al panel de la organización. */
55
+ export interface AvisosEnVivo {
56
+ emitNewEvent(webhookId: string, orgId: string, evento: object): void;
57
+ emitEventUpdate(webhookId: string, orgId: string, evento: object): void;
58
+ emitWebhookUpdate(webhookId: string, orgId: string, webhook: object): void;
59
+ /** Un paso de pipeline terminó. */
60
+ emitPipelineStep(orgId: string, paso: object): void;
61
+ /**
62
+ * Un nodo FUENTE recibió algo.
63
+ *
64
+ * Evento propio y no `pipeline:step` porque un webhook recibiendo tráfico
65
+ * no ejecuta un paso, y fabricarle uno falso ensucia a quien escucha pasos
66
+ * de verdad.
67
+ */
68
+ emitNodePayload(orgId: string, data: {
69
+ nodeType: string;
70
+ nodeId: string;
71
+ lastPayload: unknown;
72
+ /** Datos de ejemplo adoptados a mano; viaja aparte del payload. */
73
+ example?: boolean;
74
+ }): void;
75
+ /** Un trigger acaba de recibir algo de su proveedor. */
76
+ emitTriggerActivity(orgId: string, data: {
77
+ nodeId: string;
78
+ lastPushAt: string;
79
+ }): void;
80
+ emitApprovalUpdate(orgId: string, data: {
81
+ type: 'created' | 'resolved';
82
+ pendingApproval: object;
83
+ }): void;
84
+ emitSwRunComplete(orgId: string, swId: string, run: object): void;
85
+ /**
86
+ * Un contador de uso cambió. Sin payload: quien escucha vuelve a pedir
87
+ * `GET /auth/me/usage`.
88
+ */
89
+ emitUsageInvalidate(orgId: string): void;
90
+ /**
91
+ * ⚠️ Contesta una petición HTTP que el gateway tiene RETENIDA.
92
+ *
93
+ * No es difusión. Al otro lado hay un cliente esperando la respuesta de un
94
+ * webhook en modo síncrono; perder este mensaje lo deja colgado hasta el
95
+ * 408 de `waitForSync`. Los otros nueve pueden ser «dispara y olvida»; éste
96
+ * necesita entrega, y por eso está escrito aparte y no mezclado arriba.
97
+ */
98
+ resolveSyncWaiter(eventId: string, resultado: {
99
+ status: number;
100
+ body: Record<string, unknown>;
101
+ }): void;
102
+ }
103
+ /**
104
+ * ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2.
105
+ */
106
+ export declare const AVISOS_EN_VIVO = "AVISOS_EN_VIVO";
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * Lo que un nodo le manda decir al panel. Nada más.
4
+ *
5
+ * ## Por qué
6
+ *
7
+ * Costura de la Fase 2. `nodes/` y `common/` importaban `ingress/` 18 veces, y
8
+ * las 18 apuntaban a la MISMA clase:
9
+ *
10
+ * 10 `WebhookGateway` el gateway de Socket.IO
11
+ * 8 `WebhookGatewayModule` cableado, desaparece con la mudanza
12
+ *
13
+ * `WebhookGateway` es un `@WebSocketGateway` de 2000 líneas: mantiene el
14
+ * servidor de sockets, las salas por organización, la autenticación de cada
15
+ * conexión —con sus modelos de `Token`, `User` y `ApiKey`—, el túnel de
16
+ * desarrollo y las peticiones HTTP en espera. Nada de eso puede vivir en
17
+ * `hw-nodes`: quien tiene abiertos los sockets del panel es el gateway.
18
+ *
19
+ * Lo que el nodo necesita es mucho menos: **decir que algo ha pasado**.
20
+ *
21
+ * ## Lo que ya estaba bien
22
+ *
23
+ * Los diez métodos reciben datos planos, y no por casualidad: los llamantes
24
+ * ya hacen `.toJSON()` o pasan por `comoObjetoPlano(...)` antes de emitir.
25
+ * Aquí no hay ningún documento de Mongoose que aplanar — algo que en la
26
+ * costura de ficheros costó un adaptador entero.
27
+ *
28
+ * ⚠️ Por eso las cargas se declaran `object` y NO `Record<string, unknown>`,
29
+ * que fue lo primero que escribí. Una firma de índice hace el contrato MÁS
30
+ * ESTRICTO que la realidad: rechaza lo que devuelve `.toJSON()` de Mongoose
31
+ * —que no la tiene— y también una interfaz declarada como `PipelineStep`.
32
+ * Trece errores de compilación que no señalaban ningún dato mal formado,
33
+ * sólo un contrato mal escrito. Es la segunda vez en esta fase.
34
+ *
35
+ * ⚠️ Y una que el inventario evitó: un `grep` de `gateway.<algo>` sacaba
36
+ * `subscribe` y `unsubscribe` de más. Son de `DiscordGatewayService`, otra
37
+ * clase que en `discord.provider` también se llama `gateway`. Medir por el
38
+ * TIPO inyectado y no por el nombre de la propiedad es lo que los descarta.
39
+ *
40
+ * ## ⚠️ Nueve avisan, uno CONTESTA
41
+ *
42
+ * Nueve de los diez son difusión a la sala `org:<id>`: se mandan y no se
43
+ * espera nada. Si se pierde uno, el panel enseña un dato viejo hasta que
44
+ * alguien recargue — molesto y nada más.
45
+ *
46
+ * `resolveSyncWaiter` no. Ése completa una **petición HTTP que el gateway
47
+ * tiene retenida** (el modo síncrono de un webhook): al otro lado hay alguien
48
+ * esperando una respuesta. Si ese mensaje se pierde, el que llamó se queda
49
+ * colgado hasta que salte el 408 de `waitForSync`.
50
+ *
51
+ * Va en el mismo contrato porque hoy es la misma inyección y su único
52
+ * llamante es el motor, pero quien implemente esto por red tiene que tratarlo
53
+ * distinto: los nueve pueden ser «dispara y olvida», éste no.
54
+ */
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.AVISOS_EN_VIVO = void 0;
57
+ /**
58
+ * ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2.
59
+ */
60
+ exports.AVISOS_EN_VIVO = 'AVISOS_EN_VIVO';
@@ -0,0 +1,78 @@
1
+ /**
2
+ * El canal por el que el ejecutor le va dando trozos a una respuesta que el
3
+ * gateway tiene RETENIDA.
4
+ *
5
+ * ## De dónde sale la forma
6
+ *
7
+ * No es difusión. Es el mismo gesto que `resolveSyncWaiter` en
8
+ * `avisos-en-vivo.ts`, y su comentario lo dice mejor que yo:
9
+ *
10
+ * «Contesta una petición HTTP que el gateway tiene RETENIDA. No es difusión.
11
+ * Al otro lado hay un cliente esperando la respuesta.»
12
+ *
13
+ * Aquí es un SSE en vez de una respuesta única: el widget de chat mantiene
14
+ * abierta una conexión y el nodo de IA le manda tokens según los produce.
15
+ *
16
+ * ## ⚠️ Por qué NO se reusa `AVISOS_EN_VIVO`
17
+ *
18
+ * Porque ese contrato es «un aviso en vivo **al panel de la organización**» y
19
+ * todo lo suyo va con `orgId`. El visitante de un chat NO es el panel: es
20
+ * alguien anónimo en la web de un cliente. Mandar sus tokens por una sala de
21
+ * organización sería una fuga.
22
+ *
23
+ * La clave aquí es el STREAM, no la organización.
24
+ *
25
+ * ## ⚠️ Y por qué el gateway se queda el SSE
26
+ *
27
+ * Se valoró que el widget conectara directo al ejecutor. Se descartó: la puerta
28
+ * del chat es `@Public()` —sin sesión, porque es un widget en la web de un
29
+ * cliente—, así que eso le pondría una puerta pública y sin autenticar al
30
+ * servicio que corre el Code Node con código del usuario. Y habría que
31
+ * duplicar el control de orígenes, que ya avisa de que «un cliente que no sea
32
+ * un navegador ignora las cabeceras».
33
+ *
34
+ * Una sola puerta pública. El coste es un salto por trozo sobre conexión
35
+ * persistente, y el ritmo lo pone el modelo, no la red.
36
+ */
37
+ /**
38
+ * Lo que viaja por el stream.
39
+ *
40
+ * ⚠️ Se declara AQUÍ y no se importa de `ai-nodes`: es lo que cruza la
41
+ * frontera, así que su forma es parte del contrato y no del ejecutor. Y son
42
+ * datos planos —cadenas, números, un booleano—, que es lo que hace que pueda
43
+ * cruzar.
44
+ */
45
+ export type EventoDeStream = {
46
+ event: 'token';
47
+ data: {
48
+ text: string;
49
+ };
50
+ } | {
51
+ event: 'tool_start';
52
+ data: {
53
+ name: string;
54
+ input: Record<string, unknown>;
55
+ };
56
+ } | {
57
+ event: 'tool_end';
58
+ data: {
59
+ name: string;
60
+ output: string;
61
+ success: boolean;
62
+ durationMs: number;
63
+ };
64
+ };
65
+ /**
66
+ * Lo que escribe un trozo en el SSE. Es un callback y por eso NO viaja: quien
67
+ * lo tiene es el gateway, que sostiene la respuesta.
68
+ */
69
+ export type EmisorDeStream = (evento: EventoDeStream) => void | Promise<void>;
70
+ export interface CanalDeStream {
71
+ /**
72
+ * Un trozo para ese stream. Si nadie lo escucha, se tira sin ruido: quien
73
+ * cerró la pestaña no es un error.
74
+ */
75
+ emitir(streamId: string, evento: EventoDeStream): void;
76
+ }
77
+ /** ⚠️ Cadena y no `Symbol`. */
78
+ export declare const CANAL_DE_STREAM = "CANAL_DE_STREAM";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CANAL_DE_STREAM = void 0;
4
+ /** ⚠️ Cadena y no `Symbol`. */
5
+ exports.CANAL_DE_STREAM = 'CANAL_DE_STREAM';
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Las conversaciones de un chat trigger, por repositorio.
3
+ *
4
+ * ## Por qué costó más que las otras trece
5
+ *
6
+ * Las demás costuras invertían un servicio. Ésta y la de entregas invierten el
7
+ * acceso directo a una COLECCIÓN, y ésta además **mutaba el documento vivo**:
8
+ *
9
+ * const existing = await this.conversationModel.findOne({...});
10
+ * existing.messages.push(userMsg);
11
+ * existing.expiresAt = expiresAt;
12
+ * await existing.save();
13
+ *
14
+ * Eso no es una consulta que se pueda mover: es un documento de Mongoose
15
+ * cambiando en memoria y guardándose. Por eso quedó fuera del lote del #468 —
16
+ * cerrarla obliga a decidir de quién es la colección.
17
+ *
18
+ * La respuesta, mirando lo que se hace con ella: es del gateway. El panel
19
+ * lista conversaciones, la retención se administra desde `plans/` con
20
+ * `chatConversationRetentionDays`, y el índice TTL vive en su entidad. El nodo
21
+ * escribe turnos y los vuelve a leer.
22
+ *
23
+ * ## Y de paso arregla algo que ya estaba anotado
24
+ *
25
+ * El llamante de `upsert` tenía este comentario:
26
+ *
27
+ * *«Plainify each entry so we don't leak Mongoose subdocument refs (parent,
28
+ * `$__`, `_doc`, …) into the payload — those circular refs blow up recursive
29
+ * walkers like extractAttachments / template renderers with "Maximum call
30
+ * stack size exceeded"».*
31
+ *
32
+ * O sea que ya se estaba aplanando a mano, después de recibir el documento,
33
+ * porque no hacerlo reventaba. El repositorio devuelve datos planos y ese
34
+ * peligro desaparece en origen en vez de esquivarse en cada llamante.
35
+ *
36
+ * ## ⚠️ Lo que no se puede perder
37
+ *
38
+ * - **`expiresAt` se refresca en CADA turno de usuario.** La ventana de
39
+ * retención rueda con la actividad; sin eso, una sesión larga caduca a mitad
40
+ * de conversación. `null` significa «no caduca» (plan enterprise).
41
+ * - **El dedupe de voz.** Los SDK de voz repiten la transcripción final, así
42
+ * que un turno idéntico al último —mismo rol y mismo contenido recortado— no
43
+ * se añade. Sin eso el transcript sale duplicado.
44
+ * - **Los borradores pendientes son la memoria entre turnos.** Es lo que deja
45
+ * al modelo entender un «confirma» que llega en el turno siguiente.
46
+ */
47
+ /** Un turno del transcript. `mode` ausente significa texto. */
48
+ export interface MensajeDeConversacion {
49
+ role: string;
50
+ content: string;
51
+ mode?: 'voice';
52
+ }
53
+ /** Un borrador que quedó a medias y espera confirmación. */
54
+ export interface BorradorPendiente {
55
+ draftId: string;
56
+ operation: string;
57
+ subject?: string;
58
+ createdAt: Date;
59
+ }
60
+ /** Una conversación, plana. */
61
+ export interface Conversacion {
62
+ id: string;
63
+ sessionId: string;
64
+ title?: string;
65
+ messages: MensajeDeConversacion[];
66
+ pendingDrafts: BorradorPendiente[];
67
+ createdAt?: Date;
68
+ updatedAt?: Date;
69
+ }
70
+ export interface RepositorioDeConversaciones {
71
+ /** La lista paginada del panel, con su total. */
72
+ listarPorTrigger(triggerId: string, opts: {
73
+ offset: number;
74
+ limit: number;
75
+ }): Promise<{
76
+ datos: Conversacion[];
77
+ total: number;
78
+ }>;
79
+ /** ⚠️ `null` si no existe o no es de ese trigger; el llamante decide el 404. */
80
+ buscarPorId(conversationId: string, triggerId: string): Promise<Conversacion | null>;
81
+ /**
82
+ * Un turno de usuario: lo añade a la sesión, o la crea.
83
+ *
84
+ * ⚠️ `expiresAt` se escribe SIEMPRE, exista ya la conversación o no: es lo
85
+ * que hace rodar la ventana de retención con la actividad.
86
+ */
87
+ guardarTurnoDeUsuario(params: {
88
+ orgId: string;
89
+ chatTriggerId: string;
90
+ sessionId: string;
91
+ title?: string;
92
+ mensaje: MensajeDeConversacion;
93
+ /** `null` = no caduca. */
94
+ expiresAt: Date | null;
95
+ }): Promise<Conversacion>;
96
+ /**
97
+ * Un turno de voz.
98
+ *
99
+ * ⚠️ Devuelve `anadido: false` cuando el turno es idéntico al último —mismo
100
+ * rol y mismo contenido recortado—. Los SDK de voz repiten la transcripción
101
+ * final y sin eso el transcript sale duplicado.
102
+ */
103
+ guardarTurnoDeVoz(params: {
104
+ orgId: string;
105
+ chatTriggerId: string;
106
+ sessionId: string;
107
+ title: string;
108
+ mensaje: MensajeDeConversacion;
109
+ expiresAt: Date | null;
110
+ }): Promise<{
111
+ conversationId: string;
112
+ anadido: boolean;
113
+ }>;
114
+ /** La respuesta del asistente, al final del transcript. */
115
+ anadirRespuesta(conversationId: string, content: string): Promise<void>;
116
+ /** Lo que el widget relee al recargar la página a mitad de sesión. */
117
+ historialDeSesion(chatTriggerId: string, sessionId: string): Promise<{
118
+ conversationId: string | null;
119
+ messages: MensajeDeConversacion[];
120
+ }>;
121
+ /**
122
+ * Aplica lo que pasó con los borradores en un turno y devuelve cómo quedó.
123
+ *
124
+ * ⚠️ Se quitan los enviados y se añaden los nuevos EN ESE ORDEN, y devuelve
125
+ * los ids resultantes porque el llamante los registra: sin ese log no había
126
+ * forma de averiguar por qué el modelo vio el borrador equivocado en el
127
+ * turno siguiente.
128
+ */
129
+ aplicarEventosDeBorrador(conversationId: string, cambios: {
130
+ quitar: string[];
131
+ anadir: Array<{
132
+ draftId: string;
133
+ operation: string;
134
+ subject?: string;
135
+ }>;
136
+ }): Promise<string[]>;
137
+ }
138
+ /** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
139
+ export declare const CONVERSACIONES_DEL_CHAT = "CONVERSACIONES_DEL_CHAT";
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * Las conversaciones de un chat trigger, por repositorio.
4
+ *
5
+ * ## Por qué costó más que las otras trece
6
+ *
7
+ * Las demás costuras invertían un servicio. Ésta y la de entregas invierten el
8
+ * acceso directo a una COLECCIÓN, y ésta además **mutaba el documento vivo**:
9
+ *
10
+ * const existing = await this.conversationModel.findOne({...});
11
+ * existing.messages.push(userMsg);
12
+ * existing.expiresAt = expiresAt;
13
+ * await existing.save();
14
+ *
15
+ * Eso no es una consulta que se pueda mover: es un documento de Mongoose
16
+ * cambiando en memoria y guardándose. Por eso quedó fuera del lote del #468 —
17
+ * cerrarla obliga a decidir de quién es la colección.
18
+ *
19
+ * La respuesta, mirando lo que se hace con ella: es del gateway. El panel
20
+ * lista conversaciones, la retención se administra desde `plans/` con
21
+ * `chatConversationRetentionDays`, y el índice TTL vive en su entidad. El nodo
22
+ * escribe turnos y los vuelve a leer.
23
+ *
24
+ * ## Y de paso arregla algo que ya estaba anotado
25
+ *
26
+ * El llamante de `upsert` tenía este comentario:
27
+ *
28
+ * *«Plainify each entry so we don't leak Mongoose subdocument refs (parent,
29
+ * `$__`, `_doc`, …) into the payload — those circular refs blow up recursive
30
+ * walkers like extractAttachments / template renderers with "Maximum call
31
+ * stack size exceeded"».*
32
+ *
33
+ * O sea que ya se estaba aplanando a mano, después de recibir el documento,
34
+ * porque no hacerlo reventaba. El repositorio devuelve datos planos y ese
35
+ * peligro desaparece en origen en vez de esquivarse en cada llamante.
36
+ *
37
+ * ## ⚠️ Lo que no se puede perder
38
+ *
39
+ * - **`expiresAt` se refresca en CADA turno de usuario.** La ventana de
40
+ * retención rueda con la actividad; sin eso, una sesión larga caduca a mitad
41
+ * de conversación. `null` significa «no caduca» (plan enterprise).
42
+ * - **El dedupe de voz.** Los SDK de voz repiten la transcripción final, así
43
+ * que un turno idéntico al último —mismo rol y mismo contenido recortado— no
44
+ * se añade. Sin eso el transcript sale duplicado.
45
+ * - **Los borradores pendientes son la memoria entre turnos.** Es lo que deja
46
+ * al modelo entender un «confirma» que llega en el turno siguiente.
47
+ */
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.CONVERSACIONES_DEL_CHAT = void 0;
50
+ /** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
51
+ exports.CONVERSACIONES_DEL_CHAT = 'CONVERSACIONES_DEL_CHAT';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Lo que el motor necesita del flujo programado. UN método.
3
+ *
4
+ * ## Por qué
5
+ *
6
+ * `pipeline-run` llama a `ScheduledWorkflowsService.recordRun(swId, run)` al
7
+ * terminar una corrida que arrancó en un flujo programado. Es la única
8
+ * llamada: medido.
9
+ *
10
+ * ⚠️ Y NO es un `lastRunAt` que se pueda escribir a mano desde aquí. `recordRun`
11
+ * además archiva la fila en `scheduledworkflowruns` y poda a las últimas cien.
12
+ * Reimplementarlo del lado del motor sería una segunda copia de esa regla.
13
+ *
14
+ * ⚠️ Tiene que TERMINAR antes de que salga el marco `complete`: el panel
15
+ * relee el flujo cuando ese marco llega, así que apuntar después es una carrera
16
+ * que se pierde a veces. Por eso se espera, a diferencia de la salud del
17
+ * webhook.
18
+ */
19
+ export interface CorridasProgramadas {
20
+ apuntarCorrida(swId: string, corrida: {
21
+ startedAt: Date;
22
+ completedAt: Date;
23
+ durationMs: number;
24
+ status: 'success' | 'partial' | 'failure';
25
+ errorMessage?: string | null;
26
+ }): Promise<void>;
27
+ }
28
+ /** ⚠️ Cadena y no `Symbol`. */
29
+ export declare const CORRIDAS_PROGRAMADAS = "CORRIDAS_PROGRAMADAS";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CORRIDAS_PROGRAMADAS = void 0;
4
+ /** ⚠️ Cadena y no `Symbol`. */
5
+ exports.CORRIDAS_PROGRAMADAS = 'CORRIDAS_PROGRAMADAS';
@@ -0,0 +1,61 @@
1
+ /**
2
+ * ¿Puede este usuario poner esta credencial? — la pregunta, sin la respuesta.
3
+ *
4
+ * ## Por qué existe
5
+ *
6
+ * Costura de la Fase 2. `node-controller.factory` aplicaba
7
+ * `@UseGuards(CredentialFolderGuard)` a los ~30 tipos de nodo, y ese guardia
8
+ * vive en `credentials/`: consulta la colección de credenciales y resuelve
9
+ * permisos de carpeta. Con eso, el factory —y con él todos los controladores
10
+ * de nodo— no se puede mudar a `hw-nodes` sin llevarse el dominio entero de
11
+ * credenciales, que es la Fase 4.
12
+ *
13
+ * ## Lo que NO se hace, y por qué
14
+ *
15
+ * Lo fácil era quitar el guardia y decir «ya autoriza el gateway al proxear».
16
+ * Y es defendible: la autenticación va EN el gateway por diseño.
17
+ *
18
+ * ⚠️ Pero este guardia existe **porque la comprobación se olvidó trece
19
+ * veces**. Su propio docblock cuenta el agujero: bastaba con leerle el id de
20
+ * credencial a un nodo que sí se pudiera ver, ponérselo a un nodo propio y
21
+ * ejecutarlo, y la credencial de la carpeta negada se descifraba igual. Y un
22
+ * id de Mongo no es un secreto: sale en el lienzo y en la barra del navegador.
23
+ *
24
+ * Dejar esa garantía viviendo SÓLO en el proxy es apostar a que nada alcance
25
+ * nunca `hw-nodes` por otro camino — una mala configuración, un servicio
26
+ * nuevo, una prueba. Cuando pase, el agujero vuelve, y vuelve callado.
27
+ *
28
+ * Así que la pregunta se queda y lo que se va es la RESPUESTA: `hw-nodes`
29
+ * preguntará por la red privada a quien tenga el dato. Una llamada por
30
+ * escritura de nodo que lleve credencial —crear o editar—, no por ejecución.
31
+ * Un salto interno es ~1 ms contra los 100-500 ms que el nodo gasta llamando
32
+ * a Slack o a Gmail.
33
+ */
34
+ /** Los tres papeles sobre una carpeta, de menos a más. */
35
+ export type RolDeCarpeta = 'viewer' | 'editor' | 'owner';
36
+ export interface PreguntaDePermiso {
37
+ credentialId: string;
38
+ userId: string;
39
+ orgId: string;
40
+ rolMinimo: RolDeCarpeta;
41
+ }
42
+ export interface PermisoSobreCredencial {
43
+ /**
44
+ * ⚠️ Devuelve `true` cuando la credencial NO EXISTE, y eso no es un
45
+ * descuido: es la semántica que hay que conservar.
46
+ *
47
+ * Negar aquí daría un 403 sobre algo inexistente, y un 403 dice «existe
48
+ * pero no es tuya». Dejando pasar, el manejador contesta 404 como siempre y
49
+ * no se filtra qué ids existen — que es justo lo que este guardia protege,
50
+ * porque los ids circulan.
51
+ *
52
+ * O sea: `false` significa «existe y NO puedes», nunca «no la encuentro».
53
+ */
54
+ puedeUsar(pregunta: PreguntaDePermiso): Promise<boolean>;
55
+ }
56
+ /**
57
+ * ⚠️ Cadena y no `Symbol`. Un `Symbol` no sobrevive a una frontera de módulo
58
+ * duplicada ni a una serialización, y esto está pensado para acabar cruzando
59
+ * una red.
60
+ */
61
+ export declare const PERMISO_SOBRE_CREDENCIAL = "PERMISO_SOBRE_CREDENCIAL";
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ /**
3
+ * ¿Puede este usuario poner esta credencial? — la pregunta, sin la respuesta.
4
+ *
5
+ * ## Por qué existe
6
+ *
7
+ * Costura de la Fase 2. `node-controller.factory` aplicaba
8
+ * `@UseGuards(CredentialFolderGuard)` a los ~30 tipos de nodo, y ese guardia
9
+ * vive en `credentials/`: consulta la colección de credenciales y resuelve
10
+ * permisos de carpeta. Con eso, el factory —y con él todos los controladores
11
+ * de nodo— no se puede mudar a `hw-nodes` sin llevarse el dominio entero de
12
+ * credenciales, que es la Fase 4.
13
+ *
14
+ * ## Lo que NO se hace, y por qué
15
+ *
16
+ * Lo fácil era quitar el guardia y decir «ya autoriza el gateway al proxear».
17
+ * Y es defendible: la autenticación va EN el gateway por diseño.
18
+ *
19
+ * ⚠️ Pero este guardia existe **porque la comprobación se olvidó trece
20
+ * veces**. Su propio docblock cuenta el agujero: bastaba con leerle el id de
21
+ * credencial a un nodo que sí se pudiera ver, ponérselo a un nodo propio y
22
+ * ejecutarlo, y la credencial de la carpeta negada se descifraba igual. Y un
23
+ * id de Mongo no es un secreto: sale en el lienzo y en la barra del navegador.
24
+ *
25
+ * Dejar esa garantía viviendo SÓLO en el proxy es apostar a que nada alcance
26
+ * nunca `hw-nodes` por otro camino — una mala configuración, un servicio
27
+ * nuevo, una prueba. Cuando pase, el agujero vuelve, y vuelve callado.
28
+ *
29
+ * Así que la pregunta se queda y lo que se va es la RESPUESTA: `hw-nodes`
30
+ * preguntará por la red privada a quien tenga el dato. Una llamada por
31
+ * escritura de nodo que lleve credencial —crear o editar—, no por ejecución.
32
+ * Un salto interno es ~1 ms contra los 100-500 ms que el nodo gasta llamando
33
+ * a Slack o a Gmail.
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.PERMISO_SOBRE_CREDENCIAL = void 0;
37
+ /**
38
+ * ⚠️ Cadena y no `Symbol`. Un `Symbol` no sobrevive a una frontera de módulo
39
+ * duplicada ni a una serialización, y esto está pensado para acabar cruzando
40
+ * una red.
41
+ */
42
+ exports.PERMISO_SOBRE_CREDENCIAL = 'PERMISO_SOBRE_CREDENCIAL';