Este documento convierte las reglas actuales de AGENTS.md y las skills locales
en una propuesta de refactor incremental para Sermo. La prioridad es reducir
literales conceptuales, duplicacion y deriva entre superficies sin cambiar el
comportamiento publico ni relajar las garantias de seguridad.
AGENTS.md define las reglas que deben guiar cualquier refactor:
- Trabajar en el checkout actual, preservar cambios ajenos y no commitear salvo peticion explicita.
- Reutilizar el owner existente antes de crear helpers o paquetes nuevos.
- Evitar magic literals en produccion; crear o reutilizar constantes para estados, tipos, modos, backends, claves YAML/JSON, unidades, umbrales, defaults y factores de conversion.
- Mantener un vocabulario unico para cada concepto; los nombres publicos de structs, YAML, JSON y APIs mandan.
- No cambiar la estructura publica de configuracion salvo migracion explicita.
- Mantener paths runtime bajo
/runy normalizar/var/runsolo como compatibilidad Linux/init. - Mantener todas las acciones de servicio en
internal/operation;app/ycli/no deben llamar backends ni enviar senales directamente. - Usar APIs nativas o runners inyectables con timeout; no
os/execdisperso. - Mantener probes de
internal/connligados acfg.Interface. - Actualizar docs y ejemplos cuando cambie comportamiento observable o config.
- Construir checks, watches, notifiers y rule actions desde builders centrales.
- Evitar trabajo caro en hot paths de
sermod. - Validar con
make checkpara cambios Go/YAML antes de darlos por cerrados.
Las skills del proyecto viven en .agents/skills/ y cubren el refactor por
dominio:
sermo-go-implementation: cambios Go en CLI, daemon, config, checks, locks, rules y operaciones.sermo-test-engineer: pruebas table-driven, fakes y fixtures.sermo-config-schema: cambios de YAML/config, catalogo, services, watches, checks, guards, locks, rules y stop policies.sermo-safety-review: cualquier cambio que toque start/stop/restart/reload, signals, process discovery y matching, locks, preflight, guards o remediation.sermo-rule-engine: reglas, condition trees, windows, remediation y guards.sermo-profile-authorysermo-remote-testing: usar solo cuando el refactor toque perfiles de servicios o pruebas remotas.
El trabajo aplicado hasta ahora se concentra en constantes y paths derivados:
metrics.PercentScalecentraliza la conversion de ratios a porcentaje.- Paths de configuracion se derivan desde las claves dueñas en
internal/configparaengine.*,paths.*,defaults.*,web.*,notifiers.*,watches.*,then.*,policy.*,stop_policy.*,processes.*,pidfiles.*,mount.*,variables.*,service.*,also_service.*,version.on_change.*yreload_on_change.paths. - Reload y control usan paths/labels derivados para
reload.*ycontrol.*. - Acciones/status compartidos en web/API/eventos se derivan de
rulesyoperationcuando el concepto coincide. config.DaemonPIDFilenameevita duplicarsermod.pidentre daemon y CLI.- El build web tiene constantes locales para sus assets fuente.
Este estado no debe tratarse como una migracion de configuracion: los mensajes y paths publicos siguen usando la misma forma visible.
- Revisar el diff completo por ownership:
internal/config,internal/app,internal/web,internal/operation,internal/metrics,internal/cli. - Confirmar que no hay cambios de comportamiento ni de YAML publico.
- Mantener el commit pendiente hasta que se pida explicitamente.
Validacion:
go test ./internal/config
go test ./internal/app ./internal/checks ./internal/cli ./internal/config ./internal/dockerctl ./internal/metrics ./internal/operation ./internal/virt ./internal/web
make check- Buscar literales restantes por dominio y clasificarlos antes de tocar codigo.
- Extraer solo conceptos con owner claro o repeticion real.
- No convertir fixtures, ejemplos YAML realistas, strings de error locales de un solo uso, comentarios explicativos ni datos de tests.
Comandos utiles:
rg -n '"[a-z_]+(\.[a-z_]+)+"' internal --glob '!**/*_test.go'
rg -n '[^A-Za-z0-9_]100(\.0)?|\*\s*100|/\s*100' internalEstado ejecutado:
- Literales con punto restantes en Go productivo: mayoritariamente constantes de
protocolo o formato externo (
connextra keys, NUT variable names, RPC aliases, build metadatavcs.*, filenames de assets,sermod.pid,sermo.db). No se deben mover salvo que un owner los comparta realmente. - Numeros
100restantes en Go productivo: clasificados como divisores/clases HTTP, IDs de protocolo RPC/NFS/NSM/portmap, escalas de tiempo/procfs/SNMP/NTP, limites de validacion porcentual y defaults de UI/API. Los factores de porcentaje Go ya estan centralizados enmetrics.PercentScale. - Web frontend:
internal/web/src/app.jstodavia contiene conversiones porcentuales y segundos/milisegundos locales. Son candidatos de UI para un refactor posterior, pero cualquier cambio exigemake weby revisar el HTML generado. - Decision de alcance: fase 1 queda como inventario sin cambios de runtime. Las extracciones que merezcan codigo pasan a fases 2 y 3, donde se revisan helpers de paths y acciones/estados por owner.
- Mantener helpers junto al owner cuando el path es local a un validador.
- Promover helpers a package-level solo cuando ya se usan en varios archivos del mismo paquete.
- Evitar un paquete generico de "paths" si solo une strings: añadiria coupling y no resolveria un problema real.
Candidatos actuales:
- Revaluar si los helpers de
internal/configdeben vivir en un archivo dedicado comopaths.gosolo cuando crezcan mas usos cruzados. - Mantener
control.*,reload.*,watches.*yvariables.*cerca de sus validadores mientras no haya una API externa que los necesite.
Estado ejecutado:
- Los helpers de paths de validacion/configuracion cruzados se movieron a
internal/config/field_paths.go. - No se creo un paquete nuevo ni una abstraccion global. El owner sigue siendo
internal/config. control.*,reload.*,watches.*,variables.*,policy.*,web.*,notifiers.*,mount.*,pidfiles.*y helpers relacionados conservan los mismos strings resultantes; el cambio es de ubicacion y ownership.
- Usar constantes tipadas de
rules,operation,servicemgr,checks,processoconfigcuando el concepto sea exactamente el mismo. - No derivar conceptos solo por coincidencia textual. Por ejemplo, un event kind y un YAML field solo deben compartir constante si representan el mismo contrato.
- Documentar excepciones cuando un string visible debe permanecer local.
Estado ejecutado:
internal/app/event.go,internal/web/server.go,internal/operation,internal/rulesyinternal/servicemgrya derivan las acciones/status que comparten contrato exacto.- Se dejaron locales los textos que coinciden pero no son el mismo contrato: estados externos de Docker/libvirt/systemd, estados visuales del frontend, nombres JSON/DOM, estados de locks/mounts y mensajes/event kinds propios de cada superficie.
- Safety review: bajo riesgo. No se modifico ningun camino de operacion, guard/preflight/lock, kill/signal ni remediation; solo se documento la decision de no crear coupling por coincidencia textual.
- Para cambios de config:
go test ./internal/config. - Para web backend/API:
go test ./internal/app ./internal/web. - Para service operations, reload, locks o process discovery: activar
sermo-safety-reviewy correr paquetes afectados junto conmake check. - Añadir tests solo si el refactor descubre comportamiento ambiguo o un bug; no añadir fixtures con vocabulario retirado.
Estado ejecutado:
go test ./internal/config: pasa.go test ./internal/app ./internal/web: pasa.make check: pasa completo (go vet,staticcheck,revive,golangci-lint,govulncheck,go test ./...).- No se añadieron tests nuevos porque las fases 1-3 no introdujeron comportamiento nuevo; fase 2 fue movimiento de helpers con cobertura existente.
- No actualizar docs de usuario cuando el refactor no cambia comportamiento.
- Si se cambia una forma publica de config, actualizar docs, ejemplos y tests en el mismo parche.
- Si se introduce una excepcion o una razon de seguridad, documentarla en el
owner y, si es usuario-facing, en
docs/.
Estado ejecutado:
- No hubo cambios de estructura publica YAML/JSON, CLI, Web API ni comportamiento observable de operaciones.
- No se actualizaron README,
docs/ni ejemplos porque el refactor solo movio literales/helpers internos y documento el plan. - La excepcion relevante queda documentada aqui: los strings que coinciden entre superficies no se comparten si no representan el mismo contrato.
- Extraer literales conceptuales del frontend sin cambiar el contrato del Web API ni la estructura DOM.
- Mantener las constantes dentro de
internal/web/src/app.js, que es el owner de formato, tiempos, umbrales visuales y geometria de graficas del dashboard. - Regenerar
internal/web/index.htmlconmake webdespues de editarinternal/web/src/.
Estado ejecutado:
- Se centralizaron factores de porcentaje, conversiones de segundos y milisegundos, ventanas moviles de 1h/24h/7d/30d/1y, umbrales visuales de uso, umbrales SLA, longitud de preview de eventos, ticks de refresco y geometria de graficas.
slaColor,slaWindowSpanMs,pctClamp, formatters de duracion/edad, barras de uso, metric charts, SLA charts y previews de eventos consumen esos nombres en vez de numeros dispersos.- No se cambio comportamiento publico; el HTML generado se actualizo solo como artefacto derivado del build web.
- Validacion ejecutada:
make web,go test ./internal/webymake checkpasan.
- Usar
state.DefaultHistoryRetentioncomo owner unico del horizonte historico retenido para SLA, metricas y eventos. - Derivar desde ese owner el maximo de ventana que el Web API acepta para series
historicas, evitando repetir
366 * 24heninternal/web.
Estado ejecutado:
internal/web/server.goimportainternal/statey definemaxSeriesWindow = state.DefaultHistoryRetention.- La prueba existente
TestSeriesSinceParsingsigue cubriendo que una ventana excesiva se capea al valor maximo. - Validacion ejecutada:
go test ./internal/webymake checkpasan.
- Nombrar los limites numericos de eventos que tienen politicas distintas por superficie, sin compartir constantes cuando el contrato no es el mismo.
- Mantener separado el limite del listado CLI, el limite/cap del Web API y el numero de eventos que el resumen de actividad escanea internamente.
Estado ejecutado:
internal/cli/cli.gousadefaultEventsListLimitpara el listadosermoctl events.internal/app/webbackend.gousaactivitySummaryEventScanLimitpara el rollup del dashboard.- No se cambiaron defaults publicos ni limites de API.
- Validacion ejecutada:
go test ./internal/app ./internal/cliymake checkpasan.
- Nombrar el divisor de clase HTTP (
status / 100) donde se usa en runtime. - Mantener constantes locales porque el webhook solo clasifica exito de
transporte y
checkscompara codigos configurados; no son el mismo contrato publico aunque usen la misma matematica.
Estado ejecutado:
internal/notify/webhook.gousa constantes locales para divisor de clase HTTP y clase de exito.internal/checks/httpcheck.gousa la constante localhttpStatusClassDivisorpara calcular la clase de estado enstatusMatcher.- Validacion ejecutada:
go test ./internal/notify ./internal/checksymake checkpasan.
- Mantener
internal/statecomo owner de la retencion historica y de las ventanas SLA persistidas. - Nombrar los multiplicadores de dias fijos para que quede explicito que el ano/mes de SLA son ventanas rolling, no limites de calendario.
Estado ejecutado:
DefaultHistoryRetentionse deriva dehistoryRetentionDaysyhoursPerDay.- Los spans SLA de semana/mes/ano se derivan de
slaRollingWeekDays,slaRollingMonthDaysyslaRollingYearDays. internal/websigue heredando el horizonte maximo a traves destate.DefaultHistoryRetention.- Validacion ejecutada:
go test ./internal/state ./internal/webymake checkpasan.
- Mantener
internal/statecomo owner del lookback por defecto usado cuando una consulta de series historicas omitesince. - Reutilizar el mismo valor desde CLI y Web sin cambiar el default publico de 24h.
Estado ejecutado:
state.DefaultSeriesWindowdefine el lookback normal de series.sermoctl sla --seriesy el Web API derivan sus defaults desdestate.DefaultSeriesWindow.- Validacion ejecutada:
go test ./internal/state ./internal/cli ./internal/webymake checkpasan.
- Mantener el HTML del informe de
sermoctl services --notifyen su owner actual,internal/cli/services_report.go. - Nombrar colores, fuentes y formato de fecha usados repetidamente en el email HTML sin cambiar la salida ni introducir un sistema de plantillas nuevo.
Estado ejecutado:
- Se centralizaron colores, fuentes y layout de fecha del informe.
- Las cabeceras de tabla repetidas usan
writeReportHeaderCell. - El informe conserva el mismo contenido y estilos inline compatibles con email.
- Validacion ejecutada:
go test ./internal/cliymake checkpasan.
- Mantener
daemonMetricCheckcomo nombre canonico del check logico que agrupa las metricas desermod. - No reutilizarlo para nombres de logger ni otros conceptos que solo comparten
el texto
sermod.
Estado ejecutado:
internal/app/daemonmetrics.gousadaemonMetricCheckal devolverweb.MetricSeries.- Se dejaron locales los logger names de
internal/app/event.go. - Validacion ejecutada:
go test ./internal/appymake checkpasan.
- Mantener la validacion de
portseninternal/config/validate_checks.go. - Nombrar el mensaje reutilizado para specs de puertos vacias, conservando el mismo ejemplo visible al operador.
Estado ejecutado:
validatePortSpecusaportSpecRequiredMessagepara los dos caminos que reportan una spec vacia.- Validacion ejecutada:
go test ./internal/configymake checkpasan.
- Mantener el loader de plantillas en
internal/notify/template.go. - Nombrar el sufijo de archivo, los nombres internos de subtemplate y la opcion
de
text/templatepara claves ausentes.
Estado ejecutado:
LoadTemplateusatemplateFileSuffix.parseTemplateusa constantes para:subject,:bodyymissingkey=zero.- Validacion ejecutada:
go test ./internal/notifyymake checkpasan.
- Mantener los paths canonicos/fallback de utmp en
internal/utmp, que es el owner del parser de sesiones. - Reutilizarlos desde el notifier TTY/Wall sin exponer una slice mutable.
Estado ejecutado:
utmp.DefaultPathsdevuelve una copia de los paths por defecto en Linux ynilfuera de Linux.internal/notify/tty_linux.gousautmp.DefaultPaths()en vez de duplicar/run/utmpy/var/run/utmp.- Validacion ejecutada:
go test ./internal/utmp ./internal/notifyymake checkpasan.
- Mantener en
internal/configel orden canonico de lectura deos-release, usado para selectores${os}. - Reutilizar ese orden desde el backend web cuando muestra el nombre amigable del sistema operativo.
Estado ejecutado:
config.OSReleasePaths()devuelve los paths deos-releaseen prioridad.config.osReleaseIDyapp.osPrettyNameconsumen esa funcion en vez de duplicar/etc/os-releasey/usr/lib/os-release.- Validacion ejecutada:
go test ./internal/config ./internal/appymake checkpasan.
- Mantener los directorios runtime de systemd/OpenRC en
internal/servicemgr, que es el owner de deteccion y normalizacion de init backends. - Reutilizarlos desde
internal/configpara detectar el built-in${init}.
Estado ejecutado:
servicemgr.SystemdRuntimeDiryservicemgr.OpenRCRuntimeDirexponen los directorios runtime usados por el detector.config.detectInitusa esas constantes en vez de duplicar/run/systemd/systemy/run/openrc.- Validacion ejecutada:
go test ./internal/config ./internal/servicemgrymake checkpasan.
- Mantener los paths OpenRC relacionados dentro de
internal/servicemgr. - Derivar el directorio de metadata de daemons desde el runtime dir OpenRC para
evitar repetir el prefijo
/run/openrc.
Estado ejecutado:
openRCDaemonsDirse deriva deopenRCRuntimeDir + "/daemons".- Validacion ejecutada:
go test ./internal/servicemgrymake checkpasan.
- Mantener los defaults de puertos de checks dentro de
internal/checks. - Reutilizar un unico puerto TLS por defecto para el check de certificado y el WebSocket seguro.
Estado ejecutado:
defaultTLSPortreemplaza el literal443duplicado.certywebsocketconsumen el mismo default TLS.- Validacion ejecutada:
go test ./internal/checksymake checkpasan.
- Mantener el vocabulario de
/sys/class/neteninternal/checks, owner del muestreo de interfaces. - Reutilizar desde el wizard las constantes de path, archivos y parseo de flags sysfs sin mover logica ni cambiar la salida generada.
Estado ejecutado:
checksexpone las constantes sysfs de interfaces que ya usaba su sampler.sermoctl wizardconsume esas constantes en su fallback de descubrimiento de interfaces.- Validacion ejecutada:
go test ./internal/checks ./internal/cliymake checkpasan.
- Mantener el vocabulario de unidades systemd en
internal/servicemgr. - Reutilizar el sufijo
.servicedesde el wizard en vez de duplicarlo en CLI.
Estado ejecutado:
servicemgr.SystemdServiceSuffixexpone el sufijo ya usado por la normalizacion de unidades systemd.- El wizard de servicios usa ese owner para deduplicar, comparar y derivar nombres desde unidades systemd.
- Validacion ejecutada:
go test ./internal/servicemgr ./internal/cliymake checkpasan.
- Crear un owner pequeno para las constantes de las tablas Linux
/proc/net/*que hoy consumen el wizard de servicios y el probedhclient. - Compartir rutas, indices de campos, estados codificados y constantes de parseo sin mover la logica de parsing ni cambiar mensajes de error.
Estado ejecutado:
internal/procnetcentraliza paths TCP/UDP, indices, estados y bases de parseo de sockets/proc/net.internal/cliyinternal/connconsumen ese vocabulario comun.- Validacion ejecutada:
go test ./internal/procnet ./internal/conn ./internal/cliymake checkpasan.
- Mantener la construccion de paths
/proc/<pid>/...eninternal/process, owner de la lectura de identidades de procesos. - Reutilizar esos helpers desde los scans de blockers de mounts sin cambiar los criterios de matching ni la politica de senales.
Estado ejecutado:
process.PIDPathy las constantesProcFileFD,ProcFileCWDyProcFileRootreemplazan rutas/proc/<pid>/...reconstruidas localmente.process.TrimDeletedSuffixcentraliza el sufijo kernel(deleted)usado al interpretar symlinks procfs.- Validacion ejecutada:
go test ./internal/process ./internal/app ./internal/mountctlymake checkpasan.
- Mantener roots de recursos Linux usados por checks en
internal/checks. - Reutilizarlos desde diagnosticos en vez de repetir
/proc/pressurey/sys/class/block.
Estado ejecutado:
checks.ProcPressureRootPathexpone el root PSI que usa el pressure check.checks.SysBlockPathexpone el root sysfs usado para validar dispositivosdiskioen diagnosticos.- Validacion ejecutada:
go test ./internal/checks ./internal/diagymake checkpasan.
- Mantener la construccion de paths procfs en
internal/process. - Reutilizar
/proc/self/fddesde CLI sin duplicar el path literal.
Estado ejecutado:
process.SelfPathconstruye paths bajo/proc/self.- La deteccion de terminal de
sermoctlusaprocess.SelfPath(process.ProcFileFD). - Validacion ejecutada:
go test ./internal/process ./internal/cliymake checkpasan.
- Mantener los nombres y paths
/proc/<pid>/...eninternal/process. - Reutilizarlos desde
internal/metricspara lecturas por proceso, dejando los ficheros globales de/procen el owner de metricas.
Estado ejecutado:
processexpone nombres procfs por proceso parastat,statm,status,io,fdytask.metrics.OSReaderusaprocess.PIDPathpara CPU, start time, RSS, swap, IO, FD count y thread count por PID.- Validacion ejecutada:
go test ./internal/process ./internal/metricsymake checkpasan.
- Mantener la ruta
/proc/<pid>/stateninternal/process. - Reutilizarla desde locks para leer
owner_start_tickssin cambiar la semantica de deteccion de locks stale.
Estado ejecutado:
locks.OSProcessProber.StartTicksusaprocess.PIDPath(pid, process.ProcFileStat).- Se elimina el formato local
/proc/%d/stat. - Validacion ejecutada:
go test ./internal/locks ./internal/processymake checkpasan.
- Mantener defaults de conexion libvirt en
internal/conn, que ya expone socket y puerto para checks y control de VMs. - Reutilizar el timeout por defecto desde
internal/virt.
Estado ejecutado:
conn.DefaultLibvirtTimeoutexpone el timeout fallback de conexiones libvirt.virt.timeoutFromContextconsume ese default en vez de duplicar10s.- Validacion ejecutada:
go test ./internal/conn ./internal/virtymake checkpasan.
- Crear un owner estrecho para factores de conversion numericos que cruzan paquetes y no pertenecen solo a metricas, checks, estado o libvirt.
- Reutilizarlo en lecturas procfs, swap, cache SQLite y memoria libvirt.
Estado ejecutado:
internal/unitsdefineBytesPerKiByKiBPerMiB.internal/metrics,internal/checks,internal/stateeinternal/connconsumen esos factores en vez de duplicar1024.- Validacion ejecutada:
go test ./internal/units ./internal/metrics ./internal/checks ./internal/state ./internal/connymake checkpasan.
- Mantener
internal/metricscomo owner de unidades canonicas de metricas. - Reutilizar solo unidades con contrato identico desde las lecturas del Web backend, sin cambiar representaciones locales distintas.
Estado ejecutado:
internal/app/checkreadings.gousametrics.MetricUnitMegabytesPerSecond,metrics.MetricUnitCelsiusymetrics.MetricUnitRPM.- Se mantiene local
Csin simbolo porque es una representacion distinta de la lectura actual. - Validacion ejecutada:
go test ./internal/appymake checkpasan.
- Completar las unidades canonicas de
internal/metricsque el backend de lecturas ya mostraba localmente. - Mantener fuera solo representaciones que no son identicas.
Estado ejecutado:
metrics.MetricUnitMegabitsPerSecondyMetricUnitVoltnombran unidades existentes.internal/app/checkreadings.goreutiliza esas constantes para red y voltaje.watchReadingUnitCelsius = "C"sigue local porque no equivale al simbolo canonicometrics.MetricUnitCelsius.- Validacion ejecutada:
go test ./internal/metrics ./internal/appymake checkpasan.
- Mantener mensajes de validacion de
control:eninternal/config. - Nombrar el conflicto comun de
socketyhostpara Docker y libvirt sin acoplarlo al parser runtime dedockerctl.
Estado ejecutado:
validate_service.gousacontrolSocketHostConflictMessagepara ambos backends de control.- No se cambia el texto visible del error ni la validacion de Docker/libvirt.
- Validacion ejecutada:
go test ./internal/configymake checkpasan.
- Mantener en
internal/connlas claves que los probes devuelven enResult.Extra. - Reutilizar la clave de socket desde
internal/checks, que la propaga aResult.Datapara Web/eventos.
Estado ejecutado:
conn.ExtraKeySocketexpone la clavesocketya usada por acpid, fail2ban y lvmpolld.checks.DataKeySocketderiva de esa clave en vez de duplicar el literal via la clave de configuracion.- No cambia el valor visible de datos, JSON ni lecturas.
- Validacion ejecutada:
go test ./internal/conn ./internal/checks ./internal/appymake checkpasan.
- Crear un owner estrecho para constantes de red basicas que cruzan paquetes leaf sin imponer dependencias entre ellos.
- Reutilizar el loopback IPv4 y el nombre de red Unix en probes, Docker control y CLI.
Estado ejecutado:
internal/netutildefineLoopbackIPv4yNetworkUnix.internal/conn.DefaultHost,dockerctl.DefaultHost, elnetworkUnixde Docker y la direccion local por defecto desermoctl daemonderivan de ese owner.- No cambia ningun default visible: siguen siendo
127.0.0.1yunix. - Validacion ejecutada:
go test ./internal/netutil ./internal/conn ./internal/dockerctl ./internal/cliymake checkpasan.
- Extender
internal/unitspara factores binarios de bytes que cruzan parser, probes, checks, Docker control y estado. - Reutilizar los factores en limites defensivos de lectura y cache, sin tocar numeros que son IDs o campos de protocolo.
Estado ejecutado:
units.BytesPerMiB,BytesPerGiByBytesPerTiBse derivan del mismo owner queBytesPerKiB.cfgval.ByteSize, limites HTTP/checks, payloads AMQP/Kafka/NFS/SSH, limites de cuerpo Docker y cache SQLite usan esos factores compartidos.- No cambia ningun limite visible ni semantica de parseo; solo se reemplazan shifts/literales por constantes de unidad.
- Validacion ejecutada:
go test ./internal/units ./internal/cfgval ./internal/checks ./internal/conn ./internal/dockerctl ./internal/stateymake checkpasan.
- Mantener la construccion host:port repetida dentro de
internal/conn, donde viven los probes que usanhostyportenteros. - Reducir la repeticion de
net.JoinHostPort(host, strconv.Itoa(port))sin cambiar transportes, timeouts ni binding de interfaz.
Estado ejecutado:
conn.hostPort(host, port)centraliza el formateohost:portcon puerto entero.- Los probes de
internal/connconsumen el helper y se eliminan imports locales denet/strconvdonde ya no eran necesarios. - No se cambia ningun address resultante ni el uso de
BindDialer. - Validacion ejecutada:
go test ./internal/connymake checkpasan.
- Subir el formateo
host+ puerto entero ainternal/netutilpara los usos fuera deinternal/conn. - Mantener
conn.hostPortcomo fachada local para no dispersar imports en cada probe.
Estado ejecutado:
netutil.JoinHostPort(host, port)encapsulanet.JoinHostPortconstrconv.Itoa.- Docker control, checks HTTP/ports/conn y validacion libvirt reutilizan el helper.
- El helper privado de
conndelega ennetutil, conservando el refactor de la fase anterior. - Validacion ejecutada:
go test ./internal/netutil ./internal/conn ./internal/dockerctl ./internal/checks ./internal/virtymake checkpasan.
- Subir los esquemas
http/httpsy el separador://ainternal/netutilpara paquetes que no deben depender entre si. - Mantener alias locales o publicos donde son parte del vocabulario del owner, evitando cambios de API.
Estado ejecutado:
netutil.URLSchemeHTTP,URLSchemeHTTPSyURLSchemeSeparatorcentralizan el vocabulario generico de URL.checks.URLSchemeHTTP/HTTPSquedan como alias publicos; Docker, notify y los probes HTTP-like deconnreutilizan el owner generico.- No cambia ninguna URL construida ni la validacion de esquemas aceptados.
- Validacion ejecutada:
go test ./internal/netutil ./internal/checks ./internal/conn ./internal/dockerctl ./internal/notifyymake checkpasan.
- Crear un owner sin dependencias para headers HTTP estandar y media types usados por checks, probes, notifiers, CLI y Web API.
- Mantener aliases locales donde el nombre describe el contexto del owner y no tocar headers propios como CSRF o WebSocket.
Estado ejecutado:
internal/httpxcentralizaAccept,Authorization,Content-Type,Serveryapplication/json.- Checks HTTP/InfluxDB, probes HTTP-like de
conn, webhook notify, cliente de API desermoctly servidor web reutilizan esas constantes. - No cambia ningun header emitido, leido ni validado.
- Validacion ejecutada:
go test ./internal/httpx ./internal/checks ./internal/conn ./internal/notify ./internal/cli ./internal/webymake checkpasan.
- Mover los labels de estado de contenedor Docker al paquete que modela el API Docker.
- Conservar el alias publico de
connusado por los asistentes y expectativas de checks.
Estado ejecutado:
dockerctl.ContainerStatus*centralizacreated,dead,exited,paused,removing,restartingyrunning.dockerctl.Managercompara contra esas constantes en lugar de un bloque privado paralelo.conn.DockerContainerStatusRunningpasa a ser alias dedockerctl.ContainerStatusRunning, sin cambiar el valor emitido.- Validacion ejecutada:
go test ./internal/dockerctl ./internal/conn ./internal/assistymake checkpasan.
- Alinear los tokens
typeque comparten checks de conexion y controles de servicio para evitar literales paralelos. - Usar los owners existentes y no crear un paquete global solo para dos tokens.
Estado ejecutado:
conn.ProtocolNameDockerdelega endockerctl.ControlType.virt.ControlTypedelega enconn.ProtocolNameLibvirt.- No cambia ningun valor YAML ni ningun nombre de protocolo/control.
- Validacion ejecutada:
go test ./internal/conn ./internal/dockerctl ./internal/virt ./internal/assist ./internal/configymake checkpasan.
- Extender
internal/unitscon factores de conversion temporales simples, manteniendo ahi solo constantes matematicas, no timeouts configurables. - Reutilizar esos factores en ventanas de estado y formateo compacto de intervalos.
Estado ejecutado:
units.SecondsPerMinute,MinutesPerHour,HoursPerDay,DaysPerWeekyDaysPerMonthApproxnombran conversiones usadas por estado y WebBackend.statereutiliza los factores para buckets por minuto y ventanas por dia.app.formatIntervalreutiliza los mismos factores para dias, semanas y meses aproximados sin cambiar la salida.- Validacion ejecutada:
go test ./internal/units ./internal/state ./internal/appymake checkpasan.
- Extraer a constantes los formatos de mensaje de validacion repetidos en
internal/configproductivo, reutilizando el owner existente (el bloque const devalidate.godonde vivevalidationTCPPortRangeFormat). - Los textos visibles quedan byte-identicos (sustitucion textual exacta); no se
tocan variantes de un solo uso ni el owner de
internal/rules.
Estado ejecutado:
- Nuevas constantes en el owner:
validationPositiveDurationFormat,validationRequiredFormat,validationNotOneOfFormat,validationPathAbsoluteFormat,validationNotSupportedFormat,validationValueNotSupportedFormatyvalidationListIndexFormat; los 13 sitios de"%s must be a mapping"pasan alvalidationMappingFormatya existente. - 63 sitios migrados en total (15 duracion positiva, 13 mapping, 8 required, 7 not-one-of, 5 path-absolute, 5 not-supported, 4 value-not-supported, 6 indice de lista) sin cambiar ningun texto emitido.
- Los 4 field paths
+".delta"derivan ahora dechecks.CheckKeyDelta, como el resto defield_paths.go. - No se fusionan
"%s path %q must be absolute"y"%s %q must be an absolute path"(textos visibles distintos). - Validacion ejecutada:
go test ./internal/config -count=1,gofmt -l internal/configlimpio ygo vet ./internal/configsin hallazgos.
- Nombrar en
internal/applos prefijos de sujeto humano repetidos en warnings, eventos y labels, manteniendo separadas las claves persistidas. - Nombrar el separador de resumenes de lecturas del dashboard y el mensaje de watch desconocido del backend web.
Estado ejecutado:
event.go(owner del vocabulario de eventos) defineserviceSubjectPrefix(17 sitios),watchSubjectPrefix(18) ywatchUnderServiceSubject(4).- Contrato verificado:
"watch "con espacio es siempre texto humano; la clave persistida"watch:"(WatchMonitorKey) no comparte constante. watch.godefineraidSubjectPrefix(5 sitios);webbackend_watch_host.godefinereadingSummarySeparator(" · ", 4 sitios standalone; el·embebido en un format template queda local);webbackend.godefineunknownWatchMessageFmt(4 sitios), espejo delunknownServiceMessageFmtexistente.- El
"unknown watch %q"deinternal/cliqueda local: superficie distinta y uso unico; compartirlo acoplaria cli→app por un solo string. - Validacion ejecutada:
go test ./internal/app ./internal/cliy gofmt/vet limpios.
- Nombrar el formato de error repetido del sleep cancelable de
process.Wait.
Estado ejecutado:
signal.godefinewaitCancelledFormat("wait cancelled: %w", 6 sitios).- Validacion ejecutada:
go test ./internal/processy gofmt/vet limpios.
- Nombrar formatos repetidos locales sin compartirlos con
internal/config(construccion y superficie distintas, contrato distinto).
Estado ejecutado:
checks/proc_paths.godefinemalformedFileFormat("malformed %s", 4 sitios en los samplers procfs/sysfs);checks/compare.godefinemustBeMappingSuffix(" must be a mapping", 4 sitios de build/analyze).rules/model.godefineruleSubjectPrefix("rule ", 4 sitios en ParseRules).- Validacion ejecutada:
go test ./internal/checks ./internal/rulesy gofmt/vet limpios.
- No cambiar YAML, JSON, CLI ni Web API publicos durante este refactor.
- No mover logica de seguridad fuera de
internal/operation. - No introducir compatibilidad dual ni aliases para config retirada.
- No pasar acciones automaticas por caminos distintos a los manuales.
- No convertir literales de tests/fixtures en constantes salvo que reduzca ambiguedad real.
- No ocultar textos de error de un solo uso detras de constantes si pierden claridad.
- No crear abstracciones globales por estetica; primero reutilizar owners.
Una fase se considera cerrada cuando:
- El diff queda limitado al owner previsto.
- No hay cambios ajenos revertidos ni artefactos temporales sin trackear.
- Los tests focales del paquete pasan.
make checkpasa cuando hay cambios Go/YAML.- El resultado puede inspeccionarse con
git diffsin tener que reconstruir el contexto mental de otra rama o cola oculta.