Vista generada de dec-0121: Ingesta de ficheros con el SDK Zig de conduit
docs/decisions/dec-0121-ingesta-conduit-sdk-zig.mdVista generada desde
docs/decisions/dec-0121-ingesta-conduit-sdk-zig.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-09-30 |
| Fichero | docs/decisions/dec-0121-ingesta-conduit-sdk-zig.md |
Enmienda a: r26, dec-0103, dec-0117, dec-0120
Enmendado o sustituido por: dec-0129
Por qué importa (del frontmatter del ADR):
Abre el lado de escritura de styx: los clientes suben ficheros a un nodo con el wire de conduit (SDK Zig de MKS2508/conduit), el daemon los recibe en su IngestSink con SCT de scope ingest obligatoria, playback-svc decide quién sube y firma la capability, y catalog-svc da de alta el asset cuando playback-svc se lo pide por el bus (cmd.catalog.registerIngested). Fija el reparto Bun decide / Zig ejecuta de la ingesta, la semántica de la SCT ingest (verificada por spire.cap), los contratos cmd.media.openIngest/pollIngestEvents/closeIngest del socket de control de spire, la cota de abandono de una sesión que sube y la entrega al menos una vez hasta el catálogo.
Nodos del roadmap que lo citan en refs: track/byte-runtime/ingest, track/byte-runtime/egress, track/byte-runtime/ingest-web, track/oss-extraction
Páginas de la documentación que lo citan: Puesta en marcha de un nodo (implementado), Arquitectura (implementado), Catálogo federado (implementado), Servidores conectados y Jellyfin (especificado), Metadatos en el fichero (especificado), Modo público y Styx como plataforma (especificado), Protocolos binarios (implementado), Seguridad del data plane (implementado), Modelo de amenazas (implementado), Capacidades SCT (implementado), zkit, spire y conduit (implementado), Transferencias (especificado), Subir ficheros (especificado), Visión (especificado)
Leído de docs/decisions/dec-0121-ingesta-conduit-sdk-zig.md, el fichero canónico.
integration-design.md) se usa como lista de
requisitos, no como bloqueo.dec-0129 (LOCKED, 2026-10-02):
cmd.catalog.registerIngested se generaliza con origin: upload | acquisition | import | federation y la raíz de transferencia: todo lo que entra al nodo usa la misma alta
(dec-0129 §3.7);dec-0129 §3.2, P3). El SHA-256 de este ADR sigue siendo el
del wire v1; el paso de la ingesta a wire v2 y a claves BLAKE3 es CV2-03
(docs/track/byte-runtime/plans/conduit-wire-v2.md, rama w13/conduit-v2);Authorizer (configurable, household por defecto; lock de dec-0129, punto 3);dec-0120 (SDK de mensajería) y conduit el siguiente.w4/conduit mergea w4/spire):
la autoridad que verifica la SCT es la de spire.cap (styx no tiene copia, dec-0119 §9), el
IPC va por el socket de control de spire con contratos generados (§4) y el alta en catálogo
pasa de evt.media.ingestCompleted en JetStream a cmd.catalog.registerIngested por el bus
(spire no tiene consumidor durable, y el único consumidor es catalog). Enmienda de paso
dec-0120: tres rutas nuevas del daemon y dos de NATS en BUS_ROUTES.[0, max) de la SCT no impedía llenar el staging: acota un fichero, no cuántos
abre un actor (con STYX_INGEST_SESSIONS_PER_ACTOR sesiones, un solo usuario podía reservar
el staging entero y dejar a los demás en 507). Y una sesión cerrada o abandonada seguía
ocupando en el daemon su hueco de subida y su reserva de staging hasta la expiración por
inactividad de conduit (1 h): closeIngest sólo desligaba la autoridad. Ahora hay tope de
staging por actor y tope de fichero en el daemon, closeIngest aborta las subidas de la
sesión, y playback-svc cierra las abandonadas de un usuario antes de abrirle otra (conduit
0ad6ecc: Limits.staging_bytes_per_subject, Server.abortOwner,
Authorizer.stillValidFn).w13/conduit-v2 (2026-10-02; la integración la decide waxin): la ingesta
pasa a conduit wire v2 y sólo v2, con la raíz BLAKE3 como identidad de lo transferido.
fallocate del fichero entero al
crear; desde conduit 41743fe, publicado en master, fuera del lock del servidor, con
vuelta atrás completa si falla). El presupuesto de staging pasa a ser disco real, así que el daemon comprueba al
arrancar que el volumen de STYX_INGEST_ROOT tiene libre STYX_INGEST_STAGING_MIB más
STYX_INGEST_MIN_FREE_MIB (2048 por defecto) con zkit Root.availableBytes (fstatfs sobre
el fd de la raíz); si no, la ingesta no arranca. Donde zkit no puede medirlo (fuera de Linux
de 64 bits) sólo avisa. Es una comprobación al arrancar; el tope total de la raíz y el suelo
en cada create son la capa 2 de TS5-P2-01 (abajo, en las cuotas).Limits.min_protocol = max_protocol = 2): un create v1 recibe
400 protocol_unsupported. Ningún consumidor necesita v1: styx-upload es el único cliente
y habla v2; la web y Swift no tienen flujo de subida (§5); la C-ABI de conduit sigue en v1 y
el host que la use tendrá que pasar a v2.dec-0129 P3,
docs/track/byte-runtime/plans/conduit-wire-v2.md): el evento de
cmd.media.pollIngestEvents y cmd.catalog.registerIngested (los dos en versión 2) llevan
blake3 en lugar de sha256, y el nombre final bajo la raíz es esa raíz en hex (lo que
imprime b3sum). Es la identidad de los bytes transferidos, no la del asset: los ids
work_ing_/edition_ing_/asset_ing_<32 hex de la raíz> de catalog son claves de
idempotencia de la entrega al menos una vez, y el AssetId de dec-0127 (huella del
payload, que no cambia al reescribir metadatos) lo asignará el scanner de dec-0114, con la
migración de ids que dec-0127 §6.4 ya prevé. Lo subido antes en v1 conserva su nombre
SHA-256 y sus ids; el mismo contenido subido de nuevo en v2 tiene otro nombre y otras filas
hasta esa migración.styx-upload → IngestSink, 256 MiB): evidencia en
docs/track/byte-runtime/ingest/evidence/conduit-v2-2026-10-02/.r26: la ingesta de clientes es un productor más de ingress del Byte Runtime, pero con
escritores no confiables: nace una frontera de confianza nueva (la crítica tenía razón),
que este ADR cierra con capability, cuotas y fd-relativo. No se toca la lista de prioridades
del GlobalByteScheduler (no existe en código); cuando exista, la clase de la ingesta será
un ADR aparte.dec-0103: styx consume el SDK de conduit por arista .zon pinneada (el commit lo fija
native/zig/build.zig.zon); no reimplementa ni el codec ni el servidor, ni el barrido de
staging huérfano (Server.init del SDK).dec-0117: I3 aplicado a la ingesta (scope ingest, un recurso concreto), semántica de un
solo uso por jti con re-autorización por petición, e I7 en el staging.publish (SEC-Z01).feat/zig-sdk) lo convirtió en un SDK Zig: wire v1 normativo con
vectores de conformidad, máquina de upload sans-IO, servidor transport-agnostic con
autorización obligatoria, cuotas, staging fd-relativo, hash en streaming sobre el prefijo en
orden (zkit.ReorderBuffer) y handles generacionales (zkit.safety), C-ABI y WASM.| Pieza | Dónde | Hace |
|---|---|---|
| Cliente | styx-upload (native/zig/cli/), cliente del SDK | Pide la sesión a playback-svc con el access token de identity, sube directo al daemon con el wire de conduit, renueva la capability antes de que caduque (hilo propio, §2), reanuda tras 401, corte o SIGKILL (estado en $XDG_STATE_HOME/styx) |
| Decisión | playback-svc, rutas /ingest/* con policy session | Resuelve el principal (qry.identity.session por el bus, revocation-aware), liga la sesión en el daemon y firma la SCT; sesiones visibles sólo para quien las abrió |
| Ejecución | IngestSink del daemon (media-daemon/ingest/) | Sirve el conduit.server.Server por HTTP/1.1 (conduit.http.Service), autoriza cada petición contra el Authority de spire.cap, escribe en staging y deja el hecho en su cola |
| Catálogo | catalog-svc, cmd.catalog.registerIngested | Da de alta Work/Edition/MediaAsset con ids derivados del hash de la transferencia (SHA-256; raíz BLAKE3 en la rama w13/conduit-v2) y ffprobe por workers-svc |
Los bytes nunca atraviesan Bun ni NATS (r01): el IPC y el bus llevan sólo la sesión, la capability y el hecho de que un fichero verificado existe.
ingest)Authority de spire.cap (spire e2276c8+): verifyBase64 (firma, clave,
audiencia, ventana, vida; sin lock, no gasta nada) y consume (sesión ligada y jti). El
Verified que pasa de una a otra lleva un sello HMAC-SHA256 de las claims con una clave
aleatoria de esa autoridad: uno construido a mano (con las claims de peekBase64), editado
tras verificar o sellado por otra autoridad se rechaza como BadSignature.sid = los 16 bytes de ing_<32 hex>, que el daemon liga por IPC (cmd.media.openIngest) al actor y a
resource = sha256("styx.sct.res.ingest\0" ‖ rootId). El daemon rechaza ligar otra raíz.range_kind = bytes, [0, max) = tope del fichero. Sin tope, o con inicio ≠ 0, el Authority
lo rechaza en cada petición (RangeDenied). Ese tope acota un fichero, no lo que un actor
reserva: con varias sesiones (o varias subidas por sesión) un solo token o usuario podría
reservar el staging entero. Eso lo cierran los topes del daemon (§3): fichero como mucho
STYX_INGEST_MAX_FILE_MIB diga lo que diga la capability, y como mucho
STYX_INGEST_STAGING_PER_ACTOR_MIB reservado entre todas las subidas abiertas de un actor,
siempre por debajo del total (STYX_INGEST_STAGING_MIB; si no, el daemon no arranca).
playback-svc rechaza antes un sizeBytes por encima de su STYX_INGEST_MAX_FILE_MIB (el mismo
valor; PLAYBACK_INGEST_FILE_TOO_LARGE, 413, sin firmar) y el contrato lo acota a
MAX_INGEST_FILE_BYTES = 1 TiB (422).ingest_usage (una fila por raíz, actor y fichero; antes del alta, también si el alta
falla) y qry.catalog.ingestUsage (sólo playback-svc) devuelve lo guardado por el actor y por
todos. openSession suma a eso lo que aún no está en el ledger (subidas terminadas sin
registrar, sesiones vivas y aperturas en vuelo, cada una por su tope) y rechaza con
PLAYBACK_INGEST_QUOTA_EXCEEDED (507) por encima de STYX_INGEST_QUOTA_PER_ACTOR_MIB (64 GiB
por defecto) y con PLAYBACK_INGEST_UNAVAILABLE (503) por encima de STYX_INGEST_ROOT_MAX_MIB
(128 GiB) entre todos o si el ledger no responde (falla cerrado). Segunda capa, en el daemon
(TS5-P2-01 capa 2): conduit pregunta al sink en la admisión del create, bajo su lock y antes
de contar o reservar staging (Config.capacity, conduit a137617), y el create recibe
507 insufficient_storage si lo terminado bajo la raíz más lo reservado por las subidas
abiertas más su tamaño pasa de STYX_INGEST_ROOT_MAX_MIB (la misma variable y el mismo
defecto que playback-svc), o si el volumen se quedaría con menos de STYX_INGEST_MIN_FREE_MIB
libres (Root.availableBytes de zkit, fstatfs sobre el fd de la raíz; si no se puede leer,
se rechaza). La comprobación al arrancar cubre el staging; ésta cubre lo que los ficheros
terminados van sumando después, porque un fichero terminado se queda en el volumen y su
staging se libera. Lo terminado se cuenta al arrancar (los ficheros con nombre de raíz BLAKE3)
y en cada finalize de un fichero nuevo (el mismo contenido otra vez no ocupa más). Así un
fallo de playback-svc o catalog-svc, o un relay reiniciado con eventos sin registrar, ya no
llena el disco que el nodo comparte con sus bases de datos.jti: la primera petición al sink admite el token (consume el jti); las
siguientes con el mismo token se re-autorizan por petición (caducidad, sesión aún ligada,
scope, recurso). Reanudar tras caducar = token nuevo para la misma sesión.STYX_INGEST_SESSIONS_PER_ACTOR huecos por actor y 1024 en
total. El hueco se comprueba antes de que el Authority gaste el jti (las claims sin
verificar de Authority.peekBase64 sólo enrutan; todo lo que concede pasa por la
verificación), así que un token rechazado por falta de sitio se puede presentar otra vez.
Llena, se niega en vez de olvidar. Renovar en bucle cuesta a su sesión un hueco, no la caché.Authority.verifyBase64: firma, clave, audiencia, ventana, vida; sin lock y sin gastar
nada) con el sink suelto; sólo un token verificado llega al hueco y a Authority.consume
(sesión ligada y jti), ya bajo el mutex. Una riada de firmas malas no hace esperar a las
sesiones admitidas ni llega a la poda. La poda de huecos muertos corre sólo si pudo morir
alguno desde la anterior (avanzó el reloj o se desligó una sesión,
Authority.unbindCount): repetir un token sin sitio no poda cada vez.STYX_INGEST_SESSIONS_PER_ACTOR sesiones abiertas por
usuario (las que se están abriendo cuentan; PLAYBACK_INGEST_TOO_MANY_SESSIONS, 429, sin
llegar al daemon) y un cubo de 4 renovaciones por sesión que se repone una por minuto
(PLAYBACK_INGEST_RENEW_RATE_LIMITED, 429, sin firmar nada). Un cliente legítimo renueva
una vez por vida de capability (styx-upload, al 60 %: cada 180 s). Entre los dos topes, lo
que un usuario ocupa en la caché del sink y en la de jti del Authority depende de él, no
de lo que hagan los demás.styx-upload renueva la capability desde un hilo propio al 60 % de su vida (medida con
el reloj monotónico desde que llega; expiresAt acotado a 30–300 s por si el reloj local
está desviado), aunque haya chunks en vuelo, y cada petición sale con el token vigente.
Mientras el proceso vive, la sesión se renueva a cualquier velocidad de subida. Un 401
sólo pide otra si no hay ya una más nueva, y reanuda desde la lista de chunks del nodo.exp + max_skew_s + idleGraceMs de
su última capability: el daemon la admite hasta exp + max_skew_s (300 + 30 s), un cliente
puede empezar un chunk justo antes y tardar en subirlo lo que tarde un chunk entero antes
de renovar. Con el chunk máximo del sink, 16 MiB (Limits.max_chunk que fija el
daemon; styx-upload --chunk-size no pasa de ahí), y una subida mínima soportada de
400 kbps para clientes que sólo renuevan al 401, idleGraceMs >= MIN_IDLE_GRACE_MS = ceil(16 MiB × 8 / 400 kbps) = 336 s. Por defecto 360 s (STYX_INGEST_IDLE_GRACE_MS; por
debajo del mínimo playback-svc no arranca). Con otro chunk máximo o subida mínima, el
mínimo se recalcula con la misma fórmula (constantes de IngestHandler).styx-upload no espera a eso ante
un fallo definitivo: cierra la sesión con DELETE /ingest/sessions/:id antes de olvidar su
registro. Un fallo reintentable agotado conserva la sesión para reanudar.sweepExpired) cierra abandonadas, caducadas y terminadas cuyo cierre falló; si el cierre
falla (daemon caído), la deja para el siguiente. El tope global de playback-svc cuenta sólo
sesiones vivas; ya no se olvidan caducadas sin cerrarlas.cmd.media.closeIngest revoca: la siguiente petición es 401.
Y la revocación llega a los bytes: tras desligar, el daemon suelta el hueco de la sesión en su
caché de tokens y aborta en conduit sus subidas (IngestSink.revoke → Server.abortOwner):
el hueco del actor, la reserva de staging y el .part vuelven en el acto, un chunk en vuelo
acaba en 404 y una subida que ya estaba renombrando su fichero termina y se retira justo
después. Un create cuyo cuerpo aún llegaba cuando se revocó se rechaza (conduit re-pregunta
al sink bajo su lock antes de confirmarlo, Authorizer.stillValidFn). Lo mismo vale para el
barrido de playback-svc, que cierra con el mismo mensaje.protocols/capability-token/vectors.json incluye el digest
de la raíz de ingesta.Apagado por defecto: sin STYX_INGEST_ROOT no hay endpoint de escritura.
Listener HTTP/1.1 en 127.0.0.1:8790 por defecto (STYX_INGEST_LISTEN), en claro: se publica
detrás de un terminador TLS. STYX_INGEST_PUBLIC_URL es lo que se anuncia al cliente.
401 antes de enrutar o leer el cuerpo si no hay capability válida. Una sesión de otra
capability es 404 (el SDK separa la clave de cuota, Grant.subject = actor, de la de
visibilidad, Grant.owner = sesión de ingesta).
Cuotas: sesiones abiertas por actor (STYX_INGEST_SESSIONS_PER_ACTOR), bytes de staging
reservados al crear (STYX_INGEST_STAGING_MIB en total, STYX_INGEST_STAGING_PER_ACTOR_MIB
por actor, 16384 por defecto y siempre por debajo del total), fichero más grande
(STYX_INGEST_MAX_FILE_MIB, 16384 por defecto, ≤ el tope por actor), memoria de sesiones con
BudgetAllocator y ventana de chunks.
Conexiones e hilos acotados por identidad, no sólo en total (conduit.http.Service,
ZSDK3-P2-01; enmienda: antes eran 64 conexiones globales y 30 s sin progreso, y 64 cabeceras
sin token o un actor goteando 64 KiB cada 29 s dejaban la ingesta fuera de servicio). Una
conexión sin credencial aceptada espera en un pool pre-auth de 16 con 10 s absolutos para su
cabecera; lleno, se cierra la más antigua, así que mantener cabeceras abiertas no deja fuera a
nadie. Una petición con cuerpo cuya SCT se aceptó ocupa uno de 64 sitios, como mucho 16 por
actor (Grant.subject): por encima, 429 too_many_connections (reintentable) antes de leer
el cuerpo. Su cuerpo debe llegar a ≥ 32 KiB/s en cada ventana de 10 s y la petición acabar en
10 s + longitud / 32 KiB/s; si no, se corta y el chunk se descarta. El tope de 4 cabeceras por
cliente (una IPv6 cuenta por su /64) cuenta clientes reales, nunca al proxy. El borde se
declara, no se deduce (enmiendas DS6-01 y DS4-01: antes se deducía de que
STYX_INGEST_LISTEN fuera loopback, y producción escucha en 0.0.0.0 detrás de Traefik, así
que todos los usuarios compartían los 4 sitios de la IP del proxy y cualquier cliente anónimo
desalojaba las conexiones de los demás). ingest_sink.edgeFrom lo resuelve de
STYX_INGEST_PEERS:
local, un
borde TLS en el mismo host, sin tope por dirección;direct: el socket es el cliente, con el tope del SDK; con proxy o prueba es contradictorio;proxy: exige además STYX_INGEST_TRUSTED_PROXIES (la IP de host del proxy) y
STYX_INGEST_PROXY_PROOF_FILE. Conduit (ServeOptions.trusted_proxy) cuenta cada petición
contra la última entrada de X-Forwarded-For sólo si llega del socket del proxy con una
única x-styx-proxy-proof válida; si no, 500 y cierra (falla cerrado), y entre
peticiones la conexión del proxy no cuenta contra nadie.Fuera de loopback sin declarar, una configuración a medias o contradictoria, una subred o una
prueba corta no arrancan. Detrás del proxy hay dos capas a la vez: el borde limita por cliente
real (middleware styx-ingest-limits de deploy/traefik/styx-ingest.yml, check:deploy:
rateLimit e inFlightReq por la dirección del socket con ipStrategy depth: 0, IPv6 por
/64, y un tope total de 64 peticiones en vuelo, los sitios autenticados del daemon; ninguno con
el criterio por defecto de Traefik, enmienda DS7-01: el de inFlightReq es el Host), y el
daemon cuenta ese mismo cliente real, así que un borde mal configurado no devuelve el
desalojo. check:deploy exige al daemon de styx-edge proxy, la IP fija del proxy y la
prueba. Ninguna opción apaga el resto de límites.
Staging <uploadId>.part exclusivo 0600, relativo al fd de la raíz y nunca a través de un
symlink (I7). El nombre final es el SHA-256 del contenido (la raíz BLAKE3 en wire v2, rama
w13/conduit-v2): el cliente nunca elige una ruta y
el filename es sólo metadata validada (sin /, \ ni controles). El finalizador hace
fsync del fichero, el rename y fsync del directorio. Al arrancar se borran los .part de
una ejecución anterior (las sesiones de subida viven en memoria). Todo eso, en el SDK, va por
las entradas de zkit.safety.fs.Root (entries, deleteEntry, renameEntry, syncDir:
un solo componente, el enlace nunca se sigue); conduit no llama a ninguna primitiva de path
cruda en lo que corre en el daemon (ver §5, alcance del audit).
SHA-256 por chunk (Content-Digest) y del fichero entero (en orden, con ReorderBuffer),
comprobado contra el que declara el cliente. En wire v2 (rama w13/conduit-v2): CV BLAKE3 por
chunk (Conduit-Chunk-Cv) en el mismo pase que lo escribe, chunks en cualquier orden y la raíz
del fichero combinada de los CVs, sin releer.
Una sesión de ingesta, un fichero (enmienda ZSDK4-P2-01: antes un solo token encadenaba
create/chunk/complete sin límite mientras playback-svc no cerraba la sesión — 40 ficheros y
40 eventos con una aprobación, fuera de toda cuota porque complete devolvía la sesión y el
staging). Dos capas, cada una suficiente por sí sola:
Grant.max_uploads = 1 por sesión. El SDK cuenta por
Grant.ownerKey() cada create que se confirma y no lo devuelve ni complete, ni abort, ni
el desalojo de terminadas, ni la expiración; sólo abortOwner (el closeIngest, tras el
unbind) olvida la cuenta. Pasado el cupo, 409 upload_limit, no reintentable. Una subida
que falló la verificación también gasta el cupo: otra subida necesita otra sesión.authorize y en stillValidFn, sin esperar a que
playback-svc cierre la sesión. Una renovación del mismo sid hereda la marca. El token sigue
respondiendo status y complete de su subida (idempotentes).Cola de eventos por actor: como mucho 64 sin confirmar (max_events_per_actor) y bytes de
ficheros terminados sin confirmar ≤ su cuota de staging (max_pending_bytes_per_actor). Un
actor no llena la cola (antes, EventQueueFull daba 500 al complete de todos) ni apila
ficheros finales por encima de su cuota mientras catalog no confirma; su complete responde
500 (reintentable) y su staging sigue reservado.
@styx/api-contracts/bus, Zig generado, policy de
BUS_ROUTES: sólo playback-svc y styx-ops): cmd.media.openIngest{capActor, capResource}, cmd.media.pollIngestEvents{ackSeq} (hasta 32 eventos por respuesta) y
cmd.media.closeIngest{sessionId} (idempotente). Si la respuesta de openIngest no se puede
sellar, spire deshace el binding (onReplyFailure).pollIngestEvents) y confirma sólo lo que catalog ya registró. Con la cola llena, el
complete del cliente responde 500 y se reintenta: nunca se pierde un evento.closeFinishedSessions, para que el hueco
no espere a que catalog conteste al relay) y pide a catalog-svc cmd.catalog.registerIngested (policy: sólo playback-svc)
con el hecho. catalog resuelve rootId con su configuración (STYX_INGEST_ROOTS, nunca una
ruta del mensaje) y da de alta el asset con ids deterministas del hash de la transferencia
(raíz BLAKE3 en la rama w13/conduit-v2): repetir es
idempotente. Respuesta ok → se confirma al daemon; error retryable o bus sin respuesta →
no se confirma y se reintenta en la siguiente pasada, como mucho 10 (el max_deliver del
consumidor JetStream de antes); error permanente o intentos agotados → se confirma y se
descarta con un error en el log (el fichero sigue en la raíz): un fichero que ffprobe no
entiende no bloquea a los siguientes.MEDIA_INDEX_LIMITS) es CATALOG_MEDIA_UNSUPPORTED, un error determinista de Postgres
(SQLSTATE 22/23) es permanente, el alta (work, edition, asset, índice) es una transacción y
por el bus sólo viaja el texto fijo del código de error.evt.media.ingestCompleted: su único consumidor era catalog, y un cmd con respuesta
da la confirmación de extremo a extremo que el relay necesita (hasta que catalog guardó el
asset) sin un stream MEDIA que provisionar.@mks2508/chunk-engine o el WASM del SDK) y Swift (C-ABI del SDK):
cuando la web tenga su flujo de subida. Las rutas /ingest/* no están en el CORS de
playback-svc (sólo el CLI las usa hoy).GlobalByteScheduler (r26) cuando exista.audit:safety (dec-0117 §6: zig-pkg/ es
non_production, y la especificación del guard está cerrada). Lo que se movió de styx a
conduit (barrido de staging, borrado, rename final) no se escapa por ahí: conduit lo hace con
zkit.safety.fs.Root, y el mismo safety-audit de styx aplicado al árbol de conduit deja
server.zig, http.zig y wire.zig (lo que corre en el daemon) en 0 hallazgos
(evidencia en el ticket #01). Quedan, en el cliente de styx-upload, dos S1 declarados en
resume_state.zig: el realpath del fichero a subir (sólo forma la clave del registro, no
abre nada) y el makeDir del directorio de estado del usuario. Follow-up SEC-Z06-F4:
auditar las fuentes pinneadas de los SDK que styx enlaza (conduit, spire) con el mismo guard
y su propio baseline, con test, mutante y enmienda de dec-0117 §6.actorId.session de @styx/service-http y de qry.identity.session.styx-upload como cliente), lo
que activa el disparador de reapertura del lock lock/conduit-motor-al-core del ledger de
zkit (su enmienda sigue en la rama docs/enmienda-conduit-spire-styx de ese repo).styx-upload enlazan un solo zkit: el que fija native/zig/build.zig.zon,
el mismo commit que pinnea conduit.zig build test:ingest (sink sobre sockets reales con el cliente del SDK: subida,
resume con token renovado, chunks desordenados, digest y hash malos, path traversal, cuotas,
cola de eventos, .part huérfanos, una sesión renovando en bucle que no deja fuera a otra,
supersesión, topes por actor y total con el token rechazado sin gastar) y test:security
(regla del Authority); mutantes en rojo.control_test.zig (la ingesta por el socket de control de spire: policy,
raíz, binding, revocación); styx_upload.zig (renovación en segundo plano contra un servidor
conduit real con un playback-svc falso que abandona: subida lenta que cruza la vida de la
capability; mutantes sin renovación y con token fijo en rojo).cap/authority.zig (verify/consume, Verified no falsificable, tope de
ingesta) con cuatro mutantes, en el repo spire.ingest-handler.test.ts (cota de abandono con skew y chunk en vuelo, barrido que
reintenta, relay que cierra antes de registrar y con intentos acotados), ingest-routes.test.ts,
ingest-daemon-client.test.ts, ingest-catalog.test.ts, principal-resolver.test.ts
(playback-svc), ingest-completed.test.ts y bus-handlers.test.ts (catalog-svc),
policy.test.ts (service-http).tools/e2e-harness/ingest/run.sh (sobre el bus de spire con ACL; caso I = subida lenta
de 17 MiB a ~400 kbps con chunks de 16 MiB que cruza la caducidad de la capability), evidencia
en docs/track/byte-runtime/ingest/evidence/e2e/.