Vista generada de dec-0126: Metadatos incrustados en el propio fichero: plugin `metadata-embed`, escritura en el daemon y huella que los excluye
docs/decisions/dec-0126-metadatos-incrustados-en-el-fichero.mdVista generada desde
docs/decisions/dec-0126-metadatos-incrustados-en-el-fichero.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-0126-metadatos-incrustados-en-el-fichero.md |
Enmienda a: dec-0114, dec-0117
Enmendado o sustituido por: dec-0127
Por qué importa (del frontmatter del ADR):
Fija cómo Styx guarda los metadatos descriptivos y la portada dentro del propio fichero de vídeo (MP4 moov/udta/meta/ilst + covr; Matroska Tags/SimpleTag + Attachments): un plugin first-class metadata-embed decide QUÉ se escribe, catalog-svc ordena CUÁNDO, y el daemon Zig escribe los bytes con raíz fd-relativa, scope SCT annotate, in-place sobre padding con journal de preimagen o reescritura a staging + rename. Fija también que la huella de contenido de dec-0114 se calcula sobre el payload (mdat / Clusters) y una lista blanca de facts, para que incrustar no cambie la identidad del asset; que es opt-in por raíz; qué se excluye (raíces RO, fuentes remotas, ficheros en seeding o con hardlinks) y qué parte del catálogo es reconstruible desde los ficheros. Sin este ADR un executor escribiría tags desde TS, con la huella atada a los bytes de cabeza del fichero, o corrompería el único ejemplar de una película de la biblioteca.
Nodos del roadmap que lo citan en refs: track/domain-media/file-ssot, track/media-engine/metadata-embed
Páginas de la documentación que lo citan: Servidores conectados y Jellyfin (especificado), Metadatos en el fichero (especificado), Modelo de datos (especificado), Capacidades SCT (implementado), Transferencias (especificado), API para agentes y MCP (especificado), Importar NFO de Jellyfin (especificado), Activar los metadatos incrustados (especificado), Raíces y fuentes (especificado), Storage Box (especificado), Comandos del CLI (especificado), Plugins (implementado), Visión (especificado)
Leído de docs/decisions/dec-0126-metadatos-incrustados-en-el-fichero.md, el fichero canónico.
metadata-embed, escritura en el daemon y huella que los excluyedec-0127 y enmendado por él. waxin, vía
AskUserQuestion: lockear juntos dec-0126 (mecánica de escritura) y dec-0127 (modelo de
datos, metadata SSOT en el fichero). Las respuestas y las enmiendas que el lock aplica a este
texto están en Lock (2026-10-02, waxin). Donde este ADR y dec-0127
chocan, gana dec-0127: P1 pasa a fichero-SSOT con el catálogo como índice derivado, P2 pasa a
merge en las raíces que gestiona Styx, y §6/§7 quedan sustituidos. El análisis de formatos, la
huella y la escritura (§2, §3.3–§5, §10, §11) no cambian. Las enmiendas a dec-0114 y
dec-0117 (§8) entran en vigor con el lock.w9/docs), ticket track/docs#13, item DC9 del gate propuesto
de track/docs. La implementación cae en track/plugin-seams (la wave del scanner de
dec-0114), track/media-engine (editor de contenedor) y el daemon. Este ADR no crea nodos.dec-0123 es de w7/release-eng, dec-0124 es docs como contrato y
dec-0125 es el ADR de cuentas e identidad de esta misma lane. Este toma el siguiente libre.r01: los bytes de vídeo no atraviesan JS. Escribir dentro de un fichero de vídeo es data
plane.r04: SeekableMediaSource; las fuentes remotas no son escribibles por este ADR.r08: catálogo Work → Edition → MediaAsset → SourceBinding.r28: cada capa con su consumidor real.r48 §3.2/§3.3 y r49: precedencia de metadatos y MetadataPatch declarativo; un plugin
nunca muta.dec-0019: canonical media factory, AcquisitionArtifact con seeding lease.dec-0030: Storage Box como durable primary.dec-0110: MediaEngine dual Zig/libav.dec-0114: scanner por contenido y huella SC1.dec-0117: I1, I3, I5, I7, I8, I11 y I12.dec-0119 y dec-0120: bus y socket de control por spire, BUS_ROUTES.dec-0121: staging y rename fd-relativos de la ingesta.dec-0123 (PROPOSED): el CLI styx.dec-0124 (PROPOSED): la página explicacion/metadatos-en-el-fichero y el registro de
operaciones headless.metadata-local-nfo lee
<basename>.nfo con node:fs y extrae cuatro campos (plan vertical-vod-web, fila "Metadata
NFO de Jellyfin"). No hay ningún escritor de metadatos.dec-0114 §3 no existe en código. grep fingerprint da 0 hits
en native/zig y en los servicios; el único hit TS es packages/domain/src/models/ AcquisitionArtifact.ts. Es el momento barato de definirla de forma que no dependa de los
metadatos: después de implementarla, cambiarla obliga a re-hashear toda la biblioteca.dec-0114 §3 define la huella con "hash de una ventana de cabeza y una ventana de cola" y
con "tamaño exacto". Sobre un MP4 normalizado con moov delante (faststart), la cabeza es
moov. Sobre un MKV con Tags o Attachments al final, la cola es metadato. Incrustar
una portada cambiaría las dos ventanas y el tamaño. El asset dejaría de reconocerse a sí mismo.dec-0117
I7, {rootId, relPath} de extremo a extremo). La ingesta hace staging y rename fd-relativos
bajo su raíz (dec-0121). Y media-core tiene parsers ISOBMFF y Matroska con
BoundedReader y fuzz (native/zig/media-core/container/isobmff.zig, matroska.zig;
matroska.zig:359 ya reconoce Tags y Attachments como hijos de Segment).facts_from_av.zig:120-150
mapea cada AVStream y no mira AV_DISPOSITION_ATTACHED_PIC. El demuxer MP4 de FFmpeg
expone covr como un stream de vídeo con esa disposición. Tras incrustar, engine=libav
vería una pista MJPEG/PNG de más y el motor Zig no. Los dos motores divergen y el planner podría
elegirla.moov delante. Matroska es el segundo caso, y en él la edición in-place es
estructuralmente más fácil (§3.2).0x1254C367, hijo de Segment): uno o más Tag. Cada Tag lleva Targets y
SimpleTag anidables (TagName, TagLanguage o TagLanguageBCP47, TagString o
TagBinary, y SimpleTag hijos, como ACTOR con un CHARACTER dentro).TargetTypeValue (matroska.org, Tagging) para vídeo: 70 COLLECTION,
60 SEASON/SEQUEL/VOLUME, 50 MOVIE/EPISODE/CONCERT, 40 PART/SESSION,
30 CHAPTER, 20 SCENE, 10 SHOT. Una película va en 50. Un episodio lleva un Tag a 50 (el
episodio), otro a 60 (la temporada) y otro a 70 (la serie).IMDB: tt seguido de al menos 7 dígitos.TMDB: dígitos con prefijo obligatorio movie/ o tv/.TVDB: Series ID, legado.TVDB2: dígitos con prefijo obligatorio series/, episodes/ o movies/.IMDB, TMDB y TVDB2 con esos formatos exactos, y lee también TVDB.0x1941A469): cada AttachedFile lleva FileDescription, FileName,
FileMediaType, FileData y FileUID. La convención de portadas de matroska.org es:
cover.(jpg|png) (normal, 600 px en el lado menor), small_cover.* (120 px),
cover_land.* y small_cover_land.*;AttachedFile en orden de almacenamiento.mkvpropedit (MKVToolNix, src/common/kax_analyzer.cpp,
update_element):
EBML Void contiguos, se
sobrescribe ahí.Void lo bastante grande o se añade al final del fichero.
Para Tags la estrategia es siempre "al final". El hueco viejo pasa a ser Void y los
Void contiguos se fusionan.SeekHead (meta seek). Si el SeekHead de cabeza no tiene
sitio, se crea uno al final y el de cabeza se enlaza a él.Segment cuando se añade al final.Clusters. Es una
escritura al final más parches de pocos bytes en SeekHead, en el tamaño del Segment y en
los Void.Segment de tamaño desconocido, un Segment que no es el último elemento de
nivel 0, y un SeekHead sin sitio ni Void contiguo. En esos casos hace falta reescribir.CRC-32 (0xBF) es opcional como primer hijo de un elemento maestro. Si el Tags viejo lo
lleva, el editor lo recalcula o no lo emite. Nunca deja un CRC inválido.moov/udta/meta con hdlr de tipo mdir, e ilst con los items al estilo
iTunes. Es lo que leen ffprobe/libavformat, MP4Box, AtomicParsley, mutagen y las aplicaciones
de Apple.
meta es una FullBox (4 bytes de versión y flags) y en QuickTime
clásico no. El parser acepta las dos formas.data (1 = UTF-8,
21 = entero BE, 13 = JPEG, 14 = PNG):
©nam título; ©day fecha; ©gen género;desc y ldes descripción corta y larga;tvsh, tvsn, tves, tven serie, temporada, episodio e id de episodio;stik tipo de medio (9 película, 10 serie);covr portada, con uno o más átomos data, de tipo 13 (JPEG) o 14 (PNG).---- (mean + name +
data) en el espacio dev.mks2508.styx, con los mismos nombres y formatos de valor que
Matroska (IMDB, TMDB, TVDB2). Así un solo modelo de tags sirve para los dos
contenedores.moov:
moov delante (faststart, el caso de la biblioteca de waxin), si udta crece más allá
del espacio libre, mdat se desplaza y todos los offsets de stco/co64 cambian. Eso
obliga a reescribir el fichero entero: 4K son decenas de GB.free (o skip) justo detrás de
udta dentro de moov, o justo detrás de moov en el nivel 0.moov delante
con -moov_size. -movflags +faststart mueve moov delante con una segunda pasada que
reescribe el fichero.moov: udta más la cabecera de
free, y el tamaño de moov si free está fuera.mdat ni ningún byte de las tablas de muestras.moov es pequeño y no lleva tablas de muestras. Las mismas reglas
aplican sobre su udta. El mfra del final no se toca.udta. Incrustar no cambia la reproducción directa
si la escritura es correcta.No se afirma qué campos leen Jellyfin, Plex o Kodi de los tags incrustados, ni con qué nombre.
La compatibilidad con lectores de terceros se mide en el ticket con fixtures escritos por Styx y
leídos por cada herramienta. Ningún claim comparativo sin WC-JF12 (dec-0112).
| Etapa | Quién | Qué |
|---|---|---|
| Buscar metadatos y artwork | plugin metadata-embed (kind metadata, r48) | consulta proveedores (TMDb y otros, opcionales y con egress declarado) al añadir a la biblioteca, y devuelve MetadataPatch con evidencia, como cualquier proveedor |
| Merge | core (catalog-svc) | aplica la precedencia de r48 §3.2: lock manual > autoridad local (incluidos los tags ya incrustados y el NFO) > orden configurado > confianza |
| Proyección a contenedor | plugin metadata-embed (kind nuevo embed) | planEmbed(snapshot, containerFacts, embeddedNow) → EmbedPlan: función pura y declarativa. Dice qué tags y adjuntos deben quedar, en el modelo de tags común de §2, y no ve bytes de vídeo |
| Orden de escritura | core (orquestador de embed en catalog-svc) | decide si y cuándo se escribe (§4), pide la capability y manda el comando. Lleva un ledger idempotente por (assetId, embedRevision) |
| Escritura | daemon Zig | traduce EmbedPlan a operaciones de contenedor, verifica precondiciones, escribe con journal o con reescritura, y verifica después (§5) |
embed se añade a PluginExportsByKind en el mismo cambio que su primer consumidor
(el propio metadata-embed), como exige manifest.ts. Nombres finales en el ticket.dec-0117 I11). Devuelve datos y el core los
aplica.ro como una raíz más de I7. El
EmbedPlan los referencia por sha256 y tamaño. El daemon verifica el hash antes de
incrustar.TITLE, DATE_RELEASED, GENRE, SYNOPSIS,
SUMMARY, LAW_RATING…) y su correspondencia con los items ilst de §2.2 es una tabla fija y
versionada en media-core.IMDB, TMDB, TVDB2, con los formatos de §2.1. Son la clave de
reconstrucción (§6).cover (vertical) y, opcional, cover_land. En MKV se escriben como
Attachments con la convención de nombres de matroska.org y la normal primero. En MP4 van en
covr: el primer data es la vertical y el segundo, si existe, la horizontal. Tamaño de la
normal: 600 px en el lado menor. El fondo (backdrop) y el resto del artwork se quedan en el
catálogo.STYX_* en MKV, ----:dev.mks2508.styx:* en MP4):
STYX_SCHEMA: versión del modelo de tags.STYX_KIND: movie o episode.STYX_EDITION: etiqueta de edición (r08), por ejemplo "Director's Cut".STYX_EMBED_DIGEST: hash de los valores que Styx escribió. Detecta ediciones de terceros
(§7).@styx/authz. El fichero viaja: se copia, se comparte, se siembra. Sólo lleva metadatos
públicos de la obra.STYX_EMBED_DIGEST) no se
pisa en el modo por defecto merge. Se lleva a la cola de revisión. El modo overwrite es
explícito por raíz o por petición.dec-0114 §3)Precisado en el lock (
dec-0127, identidad por layout;dec-0132M3): esta huella es la dellayout: fileaudiovisual. Un árbol se identifica por la raíz de su manifiesto canónico (sin.styx/), y una imagen o un fichero firmado por el hash del fichero entero.
La huella fingerprintVersion: 1 se define sobre el payload y sobre una lista blanca de
facts. No usa los bytes de cabeza y cola del fichero.
mdat (en fragmentado, los de cada par
moof/mdat).Cluster completos, desde el primero hasta el
último.payloadSize, que sustituye a "tamaño exacto";dec-0114, del orden de 64 KiB);extradata/codec_private, timescale, número de muestras, duración, dimensiones, frecuencia
de muestreo y canales. Más la duración global.udta/meta/free/skip, Void, SeekHead, CRC-32;Info/Title, MuxingApp, WritingApp y DateUTC; mvhd/tkhd creation y modification
time;default/forced de pista, que mkvpropedit también edita;attached_pic y de adjunto;mdat y reescribe stco, o un MKV
al que se le añade un elemento al final, conservan la huella.dec-0114: pasa a ser el hash del payload, no del fichero.preEmbed. Deja de ser una señal tras incrustar, y no se recalcula.media-core): para todo fichero válido
F y todo EmbedPlan P, huella(aplicar(P, F)) == huella(F), y
facts_zig(aplicar(P, F)) ≡ facts_zig(F) en la lista blanca. Lo mismo con engine=libav, una
vez filtradas las pistas attached_pic (§1.5).annotate (enmienda a dec-0117 I3)annotate:
{rootId, relPath, dev, ino, size, mtimeNs, planDigest}. La precondición
es la observación del fichero que produjo el plan, y planDigest es el hash del EmbedPlan
exacto.jti y TTL corto.annotate nunca escribe bytes de mdat
ni de Cluster.dec-0117). Firma a petición de
catalog-svc por un comando de bus (cmd.playback.authorizeAnnotate, nombre final en el
ticket) que BUS_ROUTES sólo permite a la identidad de servicio de catalog. Comprueba que el
actor que originó la orden (usuario o automatismo de la raíz) tiene un permiso nuevo de
@styx/authz (provisional asset:metadata-write, junto a asset:play en policy.ts).cmd.media.embedMetadata por el socket de control de spire (dec-0120), el
mismo camino que openIngest, con la SCT annotate, el EmbedPlan y las referencias de
artwork. Respuestas y eventos: evt.media.metadataEmbedded y evt.media.metadataEmbedFailed
con motivo tipado. Lectura sin capability de escritura: qry.media.readEmbedded devuelve los
tags como datos y el artwork al almacén (§3.1). Es metadato, nunca bytes de vídeo.annotate ligada al plan, y la raíz marcada como escribible (§4.1). Que
un servicio que no sea playback quede comprometido no basta para escribir.evt.security.* o
evt.media.* con jti, rootId, hash del path y motivo. El path en claro sólo va en debug
local.Enmendado en el lock (
dec-0127§9.3 y P1): el valor por defecto deja de seroffen todas las raíces. Las raíces que gestiona Styx nacen enmerge; las existentes se ofrecen enmergedesde el asistente. Ver Lock, punto 2.
access: ro | rw-metadata y cada raíz de biblioteca en el
catálogo declara metadataEmbed: off | merge | overwrite (por defecto off) y
allowRewrite: false | true (por defecto false, §5.2).dec-0117 I8: las raíces de media siguen montadas ro salvo las que el
operador marca rw-metadata. Esas se montan rw y check:deploy exige coherencia entre el
compose y la config del daemon.
rw-metadata es escribible (statvfs
ST_RDONLY y faccessat).ro y reporta salud degradada (embed unavailable). Falla
cerrado: nunca reintenta escribir.dec-0114:
≥ τ_auto o con confirmación del usuario en la cola de revisión;dec-0121): se incrusta en el fichero de staging antes del rename
final. Coste cero: el fichero nunca existe sin metadatos.dec-0019): la receta escribe metadatos y
padding en el mismo mux. Es el sitio natural para la biblioteca de waxin, que ya pasa por un
paso de normalización.library.metadata.embed por asset o por raíz, con --dry-run
que muestra el diff entre los tags actuales y el plan sin escribir nada.| Caso | Por qué | Qué pasa |
|---|---|---|
| Fuentes remotas (HTTP, S3, WebDAV, Storage Box por SFTP, Jellyfin, Xtream) | SeekableMediaSource es de lectura (r04). Reescribir un objeto remoto de decenas de GB no es una edición | Metadatos sólo en el catálogo. Se incrustan cuando Styx escribe el fichero: ingesta, promoción al Storage Box desde el staging local, canonicalización |
Raíz ro, montaje ro o Storage Box montado ro | no escribible | Sólo catálogo, con salud informada |
Fichero en seeding: AcquisitionArtifact con seeding lease activo (dec-0019) o binding de swarm | cualquier byte cambiado rompe los hashes de pieza del torrent | Excluido hasta que expire el lease. Al promover a canonical, se incrusta |
st_nlink > 1 | un hardlink, típico de los flujos *arr que enlazan descargas con biblioteca: in-place modificaría también la copia que se siembra, y reescribir rompe el enlace y duplica el disco | Excluido en los dos modos, salvo override explícito por raíz |
| Propietario o permisos que el uid del daemon no puede escribir | el daemon no es root (I8) y no hace chown | Excluido, informado como permission |
Formato no soportado por el editor (AVI, TS, MKV con Segment de tamaño desconocido sin allowRewrite) | — | Sólo catálogo |
El editor vive en native/zig/media-core/container/edit/. Es puro: con la estructura parseada y
un EmbedPlan, devuelve una lista de regiones {offset, bytes} o "necesita reescritura". Lo
ejecuta un módulo del daemon. El motor es Zig propio: libav sólo sabe escribir un fichero
nuevo completo, así que dec-0110 no aporta aquí un segundo motor. Los parsers que usa son los
de media-core, con I5 (BoundedReader, aritmética checked, fuzz).
Aplica cuando el plan cabe:
udta + padding free;Void contiguos, o como añadido al final del
Segment más los parches de SeekHead y del tamaño del Segment.openat2(root_fd, relPath, RESOLVE_BENEATH|…) con O_RDWR (I7). Luego fstat: fichero
regular, dev de la raíz, nlink == 1, y (dev, ino, size, mtimeNs) igual al de la SCT. Si
no coincide, se aborta con PRECONDITION_FAILED: el catálogo re-probea y vuelve a planificar.(dev, ino) en el daemon:
flock(LOCK_EX|LOCK_NB), que sólo protege frente a herramientas que lo respetan.moov de dos horas ocupa pocos MB).rw
propio, fd-relativo, nunca en la raíz de media) y hace fsync del fichero y del directorio.
El registro lleva jti, rootId, hash del path, (dev, ino), el tamaño viejo y, por
región, offset, preimagen y sha256 de la imagen nueva.pwrite de las regiones (en MKV, primero el añadido al final y luego los parches) y
fdatasync.fsync del directorio. Invalida las entradas de la caché
L1/L2 que solapan las regiones escritas. El mtime nuevo se conserva a propósito: no se
restaura, porque herramientas como rsync usan tamaño + mtime para detectar cambios y hay que
avisarles. El ETag del byte path cambia con él.(dev, ino) y
compara cada región.
fsync.evt.media.metadataEmbedRecovered.Los lectores que ya parsearon moov y están leyendo mdat no se ven afectados por diseño: no
cambia ni un byte de las tablas de muestras ni del payload. El lease existe para que nadie lea la
cola de moov a medio escribir.
allowRewrite)Aplica cuando no hay padding, el SeekHead de MKV no tiene sitio, o el Segment es de tamaño
desconocido.
dec-0121):
copy_file_range, que usa reflink o copia en el servidor si el
sistema de ficheros lo soporta;stco/co64 reescritos con el desplazamiento nuevo.fsync, verificación de la huella de payload (igual a la original) y del parse completo.renameat sobre el original y fsync del directorio. El inodo cambia:
(dev, ino) de I7 y playback recrea la sesión;fchmod) y los xattrs que el daemon puede leer. Si el propietario no
es el uid del daemon, la reescritura no se intenta (§4.3).background del
byte runtime, con presupuesto y cancelable, y nunca compite con la reproducción.fallocate(FALLOC_FL_INSERT_RANGE) (insertar bloques sin copiar, en ext4/XFS) queda como
optimización futura: no es atómico y su recuperación exige journal de la cabecera entera.
Pregunta P5.allowRewrite deja padding. A partir de ahí todo es in-place.moov delante y una caja free de padding. Así ni siquiera la primera
incrustación reescribe un 4K.
styx library prepare --root <id>: una reescritura con
padding, con presupuesto, --dry-run y avance reanudable.moov delante es también lo que hace barata la
reproducción directa por byte-range, que es el camino principal de la ola A del plan vertical.
Incrustar no cambia esa propiedad.Sustituida en el lock por
dec-0127§5–§7 (fichero = autoridad, catálogo = índice derivado, overlay para lo no escribible). Se conserva como registro de la propuesta.
dec-0114 ya cuenta "tags
embebidos" como señal. Con STYX_SCHEMA presente y un id externo válido, la propuesta llega
con confianza 1 y sin red.La parte de sincronización queda sustituida en el lock por
dec-0127§5 y §6.2 y por la adopción inteligente de su lock (P3). La parte de NFO como importación sigue vigente.
metadata-local-nfo importa movie.nfo, tvshow.nfo,
season.nfo y el NFO de episodio. El uniqueid tmdb/imdb/tvdb es una señal de confianza 1
(plan vertical, B4). En una raíz con embed activo, lo importado se incrusta y el NFO deja de
hacer falta. Styx no borra ni reescribe el NFO: no toca ficheros que no son suyos.STYX_EMBED_DIGEST permite detectar que un tercero cambió los tags escritos por Styx (por
ejemplo con mkvpropedit o con mp3tag).dec-0114 §3: la huella se calcula sobre el payload con facts en lista blanca (§3.3 de
este ADR). "Tamaño exacto" pasa a ser payloadSize. El hash completo opcional pasa a ser hash
del payload. El OpenSubtitles se toma antes de la primera escritura.
0 desechable.dec-0117 I3: nuevo scope annotate (§3.4).dec-0117 I8: raíces rw-metadata opt-in (§4.1).dec-0117 I7 no cambia: se aplica tal cual a la apertura O_RDWR y al staging.dec-0124 §6.1)Operaciones del registro (ids provisionales; la paridad la comprueba check:headless-parity):
| Operación | Qué | CLI |
|---|---|---|
library.root.update | metadataEmbed, allowRewrite, exclusiones | styx library root set <id> --embed merge |
library.metadata.embed | incrustar un asset o una raíz; dryRun devuelve el diff | styx library embed <asset|--root id> [--dry-run] |
library.metadata.embedStatus | estado por asset: sin escribir, escrito rev N, pendiente, excluido(motivo), conflicto | styx library embed status … --json |
library.prepare | reescritura con padding (§5.3) | styx library prepare --root <id> |
library.metadata.import | importar NFO de Jellyfin | styx library import nfo --root <id> |
--dry-run las operaciones son destructivas: agentSafe: false, y piden confirmación en
el CLI y en MCP.| # | Riesgo | Mitigación |
|---|---|---|
| R1 | Corromper el único ejemplar de una película | opt-in por raíz; journal con preimagen y recuperación; post-verificación con restauración; propiedad de §3.3 con fuzz; --dry-run; reescritura sólo con allowRewrite. Recomendar backup de la raíz antes de activarla |
| R2 | Incrustar una identificación equivocada | sólo con ≥ τ_auto o confirmación; STYX_* marca lo escrito por Styx; quitar lo incrustado es una operación más del editor |
| R3 | Que la huella cambie al incrustar | payload + lista blanca (§3.3); test de propiedad en el gate. El riesgo real es que la huella se implemente antes del lock con la definición vieja |
| R4 | La portada aparece como pista de vídeo en engine=libav (facts_from_av.zig:120-150 no filtra attached_pic) y el planner la elige | filtrar AV_DISPOSITION_ATTACHED_PIC y las pistas de adjunto en facts, y test diferencial Zig/libav sobre un fixture con covr y otro con Attachments antes de la primera escritura |
| R5 | Romper el seeding o un hardlink | lease de seeding (dec-0019), nlink > 1 excluido, globs. Un cliente torrent que siembra el mismo fichero sin hardlink no se puede detectar: lo cubre el opt-in, con aviso en la doc |
| R6 | Backup y sincronización externos (rclone/SFTP al Storage Box, Time Machine) re-suben un fichero de decenas de GB por cada edición | escribir una vez al añadir; agrupar ediciones con debounce; documentar el coste en operacion/ |
| R7 | Relajar I8 da a un RCE del daemon escritura sobre la media de esas raíces | sólo raíces opt-in; I5/I6/I10 siguen aplicando; el resto de raíces sigue ro. Se acepta como riesgo residual documentado |
| R8 | Carrera con otro escritor externo durante la edición | precondición (dev, ino, size, mtimeNs) en la SCT, re-verificada tras el lease; flock como mejor esfuerzo. Queda una ventana residual entre el último fstat y la escritura |
| R9 | Variantes de formato: meta FullBox o no, CRC-32, Segment de tamaño desconocido, SeekHead sin sitio, moov con largesize | el parser acepta todas; el editor declara "necesita reescritura" en vez de improvisar; fixtures de FFmpeg, MP4Box, AtomicParsley, Apple y mkvmerge |
| R10 | Términos de los proveedores (atribución de TMDb, redistribución de imágenes): un fichero con portada incrustada que se comparte o se federa (r08) lleva el artwork consigo | proveedores opcionales y declarados; por defecto, sólo portada; la federación de ficheros con artwork incrustado se revisa en su propio ADR |
| R11 | Privacidad: lo incrustado viaja con el fichero | lista cerrada de campos públicos de obra (§3.2); test que falla si un STYX_* o un item contiene ids de usuario, de hogar o rutas |
| R12 | Memoria y E/S: preimagen de moov grande, reescrituras de 4K | tope de preimagen; clase background con presupuesto; cancelación por request |
poster.jpg) como almacén. Rechazada por waxin: son ficheros aparte que se
desincronizan al mover o renombrar. Quedan como vía de importación.dec-0114 §3 literal). Cada incrustación
cambiaría la identidad del asset.ffmpeg -c copy -metadata … en un worker. Es lo mismo que reescribir siempre, con un
proceso externo (r07) y sin control de padding ni de huella.off (recomendado, opt-in explícito) o merge en las
raíces locales nuevas.styx library prepare debe recomendarse antes de activar el embed.allowRewrite en raíces con copia (recomendado sólo opt-in) y
si se investiga FALLOC_FL_INSERT_RANGE.nlink > 1 (recomendado) o permitir el
override por raíz.track/plugin-seams gana el kind embed y el plugin metadata-embed, en la wave del scanner
de dec-0114. track/media-engine gana el editor de contenedor y el filtro attached_pic.
El daemon gana cmd.media.embedMetadata, qry.media.readEmbedded, el journal y el lease de
edición. playback-svc gana el scope annotate. BUS_ROUTES gana las rutas de §3.4.explicacion/metadatos-en-el-fichero (estado especificado) y las guías de
guias/biblioteca/ se atan a este ADR y a esos nodos (dec-0124).styx.model.yml.Decisión de waxin vía AskUserQuestion (2026-10-02): lockear juntos este ADR (cómo se
escribe dentro del fichero) y dec-0127 (qué es autoridad y cómo se lee rápido). Los dos
describen una misma escritura y desde hoy se leen juntos. Donde chocaban, gana dec-0127.
styx.json incrustado (dec-0127 §4.1). El catálogo es un índice derivado y reconstruible, y
el overlay es la única autoridad provisional, sólo para lo que no se puede escribir
(dec-0127 §6.3). En consecuencia:
dec-0127 §5–§7. La frase "La base de datos sigue siendo la
autoridad de lo que sirve la API" deja de valer: lo que sirve la API sale del índice, que es
una proyección del fichero.workUid pasan a la lista de lo reconstruible (van en el
documento, dec-0127 §4.1).dec-0127 §5, §6.2 y la adopción inteligente
de su lock (P3). STYX_EMBED_DIGEST se conserva con la definición de dec-0127 §4.2.merge en las raíces propias. Las raíces que gestiona
Styx (ingesta de dec-0121, raíces canónicas o normalizadas de dec-0019, raíces creadas
desde la UI) nacen con metadataEmbed: merge, in-place y allowRewrite: false. En una raíz
existente que se añade (por ejemplo, la biblioteca de Jellyfin) el asistente ofrece activar
merge y explica los riesgos de §10. No se activa sin un sí explícito, y mientras tanto la raíz
vive en overlay sin perder nada. El montaje rw-metadata de §4.1 (enmienda a dec-0117 I8)
sigue siendo explícito por raíz: una raíz propia lo trae configurado; una existente lo gana al
aceptar el asistente.styx.json. La tabla fija de §3.2 pasa a ser la
proyección pura y determinista del documento (dec-0127 §4.2), y se añade el propio documento
(adjunto styx.json en Matroska, item ----:dev.mks2508.styx:doc en MP4).art (lock de dec-0127, P4).dec-0127 P7.cover, cover_land, logo, thumb); MP4: covr múltiple más referencias
en styx.json. Variantes y tamaños derivados siguen siendo caché del daemon (dec-0127
§8.1), nunca bytes en el fichero. Presupuesto por defecto: 4 MiB de artwork por fichero,
reversible por raíz.styx library prepare recomendado antes de activar el embed en una raíz existente.allowRewrite sólo opt-in por raíz. FALLOC_FL_INSERT_RANGE no se investiga en esta
wave.nlink > 1 excluido siempre, con override explícito por raíz.dec-0132 M3). La huella de §3.3 es la del layout: file
audiovisual y no cambia. Para árboles, imágenes y ficheros firmados manda la sección de
identidad por layout del lock de dec-0127.dec-0127 usa para AssetId), §3.4 (scope annotate), §4.2 (disparadores; la ingesta escribe
el documento antes del rename), §4.3 (exclusiones duras), §5 (in-place con journal, reescritura
con allowRewrite, padding), §9 (superficie headless), §10 (riesgos) y §11 (alternativas).dec-0114 §3 (huella sobre payload y
facts en lista blanca), dec-0117 I3 (scope annotate) e I8 (raíces rw-metadata). Los dos
llevan banner de enmienda desde este lock.El lock desbloquea el código de producción que depende de este ADR (editor de contenedor en
media-core, cmd.media.embedMetadata, scope annotate, kind embed). No crea nodos ni gates:
la wave que lo implemente los propone en styx.model.yml con el gate mínimo de §13 y el de
dec-0127 §14.
dec-0114 y dec-0117 llevan banner de enmienda desde el lock (2026-10-02).dec-0127 (LOCKED el mismo día) enmienda P1, P2, §3.2, §6 y §7.dec-0124 §6.3 nombra este ADR como el que cierra D4.dec-0019 (seeding lease, canonicalización), dec-0110 (motores), dec-0121
(staging fd-relativo), dec-0030 (Storage Box).