Vista generada de dec-0123: Release engineering: tren versionado + componentes SemVer, artefactos únicos, manifiesto firmado, canales nightly/stable, runtime adapters y CLI `styx` de instalación y actualización
docs/decisions/dec-0123-release-engineering.mdVista generada desde
docs/decisions/dec-0123-release-engineering.md. No se edita a mano:bun run docs:genla regenera ybun run docs:checkfalla si difiere. El estado aquí es el del model: si discrepa con otra página, manda el model.
| Campo | Valor |
|---|---|
| Estado | LOCKED |
| Fecha | 2026-10-01 |
| Fichero | docs/decisions/dec-0123-release-engineering.md |
Enmendado o sustituido por: dec-0133
Por qué importa (del frontmatter del ADR):
Fija cómo se versiona, construye, firma, publica, instala y actualiza Styx (track/release-engineering): versión global del tren + SemVer por componente + versiones de cable, binarios por componente como único artefacto (las imágenes OCI se derivan de esos mismos bytes),
styx-release.jsonfirmado como única unidad instalable, canales nightly/stable, CLIstyxcon una capa de runtime adapters (systemd nativo, Podman Quadlet, Docker Compose y más) sin supervisor propio, Postgres por runtime decidido con datos. Sin este ADR, cada executor de la lane release elegiría por su cuenta CalVer o SemVer, Changesets o planner, tags móviles o digests, un runtime único, y el instalador nacería sobre tags:latestque saltan migraciones y el orden de arranque del cable.
Nodos del roadmap que lo citan en refs: track/release-engineering
Páginas de la documentación que lo citan: Ciclo de vida (especificado), Releases (especificado), Comandos del CLI (especificado), Salida JSON y códigos de salida (especificado), Styx (especificado), Actualizar y versiones (especificado), Desplegar la nightly (especificado), Desplegar styx en Coolify (especificado), Instalar (especificado), Visión (especificado), Primer arranque (especificado)
Leído de docs/decisions/dec-0123-release-engineering.md, el fichero canónico.
styx de instalación y actualizaciónAskUserQuestion, lockea el ADR con sus
enmiendas del 2026-10-02: Coolify en tier 1, el tren en 0.0.0 alfa hasta estar listos, canales
nightly (builds clean/dirty) y stable, y beta como checkpoint opcional. Entra también la enmienda
de dec-0133 a D1 (los alfas son 0.0.0-alpha.N, lockeado el mismo día); la de D5 sigue
pendiente de su P5. Las preguntas de §9 que siguen abiertas quedan diferidas de forma explícita,
cada una a la lane o al ticket que la necesita. Todo está en
Lock (2026-10-03, waxin). Donde el resto del ADR y el lock difieren,
gana el lock.docs/track/release-engineering/evidence/research/.AskUserQuestion), antes del lock. Lo que sigue quedó
decidido ese día y el lock del 2026-10-03 lo mantiene:
styx-postgres empaquetado en el modo nativo (D11). PGlite sólo para tests, CI
y un perfil demo (§9 Q10, respondida)..claude/workflows/archive/orquestacion-2026-10/decisiones-pendientes.md en la rama
ops/orquestacion). Se aplica en D1 y D5:
0.0.0 en estado
alfa, indefinida;dec-0133 (propuesta el 2026-10-02, LOCKED el 2026-10-03): la entidad
release del model. D1: el primer v0.y.z lo corta waxin más adelante; hasta entonces el tren es
0.0.0 en estado alfa y los checkpoints previos son los alfas 0.0.0-alpha.N (alpha.1 =
0.0.0-alpha.1, el MVP); las versiones de componente son marcadores congelados en 0.0.0
hasta ese corte. D5 (pendiente de la P5 de dec-0133): un alpha o beta con channel: stable
recorre el pipeline de stable sin mover styx-release:stable. Coolify, tier 1 por la enmienda de arriba, es el primer despliegue real:
la aplicación del sitio de docs abre alpha.0 (track/release-engineering#26).w7/release-eng). Es la síntesis de tres diseños
independientes: UX del usuario final, ingeniería de versiones, y supply chain y canales.track/release-engineering. Plan en
docs/track/release-engineering/plans/00-plan.md.master) y stable (tag tras gates de release).migrator/svc.r16: frontier salvo blocker concreto.r17: microservicios split-duro, un contenedor por authority.r01: Bun decide, Zig ejecuta.r28: cada capa termina en un consumidor real.dec-0103: zkit, SDKs fijados por hash.dec-0110: motor dual, libav sólo como librería.dec-0117 I8 y dec-0118 §7/§9: contenedores mínimos y mínimo privilegio.dec-0120 / dec-0121: session-ipc v2 sobre spire, conduit wire v1.d12: toolchain Zig sin release.protocols/schema-versioning/README.md: contratos cerrados.Styx ya es una base usable: seis servicios Elysia 2 con bundle AOT, el daemon Zig, la web
TanStack Start, infra endurecida (postgres, valkey, NATS con NKeys, otel) y una AppSec en curso.
Lo que falta para que alguien que no sea waxin lo ejecute son estos hechos del árbol
(8f2c795):
| Hecho | Dónde |
|---|---|
Instalar exige checkout, Bun y Zig, más unos 10 pasos a mano. deploy:secrets, build:media-daemon, compose, bus:streams y host-firewall.sh apply hay que repetirlos tras cada arranque. Además: middlewares de Traefik copiados a mano y un network connect al proxy de Coolify. | deploy/README.md |
No hay registro de imágenes. Los compose de apps usan build:, no image:. | deploy/docker-compose.apps.yml |
Las versiones no significan nada: todos los apps/* están en 0.0.1, la raíz en 0.0.0 y el daemon en 0.1.0. Ningún binario reporta su versión. Hay un único tag git, checkpoint/f0a1-quality-gate. | package.json, native/zig/build.zig.zon |
Changesets está instalado pero ignora @styx/catalog-svc, con access: restricted y sin consumidor de changeset version. | .changeset/config.json |
El daemon sólo se compila para x86_64-linux-musl. | scripts/build-media-daemon.ts:22 |
apps/web no tiene Dockerfile ni servicio en compose. apps/cli es un README sin código. | docker/, apps/cli/README.md |
CI: sólo guard.yml, con permissions: contents: read. check:supply-chain prohíbe cualquier write. | .github/workflows/guard.yml, scripts/check-supply-chain.ts |
Bun: .bun-version y packageManager dicen 1.4.2; el entorno de trabajo usa 1.4.3 (lockfile v2). | .bun-version, package.json:76 |
El único camino de despliegue es Docker Engine + Compose v2. No hay unidades systemd, ni Quadlet, ni chart, ni binarios por servicio: los servicios sólo existen como bundle AOT dentro de una imagen oven/bun. | deploy/, docker/*.Dockerfile |
Las interfaces versionadas ya existen en el código: frame session-ipc 0x02, version por contrato del bus en BUS_ROUTES, sobre spire v2, SCT v1, conduit wire v1, media-object 0.1, journal drizzle por servicio. | protocols/*/version-history.md, packages/api-contracts/src/bus/ |
Los tres diseños coinciden en lo estructural:
Divergen en seis puntos: el esquema de la versión global, la herramienta de bump, el nombre y la forma del CLI, cómo se reconstruye para stable, el proxy y la firma. Este ADR elige en cada uno.
| Eje | Esquema | Fuente única | Audiencia |
|---|---|---|---|
| Global (tren) | SemVer 0.y.z. 1.0.0 lo declara waxin cuando el contrato de instalación sea estable. | tag git v0.y.z | usuario final, notas de release, styx update |
| Componente | SemVer propio. En 0.x: MINOR = incompatible o con migración; PATCH = compatible. | package.json#version (servicios, web, CLI), build.zig.zon + -Dversion (daemon, styx-upload), deploy/VERSION | operador, styx status, changelog por componente |
| Cable / esquema | enteros propios, sin cambios respecto a hoy | session-ipc frame, version por contrato del bus, sobre spire, SCT, conduit wire, media-object, journal drizzle | compatibilidad entre componentes, la comprueba el CLI |
Componente = lo que se despliega o se instala por separado:
extensions-svc entra cuando tenga código, por r28);media-daemon, web, styx-upload;deploy: el modelo de despliegue (D9), nats/, seccomp/, initdb, host-firewall.sh y
las plantillas de los adapters;styx, el CLI;styx-postgres, el Postgres empaquetado del modo nativo (D11).packages/* y protocols/* no son componentes: van dentro de los bundles o son
interfaces. El registro único es release/components.yml, con los paths de cada componente
y una lista ignore. Lo vigila check:release-map: un path que no pertenece a nada falla.
El tren no se deriva aritméticamente de los componentes. v0.4.0 significa "este conjunto
de versiones, probado junto".
Tags git:
v0.y.z para el tren, anotado. Un ruleset hace que sólo waxin lo cree y que no se pueda
mover ni borrar.<componente>/v<x.y.z> por componente, al estilo de un monorepo Go.Nightly: <próxima>-nightly.<YYYYMMDD>.<run>+g<sha12>. La fecha la pone CI al construir;
no es una fecha de roadmap. En los tags OCI el + pasa a -.
Enmienda 2026-10-02 (waxin, decidido):
0.0.0 en estado alfa, indefinida, y lo de este D1 queda
preparado (registro, metadatos, planner) sin cortar versiones. Los alfas son
0.0.0-alpha.N y el primer v0.y.z lo corta waxin más adelante (lock del 2026-10-03).g<sha12>)
y en /version. El formato exacto lo fija la lane A (tickets 01–02). Un nightly de CI sale
de un checkout limpio y siempre es clean; dirty sólo aparece en builds locales.scripts/release/plan.ts decide el nivel de cada componente. Toma el máximo de tres
fuentes:
version de un contrato que el componente provee o requiere → MINOR.contract, además un paso de upgrade.feat, fix, perf, sec, build o refactor → PATCH.! o el trailer Breaking: → MINOR.docs, test, gov y chore → nada.release/overrides/*.md. Sólo puede subir el nivel.Lo que decide el componente son los paths tocados, no el scope del commit. Una huella de
árbol evita reconstruir lo que no cambió: el sha256 de ls-tree de los paths y las deps, más
el toolchain y la base. Si la huella no cambia, el nightly reutiliza el digest anterior.
plan.ts --explain <comp> imprime el porqué de cada nivel. Es la evidencia del bump.
Los mismos extractores alimentan dos guards de PR nuevos, que viven en guard.yml:
check:interfaces:
version no sube;requires no lo provee nadie.check:migrations:
-- styx:kind=contract;Changesets se retira: .changeset/, @changesets/cli y los scripts changeset*. Hoy ignora
lo que se despliega, no publica nada y pide intención a mano donde hay detectores objetivos.
Tiene 0 consumidores reales; DELETE > @deprecated. La prosa de usuario sale de los trailers
Release-Note:, Breaking:, Upgrade-Step: y Security:.
styx-release.json: la única unidad instalableLo genera scripts/release/manifest.ts, con --check en CI; nunca se edita a mano. Lleva:
manifestVersion, train, channel, source{repo, commit, sourceDateEpoch};components{<comp>: {version, binaries{amd64, arm64}: {sha256, size, url}, image (digest del índice OCI), platforms{amd64, arm64}, treeHash, interfaces{provides, requires}, db{migration, snapshot}, changelog}}. El sha256 del binario es el mismo que el del fichero
dentro de la imagen (D4);infra{postgres, valkey, nats, otel-collector}: digest de índice para los runtimes OCI y
binario por arquitectura con sha256 para el modo nativo (D11);runtimes{<runtime>: {tier, adapterVersion, minHostVersion}}: qué runtimes soporta este tren
y con qué tier lo probó el gate (D10, D12);wire{session-ipc, conduit, sct, spireEnvelope, busRoutesHash};dependencies{zkit, spire, conduit, quic-zig}: commit más hash del .zon;toolchain{bun, zig, zigTarballSha256, zigTarballOci};upgrade{minUpgradeFrom, steps[], order[], reversibleWithoutRestore, minCli};gates{spec, attestation}, sbom[].Reglas:
image: …@sha256: o
con el binario verificado por sha256. Watchtower y el :latest quedan fuera, porque
saltarían migraciones y el orden de arranque.schema-versioning), así que no hay skew N-1 en el bus: emisor y receptor salen en el mismo
tren. Sí se garantiza que el código N-1 corre sobre el esquema de BD N (expand/contract, D7),
y eso es lo que hace barato el rollback.ghcr.io/mks2508/styx-release:<ver|nightly|stable> vía
ORAS, que es la fuente primaria porque el CLI sólo habla con un registro. Los binarios por
componente y arquitectura van al mismo registro, también como artefactos ORAS. Hay un espejo
en GitHub Releases.cosign sign-blob --bundle keyless con el OIDC de GitHub Actions.release-build.yml@refs/heads/master|refs/tags/v*), además de las extensiones de repo y
trigger del certificado. Un fork u otro workflow del repo no pasa.stable, nightly) van firmados;--allow-downgrade;revoked.json firmada antes de instalar o actualizar, porque Sigstore no
revoca.Artefacto único (enmienda runtime-agnóstico): por componente y arquitectura se construye un binario, y todo lo demás se deriva de esos bytes. La imagen OCI del componente es una base mínima por digest más ese binario, copiado tal cual; el modo nativo instala el mismo fichero. El gate lo comprueba: el sha256 del binario dentro de la imagen es el del manifiesto (mutante: una imagen con otro binario da rojo). Así, lo que se prueba en un runtime es exactamente lo que corre en los demás.
bun run release:build [--component X] [--arch amd64,arm64] [--push]
(scripts/release/build.ts). Se usa igual en local y en CI, con la lección de
test-zig-tsan.sh: una implementación y CI la llama. build:media-daemon pasa a ser un caso
de él. En local, sin --push, construye una imagen amd64 de prueba.target: 'bun') se compila una vez en la plataforma del runner con
bun build --compile --target=bun-linux-{x64,arm64}[-musl], que embebe el runtime Bun
fijado. Resultado: un ejecutable por servicio y arquitectura, sin Bun en el host.<svc> healthcheck), no wget ni un .js
aparte. El test/dockerfile-healthcheck.test.ts se actualiza conservando su motivo.RUN: sólo FROM (base mínima por digest de índice
multi-arch), COPY del binario de su TARGETARCH, ENV y USER. buildx produce amd64 y
arm64 sin emular, y el build es reproducible: SOURCE_DATE_EPOCH = fecha del commit,
rewrite-timestamp=true y frontend de Dockerfile por digest.web es el servidor SSR de TanStack Start compilado igual, no vite preview. Gana un
docker/web.Dockerfile derivado de su binario y entra en el modelo de despliegue con el
mismo endurecimiento.MemoryDenyWriteExecute (el JIT de JSC escribe código). Los
adapters lo aplican sólo al daemon y lo documentan como excepción, no como degradación
silenciosa.ReleaseSafe, {x86_64,aarch64}-linux-musl, -Dcpu=baseline (obligatorio para Pi 4/5 y
Ampere), -Dversion y -Dcommit.qemu-aarch64 user-mode. El smoke nativo arm64 es gate de stable.RUN en plataforma destino, con TARGETARCH, y lleva el mismo binario
estático que instala el modo nativo.ghcr.io/mks2508/styx-toolchain-zig (ORAS, retención infinita) y setup-zig-dev lo usa como
último mirror. Un stable sigue siendo reconstruible aunque ziglang.org retire el dev build.
Esto no resuelve d12; lo hace inofensivo para release.GET /version (policy: 'public', sólo componente, versión, tren y commit),
version en /health, y service.version y styx.train en OTel;styx-upload: --version e --interfaces, y metadata en el handshake spire,
para que playback-svc falle explícito ante un daemon incompatible;org.opencontainers.image.* más dev.styx.{component,train,interfaces}.BUILDKIT_SBOM_SCAN_STAGE, metafile de
Rolldown cruzado con bun.lock (lo que de verdad está en el bundle) y build.zig.zon, que
syft no entiende;mode=max y una provenance propia del daemon;schedule diario más workflow_dispatch. Se salta si master no cambió y
exige guard.yml verde en ese sha.release-build.yml (reutilizable). Es el único workflow que empuja y firma.styx verify
(check:infra-live empaquetado), ingesta e2e, web VOD, suite arm64 en qemu, SBOM completo,
licencias y verificación de firma de ida y vuelta. Los de tier 2, render + validate +
smoke.:nightly y styx-release:nightly. En rojo no mueve nada y abre un issue
rodante.v* que crea waxin sobre un sha con un candidato construido por
release-build.yml, normalmente el nightly de ese sha.release/gates.yml):
test:zig:tsan con canario y audit:safety;releaseBlocking (ver §9).release, con waxin como revisor, y la
promoción por digest con crane tag (nunca imagetools create, que deja la firma
huérfana). Por último se firman el manifiesto stable, la GitHub Release y
styx-release:stable.styx-release:stable). No hay un canal beta permanente hasta que el proyecto crezca mucho.scripts/release/version.ts aplica el plan (versiones, CHANGELOG por
componente, notas del tren) en un PR release/v0.y.z. Es el único punto de revisión humana
del contenido. Mergear el PR y poner el tag corta el stable.release/0.y desde el tag, con cherry-pick y el mismo pipeline. Sólo admite
PATCH: un detector que pida MINOR bloquea el hotfix.sha-* sin nightly verde, 7 días.guard.yml sigue en contents: read;release-build.yml, nightly.yml, release.yml y
retention.yml que declaren environment:.check:supply-chain se enmienda con esa allowlist cerrada y con las reglas runner-no-run,
base-is-index, buildkit-pinned y release-tools-pinned, cada una con su mutante.styxEl usuario final necesita Linux amd64 o arm64 con uno de los runtimes de D10: systemd (modo nativo, sin contenedores), Podman con Quadlet o Docker Engine con Compose v2; o un Incus, un clúster Kubernetes o un Coolify que ya tenga. Nada más: ni checkout, ni Bun, ni Zig.
curl -fsSL https://<base>/install.sh | sh # stable, runtime detectado
curl -fsSL https://<base>/install.sh | sh -s -- --channel nightly
curl -fsSL https://<base>/install.sh | sh -s -- --runtime podman # runtime forzadoinstall.sh es corto, POSIX, auditable y versionado en el repo.
styx-linux-<arch>, SHA256SUMS y su bundle de firma.sha256sum -c (la misma regla que check:supply-chain aplica a CI) y la firma
con cosign si está presente.--insecure.styx en apps/cli, que hoy es un TARGET sin código. Se compila con
bun build --compile para bun-linux-{x64,arm64}, va firmado y también se publica como
imagen OCI para los runtimes que lanzan tareas en contenedor (Coolify, Kubernetes; D6.7).
No es un supervisor: no queda residente y no vigila procesos. Habla con cada runtime por
su adapter (D9), que usa la API nativa (socket de Docker o de Podman, D-Bus de systemd,
API de Incus, API de Kubernetes), no el parseo de un CLI. Reutiliza el TS ya probado
moviéndolo a paquetes: gen-deploy-secrets, bus-streams, check-infra-live y
check-deploy.
styx install: asistente idempotente; dos ejecuciones dan un no-op con informe.
detect() de cada adapter), cgroups
v2, puertos 80/443 y UDP 4433-4435, disco, reloj, arch, GPU visible y, en modo nativo,
un Postgres del sistema utilizable (D11).--runtime; si no, el primero disponible en el orden por
defecto de la plataforma (D10), que fija la evidencia de la lane E y no esta prosa.:ro, OIDC y canal./opt/styx/releases/<tren>/ más el symlink current,
/etc/styx/styx.toml, /etc/styx/secrets/ (0400, dueño por Authority) y
/var/lib/styx/{backups,state.json}. Cada adapter añade lo suyo (unidades, Quadlets,
compose, chart) bajo releases/<tren>/<runtime>/.secrets: de compose, Secret= de Quadlet, credentials de systemd,
Secret montado en Kubernetes).bus:streams → *-migrate → servicios → proxy, expresado
con las dependencias del runtime (Requires=/After= y Type=oneshot en systemd y
Quadlet, depends_on: condition en compose, Job previo en Kubernetes).styx-firewall.service
donde hay systemd, y en modo nativo además IPAddressDeny=/IPAddressAllow= por unidad.styx verify, que es check:infra-live empaquetado y parametrizado por
runtime. Si falla, la instalación no se da por buena.styx update es una transacción:
minUpgradeFrom);bus:streams y *-migrate;current y recrea sólo lo que cambia, en el upgrade.order del
manifiesto (activate() del adapter);/version igual al manifiesto, handshake spire con
el daemon y styx verify;state.json hace la transacción reanudable tras un corte. --dry-run imprime el plan.
styx rollback tiene dos formas:
activate() de la release anterior) si el tren tenía
migraciones sólo expand (reversibleWithoutRestore);styx backup create|list|restore|prune:
pg_dump -Fc por base con el rol migrador, no el superusuario (encaja con dec-0118
§7);age a una clave de recuperación que se muestra una vez en el
install;styx status (con --json): tren, canal, versión y digest por componente, salud,
update disponible y último backup.
styx doctor, styx logs, styx config, styx secrets rotate,
styx channel, styx self-update y styx uninstall. Todo lo destructivo
lleva --dry-run y confirmación (apps/cli/README.md).
styx render --runtime <r> produce los ficheros de un runtime sin instalar nada, para
quien quiera aplicarlos con sus propias herramientas: compose por digest para Coolify
(Raw Compose Deployment, DS8-01) o un NAS, chart de Helm, módulo de NixOS. Las tareas
one-shot de esos runtimes se lanzan con la imagen del CLI
(ghcr.io/mks2508/styx-cli@sha256:… <tarea>).
notify.
notify (default), patch (sólo PATCH de tren con reversibleWithoutRestore) y
minor.minUpgradeFrom, nunca cambia de canal y nunca actualiza con reproducción
activa.Entre trenes stable, una migración que borra o renombra se parte en dos: expand en el tren N
y contract en N+1 o después. Cada .sql lleva la cabecera -- styx:kind=expand|contract|data.
El planner deriva reversibleWithoutRestore y los pasos de upgrade, y check:migrations lo
enforcea. Los *-migrate llevan lock_timeout, statement_timeout y advisory lock por base.
Se mantiene lo que ya es bueno: DDL sólo con <svc>_migrator, runtime sin DDL, one-shot antes
del runtime.
El primer stable sale con engine=zig: binario estático musl, stub libav, sin código LGPL
enlazado. libav llega como variante de imagen media-daemon:<v>-libav, con FFmpeg 8
dinámico (--enable-shared, sin gpl ni nonfree, lo que ya asserta build-ffmpeg.sh), las
.so en la imagen junto a LICENSE.LGPL y la URL y el sha del tarball fuente (LGPL §6). El
manifiesto marca engine. Respeta dec-0110: el motor dual sigue configurable; sólo cambia qué
variante se distribuye por defecto.
Styx no trae un proceso que supervise procesos. El CLI renderiza y delega: genera los ficheros nativos de cada runtime y le deja a él el ciclo de vida (systemd, Podman, dockerd, incusd, kubelet). Esto cumple la decisión 5 de waxin y r17: cada runtime expresa una unidad por Authority con sus propias herramientas.
Modelo de despliegue (deploy/model/, nuevo, versionado con el componente deploy):
describe cada Authority una sola vez, sin decir cómo la ejecuta cada runtime:
internal, edge, public-udp) y el socket de control del daemon con
su grupo;:ro;gpu: none|optional|required), límites de memoria, CPU y pids;*-migrate, bus:streams) con su orden, healthcheck y upgrade.order.El compose de deploy/ pasa a ser un render de este modelo; check:deploy valida el
render y un guard nuevo (check:deploy-render) falla si el compose versionado difiere de
lo que genera el modelo.
Interfaz RuntimeAdapter (TS en apps/cli, un módulo por runtime):
| Operación | Contrato |
|---|---|
detect() | ¿está el runtime, en qué versión, con cgroups v2, qué GPU ve? |
render() | modelo + manifiesto + config del host → ficheros nativos. Puro y determinista; se prueba con snapshots. |
validate() | comprueba los invariantes sobre lo renderizado (generaliza check:deploy) con la herramienta del runtime si existe. |
stage(release) | descarga y verifica (sha256 y firma) sin tocar lo que corre. |
runOneShot(name) | migraciones y bus:streams, en orden, como tarea del runtime. |
activate() | swap atómico y recreación sólo de lo que cambió, en upgrade.order. |
health() | readiness por Authority y /version igual al manifiesto. |
rollback() | vuelve a la release anterior con el mecanismo del runtime. |
metrics() | RAM y CPU por Authority leídas de su cgroup; alimenta la lane E. |
Común a todos, por encima del adapter: backup y restore, generación y rotación de
secretos, bus:streams, styx verify y la transacción reanudable de styx update
(state.json, D6.2). La lógica de update no se duplica por runtime.
Invariantes que todo adapter debe expresar (los mismos que hoy cumple el compose):
no-new-privileges,
seccomp o filtro de syscalls, topes de pids, memoria y CPU;<svc>_migrator y <svc>_svc, *-migrate antes
del servicio;/dev/dri o NVENC sin relajar el
resto.Un adapter que no pueda expresar un invariante falla en validate() con el paso que falta,
nunca degrada en silencio. Ejemplo: si la plantilla one-click de Coolify sólo entrega
secretos como variables de entorno (la encuesta no lo pudo confirmar; lo verifica el adapter), que resolveSecretEnv rechaza en prod; el adapter añade un
one-shot styx-cli init que materializa los secretos como ficheros en un volumen, y si no
puede, se niega.
Mecanismo de update y rollback por runtime:
systemd-sysupdate con imágenes sysext/confext firmadas
(dm-verity) cuando el host lo soporta, que mantiene N versiones y vuelve a la anterior sin
descargar; en hosts sin sysupdate, el mismo layout releases/<tren>/ + symlink current +
daemon-reload. La elección la hace detect(), no el usuario.Image=…@sha256: y hace daemon-reload; el
podman auto-update de Podman no se usa para elegir versión (saltaría el manifiesto),
sólo su rollback por unidad si el CLI lo delega.current + docker compose up -d por digest.helm upgrade --atomic con el Job *-migrate como hook previo.| Plataforma | Tier 1: e2e completo en cada release (D12) | Tier 2: render + validate + smoke | Tier 3: render + validate estático, documentado |
|---|---|---|---|
| Linux servidor u homelab, amd64/arm64 | systemd nativo (unidades endurecidas + credentials + sysupdate/sysext) · Podman Quadlet · Docker Compose | Incus (OCI) · Kubernetes (Helm, k3s) | módulo de NixOS |
| NAS | — | — | TrueNAS (Custom App YAML), Unraid (Compose Manager), Synology (Container Manager): todos consumen el compose renderizado |
| PaaS | Coolify (plantilla propia; enmienda 2026-10-02) | — | — |
| macOS | diferido | diferido | diferido: launchd nativo por Authority (la GPU no llega a una VM Linux) |
| Windows | diferido | diferido | diferido: WSL2 + Compose |
styx-cli init o validate() se niega). Si la plantilla one-click pública sigue esperando a
imágenes públicas es §9 Q1; la plantilla propia del tier 1 no depende de eso. El resto de
niveles se confirma: tier 2 = Incus y Kubernetes/Helm; macOS y Windows, diferidos.styx install cuando hay varios runtimes no lo fija este texto:
lo fija la evidencia de la lane E (RE12). Hasta entonces el orden provisional es systemd
nativo → Quadlet → Compose, el de la encuesta cualitativa.Medido el 2026-10-01 con el código real de catalog, sources e identity (informe y salidas en
docs/track/release-engineering/evidence/research/): PGlite 0.5.8 (motor PostgreSQL 18.3 en
WASM), in-process y con pglite-socket 0.2.11, frente a un PG 18.4 relocatable, un PG17 en
OCI y el PG16 del sistema. Una ejecución por backend con ±20 % de ruido: los números
orientan; la lane E los repite con N ≥ 5.
| Medida | PGlite in-process | pglite-socket | PG18 relocatable (nativo) | PG17 OCI |
|---|---|---|---|---|
Roles migrator/svc y audit append-only en el motor (I-d) | no: superusuario, SET ROLE reversible por el propio proceso | no: ignora usuario, contraseña y base | sí, deniega todo lo que debe | sí |
Concurrencia con el pool de bun:sql | una conexión | sólo max:1 + prepare:false; con max:5, SQLSTATE 26000 y cuelgues | sí | sí |
| Durabilidad ante corte de luz | no: 0 fsync, ni forzado | no | sí (fsync=on) | sí |
| Throughput mixto, 16 workers | 616–742 ops/s | 689 ops/s | 2722 ops/s | 2104 ops/s |
| RSS en reposo | 375–420 MB por servicio | 290–360 MB por instancia | ≈133 MB el clúster con las 3 bases | 127 MiB |
| Otros | pico de ≈1,1 GB en initdb; backup sólo en formato INSERT | un COPY … TO PROGRAM deja la instancia colgada hasta reiniciar | initdb 0,7 s, arranque 113 ms | — |
Decisión:
pglite-socket, además, rompe el aislamiento entre conexiones del pool.
Queda para tests y CI efímeros (driver drizzle-orm/pglite in-process, en los tests que no
prueban roles) y para un perfil demo sin datos reales, con aviso explícito de que no hay
durabilidad ni aislamiento de roles (§9 Q10, respondida por waxin el 2026-10-02: sí al demo).styx-postgres, un componente propio: PostgreSQL 18.x
compilado por el pipeline de release desde el tarball firmado de postgresql.org, con
OpenSSL 3, ICU actual, --with-systemd y rutas relativas $ORIGIN. No se distribuye el
relocatable de npm medido: lleva OpenSSL 1.1.1 (fuera de soporte) e ICU 60.
Type=notify, ProtectSystem=strict, StateDirectory=),
sólo socket Unix (listen_addresses=''), scram-sha-256 con la contraseña como
credential de systemd.10-styx-roles.sh; styx-<svc>-migrate
(Type=oneshot) corre antes de styx-<svc> por Requires=/After=.pg_upgrade --link y los binarios viejo y nuevo lado a lado; el
A/B de sysupdate da la vuelta atrás.--postgres=system si el preflight detecta un Postgres del sistema ≥ 16;
el CLI crea bases y roles con el mismo script. No es el default: la versión varía por
distro, su actualización mayor no la controla styx y el clúster es compartido.postgres
por digest de índice, como hoy. En Kubernetes, un StatefulSet con la misma imagen; quien ya
opere CloudNativePG puede apuntar a su Cluster. Si el OCI debe usar también la imagen de
styx-postgres para tener un solo Postgres en todos los runtimes es la §9 Q11.pg_dump -Fc por base con el rol migrador (D6.4).Un solo e2e, independiente del runtime: instala con styx install --runtime <r> desde
los artefactos publicados y sólo habla con la instalación por el CLI y por los endpoints
públicos. El harness recibe --runtime y nada más cambia. Es la regla de "un artefacto
probado": lo que se instala en la prueba es lo que instala el usuario.
Suite por runtime:
styx verify verde;Matriz:
| Runtime | PR (guard.yml) | Nightly | Stable |
|---|---|---|---|
| systemd nativo, amd64 | render + validate (snapshots) | suite completa | suite completa + upgrade desde el stable anterior |
| Podman Quadlet, amd64 | render + validate | suite completa | ídem |
| Docker Compose, amd64 | render + validate | suite completa | ídem |
| tier 1, arm64 | — | suite arm64 del daemon en qemu | install + verify + smoke en arm64 nativo (§9 Q8) |
| Coolify, amd64 | render + validate | suite completa | suite completa + upgrade desde el stable anterior |
| Incus, k3s/Helm | render + validate | install + verify + smoke | ídem |
| NixOS, NAS | render + validate estático | — | render + validate |
Un runtime soportado sin su fila verde es un gate rojo, como cualquier gate no ejecutado (D5). Bajar un runtime de tier exige cambiar este ADR, no saltarse la fila.
Cada invariante de D9 tiene su mutante: un render con dos Authority en una unidad, un
secreto como variable de entorno o un contenedor sin cap_drop deben dar rojo en
validate() y en el test sobre lo instalado.
Benchmark (lane E): con el mismo host y la misma release, mide por runtime la RAM en
reposo (suma de memory.current de cada cgroup más el propio runtime), los tiempos de
instalación, update y rollback (con la ventana sin servicio medida por sondas HTTP y QUIC),
el data plane con y sin /dev/dri, la superficie (systemd-analyze security,
capabilities efectivas, syscalls permitidas), el arranque en frío tras reboot y el disco. Y
por backend de BD, lo de D11 con N ≥ 5, mediana e intervalo. Los resultados son evidencia
del nodo y fijan el orden por defecto de D10.
| Opción | Diseño | Motivo del descarte |
|---|---|---|
CalVer YYYY.M.PATCH para el tren, estilo Home Assistant | 1 | Comunica antigüedad y presupone cadencia. Styx no tiene cadencia y la regla de casa prohíbe inventar fechas. SemVer 0.x dice lo que importa al que actualiza: si puede hacerlo sin pensar (PATCH) o si tiene que leer las notas (MINOR). Queda como alternativa si waxin lo prefiere (§9 Q2); el resto del diseño no cambia. |
| Una sola versión copiada en todos los paquetes | — | Pierde qué cambió de verdad, reconstruye y republica todo en cada release y hace inútil la huella de árbol. |
Changesets como fuente del bump, quitando el ignore y añadiendo un package.json falso al daemon | 1, 3 | Pide intención manual donde hay detectores objetivos, necesita un paquete npm ficticio para versionar Zig y no ve los cambios de interfaz. Los detectores + Conventional Commits + overrides que sólo suben cubren lo mismo con evidencia (--explain). |
| release-please | 2 (mencionado) | No conoce el grafo de workspace de Bun, ni Zig, ni los detectores. El patrón de PR de release sí se adopta. |
Tags móviles (:stable, :latest) en el compose de usuario o Watchtower | — | Saltan migraciones y el orden de arranque del cable. Los tags móviles sólo sirven para descubrir. |
| Stable recompilado desde el tag | — | Rompe "se publican los bytes probados". Se reconstruye sólo como gate de reproducibilidad. |
| Build arm64 con QEMU | — | Es lento, frágil con Bun y no reproducible (apk add en destino). El runner sin RUN y el cross-compile de Zig lo hacen innecesario. QEMU queda sólo para la suite de tests arm64. |
| Runners arm64 nativos para compilar | 1 | Son gratis sólo en repos públicos y no hacen falta para compilar. Se reservan para el smoke de stable. |
imagetools create para promocionar | — | Envuelve el manifest en un índice nuevo, cambia el digest y deja la firma huérfana. Se usa crane tag / regctl. |
| minisign con clave custodiada como firma primaria | 1 (doble firma) | Exige custodia y rotación manual de una clave. cosign keyless liga el artefacto al workflow exacto sin clave que robar. minisign queda como plan B si Rekor público no es aceptable con el repo privado (§9 Q3). |
Imagen styx-deploy aparte con las herramientas de host | 3 | El CLI compilado ya las lleva dentro. Para Coolify se publica el mismo CLI como imagen: un artefacto menos que versionar y firmar. |
Nombre styxctl en tools/ | 2 | apps/cli ya existe como TARGET con el nombre styx y sus reglas (dry-run, salida estable). Un segundo CLI duplicaría ownership. |
| CLI en Zig sobre zkit desde el principio (frontier r16) | 1 | El blocker es concreto y material: reescribir la generación de secretos (NKeys, SCT keys, proof headers, dueños por uid) y check:infra-live, que ya están probados en TS, retrasaría el primer stable sin ganancia para el usuario. Queda como candidato para una segunda iteración, con el binario Bun como vara de medir (tamaño y arranque). |
| Auto-update activado por defecto, estilo Coolify | 1 | Hay BD de usuario y el rollback de una migración contract exige restore. El default es notificar; PATCH automático sólo si se activa. |
| Skew N-1 en el bus (handlers que sirven v_n y v_n-1) | 2 | schema-versioning decide contratos cerrados y despliegue conjunto. Hacer skew exigiría doble versión en cada handler. Fuera de alcance: el tren es la unidad atómica. La ventana de inconsistencia durante el recreate secuencial queda en segundos y está documentada. |
Publicar packages/* en npm | — | Son internos (private). Si algún día se publica plugin-sdk o source-sdk, se promueve a componente con su tag. |
Un supervisor nativo universal propio (un styxd que arranque y vigile todas las Authority) | enmienda | Lo descarta waxin (2026-10-01). Además duplicaría lo que ya hacen bien systemd, Podman, dockerd o kubelet (reinicios, cgroups, logs, dependencias) y añadiría un proceso privilegiado más que auditar. El CLI renderiza y delega (D9). |
| Un solo runtime soportado (sólo Docker Compose, el de hoy) | enmienda | Contradice la decisión de waxin de un runtime totalmente agnóstico. Deja fuera a los hosts sin Docker (Fedora/RHEL con Podman, instalaciones sin contenedores) y no permite comparar con datos. |
Imágenes OCI construidas aparte de los binarios nativos (bundle sobre oven/bun en la imagen, binario compilado para nativo) | enmienda | Serían dos artefactos distintos por componente: lo probado en un runtime no sería lo que corre en otro. Con un binario y la imagen derivada de él, el sha256 es el mismo en todas partes (D4). |
| PGlite como Postgres por defecto del modo nativo | enmienda | Medido (D11): roles en disciplina de código y no en el motor (rompe DS-02 y el audit append-only), sin fsync, una conexión por servicio, entre 1/4 y 1/3 del throughput y unas 8 a 10 veces más RAM para los tres servicios. Queda para tests, CI y, si se quiere, demo. |
pglite-socket como Postgres por servicio | enmienda | Medido (D11): ignora usuario, contraseña y base; con el pool de bun:sql rompe el aislamiento entre conexiones, da SQLSTATE 26000 o se cuelga; un COPY … TO PROGRAM lo deja sin servicio hasta reiniciar. No es un Postgres para datos reales. |
Distribuir el Postgres relocatable de npm (@embedded-postgres) | enmienda | Es el más rápido medido, pero lleva OpenSSL 1.1.1 (fuera de soporte) e ICU 60 y su versión es beta. Se compila styx-postgres desde las fuentes (D11). |
| Postgres del sistema como default del modo nativo | enmienda | La versión cambia por distro, su actualización mayor la decide la distro (rompe el A/B y el rollback de D6) y el clúster es compartido con otras apps. Queda como --postgres=system. |
| Distribuir la imagen de SO del appliance | 1 | track/appliance-pi3 es un cliente. La imagen de SO es de F-APPLIANCE-OS (r59). Este ADR cubre el servidor. |
deploy/model/ como fuente del despliegue. El compose de deploy/ pasa a ser un
render guardado por check:deploy-render.styx-postgres (build propio de PostgreSQL, con su
propio seguimiento de CVE). Es el precio de tener una versión única probada en el modo
nativo.oven/bun a binario compilado con el runtime Bun
embebido; su tamaño y su RSS los mide la lane E antes de fijar la variante glibc o musl.check:interfaces y
check:migrations. También check:release-map, y check:supply-chain se amplía.deploy/docker-compose.apps.yml pasa a image: ${STYX_IMG_*:?}, generado desde
deploy/model/. El build: se mueve a un override de dev/CI
(docker-compose.apps.build.yml), con lo que el flujo de dev cambia en una línea.docker/, deploy/ y check-supply-chain.ts. Las
lanes B y C esperan a que la AppSec integre sus pasadas sobre esos ficheros, o se coordinan
por worktree (plan §4). Las lanes A y E son aditivas y pueden avanzar antes del lock.release/ en la raíz (registro de componentes, gates, overrides, notas, vex) y
scripts/release/.releaseBlocking en cuanto waxin apruebe el campo (§9 Q6).Respondidas por waxin el 2026-10-01 y fuera de esta lista: versionado global y por servicio (D1), prioridad de instalar y actualizar fácil (D6), canales nightly y stable (D5), runtime agnóstico sin supervisor propio y con varios runtimes medidos (D9, D10, D12; eso responde también si el tier 1 lleva dos o tres runtimes: lleva tres), y que el Postgres del modo nativo lo decida el ADR con datos (D11). Las de abajo siguen abiertas; Q4 y Q8 se reformulan por la enmienda y Q10–Q13 son nuevas.
0.0.0 alfa y los nightly son builds de desarrollo
clean/dirty (D1); los alfas son 0.0.0-alpha.N (lock de dec-0133) y el primer v0.y.z se
corta más adelante. Texto original: SemVer 0.y.z (recomendado) o CalVer.styx-proxy-proof,
styx-ingest-limits y ACME ya generados) como default en todos los runtimes de tier 1,
o si en cada runtime se integra con el proxy que ya tenga el host (Traefik, Caddy, el
ingress del clúster). Recomendado: proxy propio por defecto e integración como opción.
Toca DS3-01/DS8-01.styx install? Es la mayor
fricción de UX después del proxy.releaseBlocking. Añadir el campo al esquema del model (check:model) y decidir qué
nodos bloquean el primer stable. Como mínimo, las AppSec track/identity/sec y
track/byte-runtime/sec sin P0/P1.engine=zig por defecto (recomendado) o con la variante libav.ubuntu-24.04 y ubuntu-24.04-arm), gratis sólo si
el repo es público (Q1); hay que verificar la versión de Podman y de systemd que traen..bun-version, packageManager y las bases de los Dockerfiles a la
versión con la que se regenera el lock (1.4.3). Nunca un bun.lock de 1.3.x.demo ("probar styx sin instalar
nada", sin datos reales, con aviso de que no hay durabilidad ni aislamiento de roles), o
PGlite se queda sólo en tests y CI? Recomendado: sólo tests y CI hasta que haya demanda.postgres por digest (como hoy,
recomendado mientras styx-postgres no haya pasado RE11) o la imagen derivada de
styx-postgres, para que haya un solo Postgres probado en todos los runtimes?Decisión de waxin vía AskUserQuestion (2026-10-03), apuntada en el §33 de
.claude/workflows/archive/orquestacion-2026-10/decisiones-pendientes.md (rama
ops/orquestacion):
0.0.0 alfa hasta estar listos (D1): no se cortan versiones de tren hasta las alfas.styx-postgres en el modo nativo; PGlite para tests, CI y demo (D11).dec-0133, el mismo día): 0.0.0-alpha.N; alpha.1 =
0.0.0-alpha.1 es el MVP. El primer v0.y.z lo corta waxin más adelante. Las versiones de
componente quedan en 0.0.0 hasta ese corte (deploy/VERSION y los package.json de los
componentes Bun; los componentes Zig se alinean en un cambio aparte).releaseBlocking), Q7 (libav en el
primer stable), Q8 (runners del gate), Q9 (Bun), Q11 (Postgres en OCI) y Q12 (qué decide el
benchmark). Cada una se decide con waxin, vía AskUserQuestion, cuando la lane o el ticket que
la necesita llegue a ella; las que tocan publicar (Q1, Q3, Q6, Q7, Q8) antes del primer stable.dec-0133 a D5 (un alpha o beta por stable no mueve styx-release:stable):
pendiente de la P5 de dec-0133.