Un modelo de video toma más que una instrucción. Algunos toman un primer cuadro y un último cuadro. Algunos toman un puñado de imágenes de referencia. Algunos toman un clip de referencia, y luego se comportan distinto por eso — silenciando su propia banda sonora, o tomando la duración del clip como la duración del resultado. Algunos toman un clip de audio. Cada modelo dibuja ese sobre de forma distinta, y la mayoría de los nuestros solo enviaban una parte de él.
Ahora hay un solo lugar que describe el sobre de cada modelo: cuántas imágenes, si se permite un clip y por cuántos segundos, si se acepta audio, si el audio necesita una imagen al lado, si un clip silencia el propio sonido del modelo, si un clip fija la duración. Se leyó de la documentación propia de los proveedores, y luego se puso a prueba ese mismo día con renderizados reales en claves reales — así se encontraron los dos puntos siguientes.
Un archivo sin extensión
Entregarle un clip a un modelo por enlace es el camino obvio. Falla en varios de ellos, de una forma muy difícil de adivinar: el archivo llega sin extensión, y el modelo valida por extensión, así que rechaza un mp4 perfectamente bueno por no ser un mp4. Los mismos bytes enviados en línea se aceptaban en todos lados. Así que cada clip se recodifica primero a un formato estándar y se envía en línea, con un techo de tamaño por encima del cual se usa igualmente la ruta de enlace, con una advertencia.
Un nivel que nadie estaba obteniendo
El segundo le estaba costando dinero a los clientes. Uno de los modelos de video no tiene ningún campo de resolución — su nivel de calidad se ajusta mediante un campo con otro nombre — y la plataforma frente a él descarta en silencio campos que no reconoce. Así que cada renderizado en ese modelo desde que se agregó volvía en el nivel por defecto mientras el cliente había pagado por el nivel superior. Nada fallaba. El video llegaba. Simplemente no era el video que se facturaba.
El nivel ahora se deriva de la resolución realmente cotizada, y se confirmó mediante sonda.
Seedance pasa a un carril directo
En el mismo tramo, toda la familia Seedance pasó del agregador a la API propia del proveedor, con el agregador como respaldo. Una fila de modelo, un nombre, dos conjuntos de tarifas — y el precio que se te cotiza es el que pagas incluso cuando el carril principal falla y corre el respaldo, con el libro registrando qué carril realmente hizo el trabajo, para que nuestra propia contabilidad de margen lea la verdad en vez de la cotización.
Una cosa que debes saber como usuario: cuando adjuntas un clip de referencia en ese carril, el proveedor factura los segundos del clip encima de los segundos del resultado. La estimación en el formulario lo dice, y el número cotizado antes de ejecutar es el número que se cobra.
Encontrado en el camino
Tres clientes de API carecían del único atributo que hace visible en nuestro contenedor a una clase con nombre de proveedor. Dos de ellos eran clientes de texto a voz, invisibles desde el día en que se agregaron — por eso dos voces sembradas en el catálogo nunca pudieron servirse de verdad. Una prueba de reflexión ahora hace fallar la compilación en la próxima omisión de este tipo, en vez de esperar a que alguien note una voz que no hace nada.
Y la página de búsqueda web que habíamos sacado el día anterior respondía 403 en todos los niveles, en web y móvil. Está bloqueada por un permiso padre; ambas semillas de roles solo otorgaban sus tres permisos hijos, y en ningún lado se infiere un padre de sus hijos. Cuatro otras funciones habían parchado esto a mano en algún momento. Ahora hay una verificación que recorre el árbol de permisos y falla donde un servicio está bloqueado por un permiso que ningún rol tiene.