Vista generada de dec-0132: Styx multi-tipo: asset genérico, plugins de tipo de contenido, acciones y pipelines por kind
docs/decisions/dec-0132-styx-multi-tipo.mdVista generada desde
docs/decisions/dec-0132-styx-multi-tipo.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-02 |
| Fichero | docs/decisions/dec-0132-styx-multi-tipo.md |
Enmienda a: dec-0107, dec-0114
Por qué importa (del frontmatter del ADR):
Fija cómo Styx pasa de gestionar sólo películas y series a gestionar cualquier tipo de contenido finito: juegos de consola y de PC, homebrew, copias de PC, música y libros. Mantiene el catálogo Work → Edition → MediaAsset → SourceBinding y añade cuatro cosas. (1) Un asset puede ser un fichero, un árbol de ficheros o una imagen de sistema de ficheros. (2) El kind de una obra es un id registrado, no una unión cerrada de literales. (3) Plugins de tipo de contenido que aportan el esquema de metadatos, la estrategia de fuente de verdad (en el fichero, sidecar u overlay), la identificación, la presentación y las acciones. (4) Pipelines tipadas por kind, compuestas de etapas con un contrato por chunks en el data plane. Lo de vídeo (MediaIndex, CMF, planner, packaging) se queda como la faceta AV y la acción play de los kinds audiovisuales. Fija también la lista de cambios mínimos que tienen que entrar antes de que outcome/first-vertical cemente el catálogo. Sin ellos, cada tipo nuevo sería una migración y no un plugin.
Nodos del roadmap que lo citan en refs: track/content-kinds, track/pipelines, spike/archive-libs, spike/console-homebrew-protocols, track/kind-playstation, track/kind-pc-games, track/kind-backup
Páginas de la documentación que lo citan: Más allá de películas y series (especificado)
Leído de docs/decisions/dec-0132-styx-multi-tipo.md, el fichero canónico.
AskUserQuestion: el core se abre antes de
la vertical (M1, M2 y M4 como DM4 de track/domain-media; M3 ya va en el lock de dec-0127),
el primer tipo no audiovisual son los juegos (PS5 primero, después PS3 y PS4, y PC), la
instalación en consola se pospone y el desempaquetado empieza por RAR, zip y 7z. El resto de
§14 se lockea con la recomendación del ADR como parámetros reversibles. Todo está en
Lock (2026-10-02, waxin). Donde §1–§15 y el lock difieren, gana el
lock. Las enmiendas a dec-0107 y dec-0114 (§13) entran en vigor hoy.w12/multikind. Este ADR no crea nodos ni milestones en
styx.model.yml: los propone en §12. La única excepción, por decisión de waxin en el lock, es el
item de gate DM4 de track/domain-media.docs/track/plugin-seams/plans/multi-tipo-auditoria.md,
la auditoría de acoplamiento con file:line. Los ids D-n, C-n, S-n, K-n, W-n y Z-n
de este ADR remiten a ella.dec-0132 es el primer número libre en todas las ramas w*, ops/*,
spike/* e integ/* el 2026-10-02 (el último ocupado es dec-0131, en w10/vision).spike/media-storage-compression investiga el formato de
bytes: troceado, deduplicación, compresión con acceso aleatorio y separación de contenedor. El
2026-10-02 la rama no tiene commits propios; su worktree tiene scripts sin commitear
(dedup.py, container_split.py, seekable.py, packets.py). Este ADR no decide nada de
eso. Define el hueco donde encaja: la etapa store de §7 y su contrato.r01: Bun decide, Zig ejecuta; los bytes no pasan por JS ni por NATS.r04 / C14: SeekableMediaSource y cancel(requestId).r08 / r20 §6: Work → Edition → MediaAsset → SourceBinding.r16: frontier salvo blocker.r23 / dec-0019: Canonical Media Factory; el artefacto adquirido no es la verdad.r26: byte runtime en todas las direcciones.r28 §3: anti-especulativo; r28 §5: el discriminante va antes de F1.r48 / r49: seams, manifest, precedencia, MetadataPatch y sidecar conceptual (§3.4).dec-0033: Plugin SDK como familia tipada; el core se queda los bytes y la seguridad.dec-0034: Acquisition Runtime.dec-0088 / r59: tracks condicionales de consola en experiments[].dec-0106 (PROPOSED): ExtentMap.dec-0107 (LOCKED en w11/locks): LiveService paralelo y WorkKind de media finita.dec-0114 (LOCKED): scanner por contenido como plugin.dec-0117 / dec-0118: I1–I12 y permisos <recurso>:<acción>.dec-0119 / dec-0120: spire.dec-0121: ingesta con conduit.dec-0125, dec-0126, dec-0127, dec-0128, dec-0129, dec-0130 y dec-0131
(PROPOSED): cuentas, metadatos en el fichero, modelo de datos fichero-SSOT, dispositivos,
transferencias, modo público y federación.La auditoría da tres hallazgos.
qry.media.probe y cmd.media.openPackaging.scanAsset no persiste
nada si el probe de medios falla (S-2, CatalogHandler.ts:164-190). registerIngested pasa
por ahí (S-3). Hoy una subida de un .pkg, un EPUB o un .7z acaba en
CATALOG_PROBE_FAILED: el fichero se queda en la raíz de ingesta, cuenta en la cuota y no
entra en el catálogo.kind a 6
literales (ListWorksReplySchema y GetWorkReplySchema reutilizan WorkSchema; C-2), y
dec-0107 pide cerrar también el reply del bus. Un cliente generado con un switch exhaustivo
(web, Swift) rompe con el primer kind nuevo. Segundo, la jerarquía serie → temporada →
episodio no existe en el dominio (sólo en mocks, W-6), y el plan vertical la va a añadir (ola
B, B5). Si entra con columnas propias de series, cada tipo con jerarquía (disco → pista, juego →
DLC, saga → libro) pedirá las suyas.Todo lo demás acoplado a vídeo (planner, capabilities AV, resolvePlaybackSource, ficha y
reproductor web, plugin NFO) es correcto que sea de vídeo: pasa a ser la faceta AV y la acción
play de los kinds audiovisuales.
En el repo ya hay cinco "kind" distintos. Este ADR no añade un sexto con el mismo nombre:
| Término | Qué es | Dónde |
|---|---|---|
Work.kind (tipo de contenido) | Qué clase de obra es: movie, episode, game.ps5, music.album… | Work.ts:20. Este ADR lo abre (§4.2) |
PluginKind | Extension point que cubre un plugin: metadata, source, scanner… | manifest.ts:54 |
SourceKind | Protocolo de la fuente: local, http, torrent… | SourceBinding.ts:34 |
EditionKind | Clase de edición | Edition.ts:17 |
MediaAsset.kind | Hoy siempre 'logical-composition' | MediaAsset.ts:33 |
Work.kind. El campo conserva su nombre, así que no hay
migración.content-type
(§5).MediaIndex es la
faceta av.dec-0088: track/ps4-homebrew y track/playstation-official
(experiments[]) tratan la consola como cliente, es decir, Styx corriendo en la PS4. Este
ADR trata los juegos de PlayStation como contenido. Los nodos de §12 usan otro nombre.Work → Edition → MediaAsset → SourceBinding vale para
todos los tipos: juego → región/versión → dump → dónde están los bytes; disco → edición →
ficheros; backup → snapshot → árbol. Work sigue siendo media finita (dec-0107): live no
entra por aquí.MediaAsset tiene layout: file | tree | image (§4.1). Un árbol tiene
un manifiesto canónico, y su identidad es la raíz de ese manifiesto.Work.kind es un id registrado, no una unión de literales (§4.2). El core registra los kinds
de hoy; los plugins de tipo de contenido registran los suyos. El registro rechaza cualquier kind
que no sea finito. Los clientes tratan un kind desconocido con una presentación genérica.WorkRelation { parent, child, rel, ordinal }, la
misma para serie → episodio, disco → pista, juego → DLC o actualización, y saga → volumen.styx.json, dec-0127), la estrategia de fuente de verdad (en el fichero,
sidecar u overlay), las señales de identificación para el scanner (dec-0114), la presentación y
la lista de acciones y pipelines del kind.action. El plugin produce un plan declarativo, y el
core lo valida y lo ejecuta con sus piezas: el planner para play, transferencias dec-0129 para
download y sync, y el puente de dispositivos para install/send. Ningún plugin mueve bytes.acquire → unpack → verify → normalize → store → deliver. La política la decide Bun; los kernels de bytes son Zig, con un
contrato por chunks reanudable por ExtentMap y verificable por el árbol Merkle de dec-0129. La
CMF de vídeo (dec-0019) pasa a ser una instancia de este modelo, sin cambiar sus locks.outcome/first-vertical (§11): cinco cambios pequeños. Con ellos,
añadir un tipo es aditivo y no una migración.MediaAsset.layout: fichero, árbol o imagenlayout | Qué es | Ejemplos | Identidad (huella de asset) |
|---|---|---|---|
file (default, lo de hoy) | Un fichero | .mkv, .flac, .epub, .pkg de PS4, .iso | La huella que defina el plugin del kind. Para AV, la huella de payload de dec-0126 §3.3; si el kind no la define, la raíz de transferencia del fichero (dec-0129 §3.2) |
tree | Un directorio con N ficheros | Dump de PS5 en carpeta (sce_sys/param.json, eboot.bin…), juego de PC instalado, copia de PC, disco en FLAC por pistas | Raíz del manifiesto: BLAKE3 de JCS (RFC 8785) de [{relPath, size, mode, transferRoot}], ordenado por relPath en bytes UTF-8 |
image | Un fichero que contiene un sistema de ficheros | Imagen exFAT de un dump de PS5, .iso de PC | La raíz de transferencia del fichero. Si una etapa unpack deriva el árbol interior (§7), ese manifiesto es una faceta y no la identidad |
AssetManifest): lista canónica de entradas con relPath (normalizado NFC, sin
.. ni absolutos, separador /), size, mode (sólo file/dir/exec; los symlinks no
se siguen ni se materializan, §8) y transferRoot (la raíz Merkle de 16 KiB de dec-0129 de
cada fichero). Se guarda como derivado regenerable en L2, igual que el outboard de dec-0129..styx/ de la raíz de un árbol (§5.2) no entra en
el manifiesto. Escribir metadatos no cambia la identidad del asset, que es la misma regla que
dec-0126 §3.3 aplica a los metadatos incrustados.bundleId, §9). Deduplicar entre árboles
sale gratis: dos actualizaciones de un juego que comparten el 95 % de los ficheros comparten sus
transferRoot.MediaAsset.path y sizeBytes siguen: en un árbol, path es la raíz lógica y sizeBytes la
suma. container sólo tiene sentido en file e image.Work.kind como id registrado^[a-z][a-z0-9-]*(\.[a-z0-9-]+)*$, como mucho 64 caracteres. Los 6 kinds de
hoy conservan su id sin prefijo (movie, series, episode, concert, extra,
collection), así que no hay migración de filas. Los nuevos van con espacio de nombres:
game.ps4, game.ps5, game.pc, homebrew.app, homebrew.payload, music.album,
music.track, book, book.comic, backup.tree.WorkKindRegistry, en el core de catalog-svc): el core siembra los 6 kinds AV y
cada plugin content-type registra los suyos al inicializarse. Cada entrada declara
{id, finite: true, facets, layouts, editionKinds, relations, actions, pipelines}.
finite: false o cualquier intento de registrar
channel/live. Así se conserva el invariante de dec-0107 por construcción, no por
convención.@styx/api-contracts, kind es Type.String({ pattern, maxLength: 64 }).
El servidor valida contra el registro en cada frontera de escritura (createWork, la aplicación
de propuestas del scanner, registerIngested). El reply puede llevar cualquier id registrado.
switch. Va con un test por cliente.qry.catalog.listKinds, con su descriptor de presentación, para
que el cliente sepa qué filtros y qué fichas ofrecer.WorkKind es desde hoy un conjunto cerrado de media
finita" y pide cerrar kind del reply con un Type.Union de literales. Este ADR conserva el
invariante (sólo media finita; live por LiveService) y cambia el mecanismo: el conjunto
se cierra en un registro validado y no en una unión estática. Es una enmienda a la letra de
dec-0107 y necesita su propio AskUserQuestion (P1). La alternativa barata (añadir un literal
por kind, como la enmienda de SourceKind de dec-0107 P4) está en §13 y §14.av: el MediaIndex de hoy, por qry.media.probe;ps.sfo: param.sfo de PS4 (TITLE_ID, CONTENT_ID, APP_VER, TITLE…);ps.paramjson: sce_sys/param.json de PS5;audio.tags: Vorbis comments, ID3v2, ilst;book.opf: metadatos OPF de un EPUB;tree.manifest: el manifiesto de §4.1.layout y su identidad. Esto no puede fallar por facetas.unidentified y visible en la cola de revisión. Nunca
se pierde.media_indexes sigue siendo la tabla de la faceta av (ya está separada, S-6). El resto de
facetas van en una tabla asset_facets(asset_id, facet, version, facts jsonb, digest).WorkRelation { parentWorkId, childWorkId, rel, ordinal? } con rel registrado por el kind
padre: season/episode (serie), track (disco), dlc/update/patch (juego), volume
(saga, cómic) y snapshot (backup, si P3 elige obra por snapshot).ordinal es una ruta numérica ordenable ([1, 3] = temporada 1, episodio 3; [2, 7] = disco
2, pista 7). Una sola columna sirve para todo tipo.seriesId/season/episode (§11, M4).content-type)PluginExportsByKind gana 'content-type': IContentTypeProvider, en el mismo cambio que su
primer consumidor real (r28 §3, manifest.ts:25-30). Un plugin de tipo de contenido declara:
| Pieza | Qué es | Quién la ejecuta |
|---|---|---|
kinds[] | Los descriptores de §4.2 | Registro del core |
metadataSchema | Esquema TypeBox del bloque kinds.<id> de styx.json (versionado) | Core: valida y fusiona con la precedencia de r48 §3.2 |
truth | Estrategia de fuente de verdad (§5.2) | Core + daemon (escritura) |
recognize | Pistas baratas: extensiones, ficheros centinela (sce_sys/param.json, META-INF/container.xml) y facetas a pedir | Core, en el discovery de dec-0114 |
presentation | Descriptor de UI (§5.4) | Cliente |
actions[] | Ids de acciones aplicables (§6) | Core |
pipelines[] | Pipelines por intención (acquire, canonicalize, install…) (§7) | Orquestador |
scanner de dec-0114. Un plugin de tipo
de contenido puede traer su scanner en el mismo paquete (dos kinds en el manifest), pero el
contrato es el de dec-0114. Lo que este ADR enmienda de dec-0114 está en §13.Generaliza dec-0127 §3 ("el fichero manda; el catálogo es un índice derivado") a formatos que no son MKV/MP4:
truth | Cuándo | Dónde vive styx.json |
|---|---|---|
embed | El formato tiene sitio y Styx sabe escribirlo sin romperlo | Dentro del fichero. MKV y MP4 (dec-0126/0127); FLAC (bloque VORBIS_COMMENT + un bloque APPLICATION), MP3 (ID3v2 TXXX/GEOB), M4A (ilst, como MP4), EPUB (OPF + un fichero en META-INF/). Cada writer es una etapa del daemon con journal (dec-0126 §5) y llega con el kind que lo necesita |
sidecar | Árboles escribibles, o ficheros que no admiten escritura segura | .styx/styx.json en la raíz del árbol (excluido del manifiesto, §4.1), o <nombre>.styx.json junto a un fichero. Es el SidecarDescriptor de r48 §3.4 hecho concreto |
overlay | Lo que no se puede tocar: fuente remota, raíz ro, fichero firmado (.pkg de PS4), seeding, nlink > 1 | En el catálogo, con fileState: overlay (dec-0127 §6.3). Se materializa en un sidecar en cuanto el asset pase a una raíz escribible |
.pkg de PS4 está firmado: escribir dentro lo invalida. Su kind declara truth: overlay, o
sidecar cuando la raíz es escribible. El param.sfo interior es autoridad local de sólo
lectura (faceta), con la misma precedencia que un NFO (local-authoritative).sidecar en .styx/. Un dump en imagen exFAT: sidecar junto a la
imagen, porque Styx no escribe dentro de la imagen (§8).styx.json multi-tiposchema, workUid, kind, edition, títulos,
fecha, ids externos, artwork, curación) valen para todos los kinds.kinds.<id> y lo valida el esquema que registró su plugin. Por
ejemplo, kinds["game.ps5"] = {titleId, contentId, region, appVersion, requiredFirmware, dlc[]}
o kinds["book"] = {isbn, authors[], series, volume}.igdb, ps.titleId,
isbn, openlibrary y musicbrainz.av, audio, book, game, generic) y elige
según el descriptor. Un kind sin familia conocida usa generic: título, tamaño, árbol de
ficheros y acciones. Un plugin de terceros nunca inyecta UI ejecutable (dec-0118 §6, CSP)./library?tipo= pasa a filtrar por ids del registro, y /work/$workId sirve a todas las
familias (W-1 a W-4).action)IActionProvider { id, appliesTo: kindIds[], targets: TargetClass[], plan(asset, target, params) → Result<ActionPlan> }.ActionPlan es declarativo y validable, el patrón del EncoderProvider de r48 §3.3: una
lista de pasos de un vocabulario cerrado del core. El core lo valida contra los permisos del
actor y del dispositivo y lo ejecuta. Un plugin nunca abre un socket ni lee un byte.| Paso | Qué hace | Pieza del core que lo ejecuta |
|---|---|---|
play | Reproducir | El planner de hoy (r05/r20), sólo para kinds con faceta av |
read | Servir un libro o cómic por rangos a un lector | Egress del daemon (http_range), SCT de lectura |
download | Fichero original o árbol a un cliente | Transferencias de dec-0129 (scope download) |
sync | Mantener una réplica de un árbol en un nodo o dispositivo, por diferencias del manifiesto | dec-0129 (nodo ↔ nodo) + manifiesto (§4.1) |
offer-url | Dar a un dispositivo una URL firmada para que él tire de los bytes | Egress del daemon + SCT download atada al dispositivo |
device-call | Llamada de control a la API del dispositivo (p. ej. "instala desde esta URL") | Puente de dispositivos (§8.1), sin bytes de contenido |
push | Empujar ficheros al dispositivo por su protocolo (FTP…) | Puente de dispositivos, con un egress sink Zig |
run-pipeline | Lanzar una pipeline del kind (p. ej. normalizar carpeta → imagen antes de instalar) | Orquestador de §7 |
| Acción | Kinds | Plan |
|---|---|---|
play | movie, episode, concert, music.* | play (el de hoy; música es el subconjunto audio del mismo planner) |
read | book, book.comic | read |
download | todos | download |
install.pull | game.ps4, game.ps5, homebrew.app | run-pipeline(normalize-for-target)? → offer-url → device-call(install-from-url). La consola descarga del daemon con un SCT de un solo dispositivo y tiempo acotado. Recomendado por defecto: no abre egress nuevo del daemon |
install.push | ídem, cuando la herramienta homebrew sólo tiene FTP | push(ftp) por el puente. Más lento y en claro (§8) |
deploy.dev | homebrew.payload | push del binario recién construido al cargador de payloads del dispositivo. Es el bucle de desarrollo que waxin hoy hace a mano por FTP. Exige el dispositivo en modo desarrollo y confirmación (§8) |
sync | backup.tree, game.pc | sync con delta por manifiesto (sólo viajan los transferRoot nuevos) |
restore | backup.tree | download del árbol en un snapshot dado |
IActionProvider con su ficha de dispositivo.console.ps4/console.ps5). Este ADR no crea un registro de dispositivos paralelo.file | tree | image | facts), declarada por un kind para una intención:
acquire, canonicalize, install, archive…| Clase | Entrada → salida | Ejemplos |
|---|---|---|
acquire | fuente → file/tree | Subida conduit (dec-0121), swarm, HTTP, otro Styx (dec-0131), volcado desde la consola (el dispositivo como fuente: FTP de lectura), agente de copia de PC |
unpack | file/image → tree | .zip, .7z, .rar (si la licencia lo permite, P10), imagen exFAT → árbol, .pkg → árbol (sólo para facetas) |
verify | cualquiera → mismo + veredicto | Árbol Merkle (dec-0129), consistencia de param.json y del manifiesto, comparación opcional con una base de dumps conocidos |
normalize | cualquiera → cualquiera | Carpeta ↔ imagen según el destino, retag de música, CMF para vídeo (dec-0019), metadatos al fichero (dec-0126) |
store | cualquiera → referencia en un tier | Troceado, dedup, compresión, tier caliente o frío. El formato es del spike media-storage-compression |
deliver | referencia → destino | Los pasos download, offer-url, push y sync de §6 |
pull(ctx, out) → Chunk | EntryBegin | EntryEnd | Eof. Chunk = {entry, offset, bytes}.
Un árbol viaja como una secuencia de entradas, nunca como ficheros temporales completos si la
etapa siguiente puede consumir en streaming;cancel(requestId) por request (r04/C14);ExtentMap (dec-0106) por entrada. Una etapa declara si es determinista
e idempotente. Sólo las que lo son se reanudan a mitad; las demás reempiezan su entrada;/health y cuya responsabilidad prevista es justo el scheduler durable (P7). La decide
Bun (qué pipeline, con qué parámetros, cuándo); la ejecuta el daemon. Las clases de scheduler
son las de dec-0129 §3.8: un canonicalize o un archive nunca degrada una reproducción.pipeline-stage): un plugin TS aporta política (parámetros, cuándo
aplicar, cómo interpretar facts). El kernel que toca bytes es Zig y se elige por trust level
(I11): builtin/trusted-native en el proceso del daemon; sandboxed/community fuera de
proceso detrás de spire, con sólo el fd del recurso. Ningún kernel es "shell arbitrario desde
plugin" (r48 §3.3).| Pieza | Relación |
|---|---|
| CMF (dec-0019, r23) | Es la pipeline canonicalize de los kinds AV: acquire → verify → normalize(AV2/AV1/HEVC según CanonicalCodecPolicy) → store. Sus locks no cambian. Cambia que deja de ser el único tipo de pipeline |
| Transferencias (dec-0129) | Son acquire (entrada) y deliver (salida) de toda pipeline, con su modelo Transfer, su árbol Merkle y su entrada única registerIngested. El origin de dec-0129 §3.7 gana device (volcado desde una consola) |
| conduit (dec-0121) | La subida es una etapa acquire. Una carpeta = N sesiones + bundleId (§9) |
swarm (track/swarm-core, track/swarm-progressive) | acquire desde BitTorrent y deliver como siembra. Un árbol es un torrent v2 multi-fichero: sus transferRoot son los pieces root de BEP 52 (dec-0129 §3.2) |
Acquisition Runtime (track/acquisition-core, track/acquisition-native, dec-0034) | Decide qué adquirir y de dónde, y lanza la pipeline acquire del kind. Las policies por kind de dec-0034 (Movies/Series/Live/EPG/Music) se amplían con los kinds registrados |
| plugin-seams / plugins-sdk | content-type, action y pipeline-stage son extension points nuevos del mismo seam (manifest, registry, precedencia, health). Su forma pública la fija track/plugins-sdk con dec-0130 §8 |
spike media-storage-compression | Rellena store: formato de troceado y dedup, compresión con acceso aleatorio, tiers. Restricción que este ADR le pasa: un kind con acción play/read/install.pull necesita servir rangos desde store, así que su formato caliente debe ser seekable. Un tier frío de backup.tree puede no serlo |
acquire (subida conduit desde el PC, o volcado desde la consola);unpack (imagen → árbol, sólo para facetas);verify (Merkle + param.json + manifiesto completo);normalize (opcional: carpeta ↔ imagen según lo que espere el cargador del destino);store (dedup entre versiones y actualizaciones del mismo titleId);deliver = install.pull..pkg como file; verify con la faceta ps.sfo; deliver = install.pull
o install.push.backup.tree): acquire (agente de copia: sólo sube los transferRoot que el
nodo no tiene); verify; store (dedup + compresión, tier frío); deliver = restore/sync.
Esto ataca "ficheros muy grandes, transferencias lentas": no se mueve lo que ya está.acquire; verify; normalize (tags + styx.json incrustado); store;
deliver = play/download.acquire; normalize (OPF + styx.json); deliver = read/download.store. acquire (salida del build, por el CLI o un watcher)
→ deliver = deploy.dev. El valor es quitar el FTP a mano del bucle editar-compilar-probar.Las acciones hacia la LAN son superficie nueva. Hoy el daemon no tiene ninguna salida (dec-0117 I8, DS5-01), y las herramientas homebrew de consola exponen servicios sin autenticación y en claro.
styx-device-bridge, Zig): un contenedor propio con la única red de
salida hacia la LAN, deny-by-default, con la lista de ip:puerto de los dispositivos
emparejados (dec-0128) en la tabla del firewall de check:deploy. El daemon no gana egress
a la LAN: I8 se cumple sin reabrir styx-edge.install.pull es el modo por defecto porque no necesita el puente para los bytes: la
consola tira del egress que ya existe, con un SCT de scope download (dec-0129) atado al
dispositivo, de un solo asset y con caducidad corta. El puente sólo hace la device-call.push o una device-call sólo van a dispositivos de dec-0128
del mismo dueño u hogar, en rangos privados (RFC 1918, ULA). Nunca a una IP que traiga la
petición.<recurso>:<acción>): asset:install, device:control y
device:deploy-code. deploy.dev exige además que el dispositivo esté en modo desarrollo y
una confirmación explícita en el cliente por sesión de despliegue: enviar código ejecutable a una
consola es la acción más peligrosa de este ADR.styx:parser
(sin unreachable, .? ni catch {}; fuzz largo en fuzz_long.zig). Además:
relPath que salga, sea absoluto o tenga .. tras normalizar;link con su destino como dato, nunca como ruta a seguir;param.sfo, param.json): son parsers de entrada no confiable con la
misma regla styx:parser.sandboxed/community fuera de proceso,
y su ActionPlan pasa por la validación del core como cualquier otro.push, device-call, deploy.dev e install.* deja un evento
de seguridad con actor, dispositivo, asset y resultado. Nunca con el SCT ni la URL firmada (I12).| Sitio | Cambio | Cuándo |
|---|---|---|
@styx/domain Work.ts | WorkKindId (string con patrón) + CORE_WORK_KINDS (los 6 de hoy) + WorkKindDescriptor | Pre-vertical (M1) |
@styx/domain MediaAsset.ts | layout: 'file' | 'tree' | 'image' (default file) | Con el primer kind de árbol; la regla de identidad, pre-lock de dec-0127 (M3) |
@styx/domain nuevo | AssetManifest, AssetFacet, WorkRelation | WorkRelation pre-vertical (M4); el resto, con su consumidor |
@styx/domain Edition.ts | EditionKind también registrado por el kind (region, version, snapshot, translation…) | Con el primer kind no AV |
@styx/api-contracts catalog.ts | kind como string validado (C-1, C-2); qry.catalog.listKinds; ScanAssetHttpReply.index opcional (C-5); indexed → facets[] en WorkAssetSummary (C-4) | kind: pre-vertical (M1). Índice opcional: M2 |
@styx/api-contracts ingest.ts | bundleId? + manifestRoot? para agrupar una carpeta | Con el primer kind de árbol |
@styx/api-contracts metadata.ts | IMetadataPatchFields + kinds?: Record<KindId, FieldPatch…> | Con el primer kind no AV |
@styx/api-contracts nuevo | actions.ts (ActionPlan, cmd.<dominio>.runAction), pipelines.ts (job, estado, evt.pipeline.*) | Con su consumidor |
@styx/plugin-sdk | IMetadataProvider.enrich(assetId, facets) en vez de (assetId, index: IMediaIndex) (K-2); ISearchProvider.kind → string con espacio de nombres (K-3); extension points content-type, action y pipeline-stage | enrich: pre-SDK-público (M5). Extension points: con su consumidor |
@styx/source-sdk | ReadPurpose + transfer, verify, install (K-7); capability opcional list() para fuentes que enumeran (K-8, la "capability de enumeración" de dec-0114 §1) | Con su consumidor |
protocols/session-ipc | Ops de pipeline (cmd.media.runStage, pollStage, cancelStage) y de facetas (qry.media.facets) | Con su consumidor |
| catalog-svc | Registro de kinds; alta sin facetas obligatorias (S-2, S-3, S-4); asset_facets; work_relations; validar row.kind al leer (S-7); operations/registry.ts:57 al registro (S-8) | M1, M2 y M4 pre-vertical |
| workers-svc | Orquestador de pipelines | Con la primera pipeline no AV |
| web | Filtro por registro (W-1 a W-3); fichas por familia + generic; regla de kind desconocido | La regla, pre-vertical (M1); las fichas, con su kind |
outcome/first-vertical cemente el catálogo y los clientes generen tipos. Después costaría una
migración de clientes.outcome/first-verticalSólo lo que no sería aditivo si se hace después. Nada de esto implementa un kind nuevo.
| # | Cambio | Por qué antes | Coste (tamaño, no fecha) |
|---|---|---|---|
| M1 | Work.kind abierto en el wire + registro con los 6 kinds de hoy + regla de kind desconocido en web | Después del vertical, web y Swift tendrán switch exhaustivos sobre 6 literales y el wire HTTP cerrado (C-2). Abrirlo luego rompe clientes. La base de datos no cambia (S-5) | S: Work.ts (tipo + schema), WorkKindRegistry en catalog-svc (~1 fichero), catalog.ts (C-1, C-2), operations/registry.ts:57, catalog-repo.ts (validar en vez de castear, S-7), blender-catalog.ts:38, web (router.tsx:124-127, library-fetch.ts, library-queries.mock.ts, home-rails.ts, lib/operations/registry.ts:73) + un test por cliente con un kind desconocido. Necesita la enmienda a dec-0107 (P1) |
| M2 | El alta no exige faceta AV: saveIndexedAsset con índice opcional, ScanAssetHttpReply.index opcional y un fallo de probe = asset unidentified, no error | Hoy toda subida no AV se pierde para el catálogo (S-3). Cuando el vertical cierre la vía "scan + probe en la misma transacción" con su contrato y sus tests, separarlas será romperlo | S: CatalogHandler.ts:164-190 y :366-375, core/ports/index.ts:25-46, catalog-repo (escritura sin índice), catalog.ts:304-308 y la semántica de indexed (C-4). La tabla ya está separada (S-6) |
| M3 | En dec-0126/0127 (PROPOSED), antes de su lock: la huella de asset y el AssetId se definen por layout y por kind, no sólo como payload mdat/Clusters, y el styx.json reserva kinds.<id> | dec-0127 §6.4 deriva el AssetId de la huella AV. Lockeado así, un árbol o un .pkg no tienen AssetId estable sin reabrir el lock | XS: un párrafo en dec-0127 §6.4 y otro en §4.1, más una línea en dec-0126 §3.3. Sin código |
| M4 | La jerarquía de series de B5 (plan vertical, ola B) se modela como WorkRelation {parent, child, rel, ordinal} y no con columnas de serie | B5 es el primer código con jerarquía. Con columnas propias, música, juegos y libros pedirían las suyas, y migrar las de series después toca datos reales | 0 extra: es la misma tarea de B5 con otra forma. Una tabla work_relations en lugar de 3 columnas en works |
| M5 | IMetadataProvider.enrich(assetId, facets) en vez de (assetId, index: IMediaIndex) | track/plugins-sdk (después del vertical) lo hace público y dec-0130 §8 lo versiona. Después sería un semver major del SDK | XS: providers.ts:15 + metadata-local-nfo (no usa el índice para leer el NFO) + enrichment-service.ts |
Lo que no hace falta antes (es aditivo en cualquier momento): layout en MediaAsset (un campo
con default), AssetManifest, asset_facets, extension points nuevos, ReadPurpose, bundleId
en ingesta, EditionKind registrado, acciones, pipelines y el puente de dispositivos.
styx.model.yml)| Propuesta | Tipo | Depende de | Contenido | Gate (borrador) |
|---|---|---|---|---|
track/domain-media DM4 open-content-kind | item de gate nuevo en un track que ya existe | — | M1, M2 y M4 | Un kind desconocido llega por el wire sin romper web; una subida no AV entra como unidentified por el production path; B5 usa work_relations |
track/content-kinds | track nuevo, zona services | track/domain-media (DM4), track/plugin-seams w2 (scanner, dec-0114) | K1 registro + extension point content-type + asset_facets; K2 layout: tree + manifiesto + bundleId en ingesta; K3 extension point action + vocabulario de pasos + download/sync | K1 con el primer kind no AV de verdad (P4); K2 con un árbol real de waxin re-escaneado sin cambios en < 5 s (la vara de SCAN-01) |
track/pipelines | track nuevo, zonas dataplane + services | track/byte-runtime/ingest, dec-0129 lockeado, el spike de compresión para store | PL1 contrato de etapa Zig + orquestador en workers-svc; PL2 unpack (zip/7z/exFAT) como styx:parser con fuzz; PL3 la CMF expresada como pipeline | PL2: fuzz largo de cada parser + test de zip-slip, symlink y bomba que fallan contra un desempaquetador ingenuo (mutante) |
spike/console-homebrew-protocols | spike | — | Medir con las herramientas y versiones que usa waxin: servidor FTP, instalador remoto por URL, cargador de payloads, formatos de dump (carpeta e imagen) | Produce las fichas de dispositivo y fija si install.pull es viable en PS4 y en PS5 |
track/kind-playstation | track nuevo, zona interop (no ps4-homebrew, §2) | track/content-kinds (K1-K3), track/pipelines (PL1-PL2), dec-0128 (dispositivos), el spike de arriba | PS1 catálogo de dumps PS4/PS5 (facetas ps.sfo/ps.paramjson, sidecar/overlay); PS2 install.pull; PS3 puente de dispositivos + install.push; PS4 deploy.dev | PS2: un juego de waxin instalado en su consola desde Styx sin FTP a mano; PS4: bucle editar → compilar → ejecutar en consola sin pasos manuales |
track/kind-backup | track nuevo, zona dataplane | track/content-kinds K2, track/pipelines PL1, el formato store del spike | BK1 backup.tree con sync incremental por manifiesto; BK2 snapshots con dedup entre ellos | Segunda copia de un árbol de waxin con < 1 % de cambios: transfiere sólo los ficheros cambiados (bytes medidos), restaura bit a bit |
| Música y libros | kinds dentro de track/content-kinds | K1 + writers embed | Kinds music.* (acción play por el planner audio) y book (read) | Con su consumidor, sin fecha |
El orden recomendado está en P4. Ninguno de estos nodos entra en el camino crítico del vertical de vídeo salvo DM4, que es pequeño a propósito.
Game, Book, Album, al estilo de LiveService).
Rechazada: dec-0107 separó live porque no es media finita (sin duración, sin ediciones, con
ventana rodante). Un juego o un libro sí son media finita con ediciones y fuentes. Duplicar el
catálogo por tipo duplicaría selector, bindings, transferencias y federación.WorkKind como unión y añadir un literal por kind (la vía de la enmienda de
SourceKind en dec-0107 P4). Es la alternativa barata y no se descarta del todo (P1): sirve
mientras todos los kinds sean first-party. Se rechaza como diseño final porque impide que un
plugin registre un tipo (dec-0130 quiere un SDK público) y obliga a recompilar los clientes por
cada tipo nuevo.kind como string libre sin registro. Rechazada: pierde el invariante de dec-0107 (alguien
registraría channel) y la presentación quedaría sin descriptor.cap_drop: ALL) y superficie
del kernel. Parser de sólo lectura en el daemon.finite: true obligatorio y channel/live vetados. El reply cierra kind con
pattern + validación contra el registro, no con Type.Union. El resto del lock (B,
LiveService, P1-P6) no cambia.content-type (dec-0132)". La huella de §3 se generaliza por layout (§4.1). El
IdentificationProposal.kind es un WorkKindId registrado.origin gana device; registerIngested gana bundleId y
manifestRoot para árboles.Contestadas o diferidas en el lock. Se conservan como registro.
| # | Pregunta | Recomendación |
|---|---|---|
| P1 | ¿Se enmienda la letra de dec-0107 para que WorkKind sea un registro validado (finito por construcción) en vez de una unión de literales? | Sí. Conserva lo que dec-0107 protege (live fuera de Work) y es lo único que permite kinds por plugin. Si se prefiere no tocar un lock de ayer, la alternativa es la unión con un literal por kind first-party, y el registro llega con track/plugins-sdk (pagando entonces el cambio de clientes) |
| P2 | ¿Ids con espacio de nombres (game.ps5, music.album) y los 6 de hoy sin prefijo? | Sí. Cero migración y legible |
| P3 | Un backup de PC, ¿es una obra con un snapshot por edición, o una obra por snapshot? | Una obra, un snapshot por edición (EditionKind snapshot): la dedup entre snapshots cae dentro de la obra, y la UI la lista como "Copia del PC de waxin" con su historial |
| P4 | ¿Cuál es el primer tipo no de vídeo? | Juegos de PS4/PS5 (track/kind-playstation PS1-PS2). Es el dolor que más se repite, y ejercita todo el núcleo: árbol, imagen, facetas, sidecar/overlay, verificación, acción install. backup.tree viene casi gratis después. Música y libros, cuando haya writer embed |
| P5 | ¿Instalación en consola por pull (la consola descarga de Styx con un SCT) por defecto, y push por FTP sólo cuando la herramienta no permita otra cosa? | Sí. Pull no abre egress del daemon y va por el egress rápido que ya existe. El spike confirma la viabilidad en cada consola |
| P6 | ¿Puente de dispositivos como contenedor aparte (styx-device-bridge) con la única salida a la LAN? | Sí. Mantiene I8 intacto para el daemon. La alternativa (egress LAN en el daemon) amplía la superficie de todo el data plane |
| P7 | ¿El orquestador de pipelines vive en workers-svc? | Sí. Es el servicio que r17 reservó para jobs durables y hoy no tiene responsabilidad. dec-0019 ya fijó el patrón (Postgres FSM + JetStream) |
| P8 | ¿Entran M1, M2 y M4 como DM4 en el gate de track/domain-media antes de outcome/first-vertical, y M3 en el texto de dec-0126/0127 antes de su lock? | Sí. Son pequeños, y después cuestan migraciones de clientes o reabrir un lock. M5 puede ir en cualquier momento antes de track/plugins-sdk |
| P9 | ¿Los kinds de consola y homebrew quedan desactivados en modo público (dec-0130)? | Sí, sin opción de activarlos en una instancia pública. Son backups personales |
| P10 | ¿Qué formatos de archivo se desempaquetan? .zip y .7z tienen especificación abierta; RAR no (el descompresor de referencia tiene licencia propia) | .zip, .7z y exFAT primero, con parsers propios styx:parser. RAR sólo si waxin lo necesita, tras revisar su licencia |
| P11 | ¿deploy.dev (enviar binarios a la consola para desarrollo) entra en el alcance? | Sí, como último milestone (PS4), detrás del modo desarrollo del dispositivo y de la confirmación por sesión |
store (troceado, dedup, compresión, tiers): es del spike
media-storage-compression.Decisión de waxin vía AskUserQuestion (2026-10-02). Respuestas a §14:
P8 y P1 — El core se abre antes de la vertical. Sí. M1, M2 y M4 de §11 entran como item de
gate DM4 open-content-kind de track/domain-media, que es dependencia de
outcome/first-vertical:
Work.kind registrable por plugin y abierto en el wire, con registro validado. Es la
enmienda a la letra de dec-0107 (P1: sí);unidentified;WorkRelation genérica, no como columnas de serie.M3 ya va en el texto del lock de dec-0127 (2026-10-02, rama w10/adr-datos): huella y
AssetId por layout y por kind, y kinds.<id> reservado en styx.json. M5 puede ir en
cualquier momento antes de track/plugins-sdk (recomendación del ADR, reversible).
P4 — Primer tipo no audiovisual: juegos. Cambia el orden que recomendaba el ADR:
layout: tree e image, §4.1, más unpack, §7);game.ps5, game.ps4, game.ps3, game.pc; nombres exactos en el
ticket);embed.P5 — Instalación en consola: pospuesta. No se decide entre pull y push. install.pull,
install.push y el puente de dispositivos no tienen milestone hasta que waxin la reabra. La
primera vertical de juegos es catálogo, verificación, desempaquetado y transferencia
(download/sync, dec-0129), sin instalar en consola.
P10 — Desempaquetado en la primera versión de la pipeline: RAR, zip y 7z. RAR sobre todo, porque es el formato de los repacks:
unrar tiene licencia propia que
sólo permite descomprimir (no recrear el compresor). libarchive es BSD, y su soporte de
RAR5 lo verifica el ticket. Nada GPL (como dec-0129 §3.4.3);styx:parser o la librería que salga de
la misma revisión;Sin respuesta separada: recomendación del ADR como parámetro reversible. Se cambian sin ADR nuevo:
game.ps5, music.album), y los 6 de hoy sin prefijo;EditionKind
snapshot);styx-device-bridge), con la única
salida a la LAN. Sólo aplica cuando se reabra la instalación (punto 3);dec-0130),
sin opción de activarlos;deploy.dev entra como último milestone de consola, detrás de la instalación
(punto 3).Enmiendas que entran en vigor hoy (§13), con banner en cada ADR:
dec-0107: el "conjunto cerrado de media finita" se implementa como registro
validado (§4.2), con finite: true obligatorio y channel/live vetados. El reply cierra
kind con pattern + validación contra el registro, no con Type.Union. El resto de su lock
(opción B, LiveService, P1–P6) no cambia. dec-0107 está LOCKED en w11/locks; en esta
rama todavía figura como PROPOSED y el banner se integra con ese lock;dec-0114: libros, cómics y música se abordan con plugins content-type; la huella de §3
se generaliza por layout (§4.1); IdentificationProposal.kind es un WorkKindId
registrado.Enmienda que este lock no aplica: origin: device, bundleId y manifestRoot en
registerIngested (§13, para dec-0129). dec-0129 se lockeó el mismo día en
w10/adr-transferencias sin recogerlas. Quedan propuestas para cuando exista su consumidor
(árboles en ingesta, K2 de track/content-kinds), con su propio AskUserQuestion.
Nodos. Este lock sólo añade DM4 al gate de track/domain-media. El resto de §12
(track/content-kinds, track/pipelines, el spike de protocolos de consola,
track/kind-playstation, track/kind-backup) sigue siendo una propuesta que crea la
gobernanza tras el lock, ahora con el orden del punto 2: PS5 → PS3/PS4, juegos de PC con
unpack de RAR/zip/7z, y la instalación fuera hasta nuevo aviso.
docs/track/plugin-seams/plans/multi-tipo-auditoria.md.dec-0107 (§4.2) y dec-0114 (§13). Los dos llevan banner.dec-0126 y dec-0127 (M3), y dec-0129 (origin: device,
bundleId).dec-0019 (la CMF como instancia), dec-0033 (familia tipada), dec-0088
(colisión de vocabulario), dec-0117/dec-0118 (§8), dec-0128 (dispositivos), dec-0130
(modo público, SDK público), dec-0131 (federación).dec-0131
Vista generada de dec-0131: Federación entre servidores: pares emparejados, concesiones acotadas que se canjean por SCT, contenido remoto como `SourceBinding` y Jellyfin como importación y sincronización
dec-0133
Vista generada de dec-0133: Entidad release en el modelo del roadmap: `releases:` ordenado por dependencias, publicado por su definition of done