Vista generada de dec-0103: La promoción de Candidate H consume zkit como dependencia real
docs/decisions/dec-0103-byte-runtime-consume-zkit.mdVista generada desde
docs/decisions/dec-0103-byte-runtime-consume-zkit.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-08-27 |
| Fichero | docs/decisions/dec-0103-byte-runtime-consume-zkit.md |
Enmienda a: r44
Por qué importa (del frontmatter del ADR):
Gobierna CÓMO se ejecuta la promoción de Candidate H en
track/byte-runtimeahora que las primitivas rescatadas viven en un repo externo (MKS2508/zkit). Sin este ADR, un executor de M6 leería r44 y reimplementaría ennative/zig/primitivas que ya existen — deshaciendo la promoción de la condición #5 de r44 ejecutada el 2026-08-27. También resuelve la colisión entre las condiciones #1 y #5 de r44, que se volvieron mutuamente inejecutables al retirarse el spike.
Nodos del roadmap que lo citan en refs: track/byte-runtime, track/byte-runtime/m5, track/byte-runtime/m6, track/byte-runtime/m7, track/byte-runtime/sec, track/byte-runtime/zkit, track/byte-runtime/ingest
Páginas de la documentación que lo citan: Puesta en marcha de un nodo (implementado), Generadores de la documentación (especificado), Guardas del repositorio (implementado), Arquitectura (implementado), Seguridad del data plane (implementado), Byte runtime (implementado), zkit, spire y conduit (implementado)
Leído de docs/decisions/dec-0103-byte-runtime-consume-zkit.md, el fichero canónico.
AskUserQuestion por el corolario
anti-trivialidad del criterio de recomendación: la respuesta arquitectónica no admitía tensión real.r44 (condiciones #1, #2, #5)dec-0102 (sin el levantamiento del freeze, track/byte-runtime no avanza)r44 (2026-07-12) seleccionó Candidate H como la arquitectura del byte-runtime, y su
condición #5 mandaba: "el código del spike se elimina tras promover (regla spikes)".
Esa condición se ejecutó el 2026-08-27, seis semanas después, pero no hacia
native/zig/media-core/ como r44 asumía implícitamente: las primitivas genéricas se rescataron a
un repo nuevo standalone, MKS2508/zkit, dentro del programa de convergencia del toolchain Zig
(zkit.model.yml, nodo zkit/rescue → zkit/green, mergeado en a3ed91f).
Superficie pública de zkit hoy (src/root.zig, verificado):
| Símbolo | Origen | Uso esperado en byte-runtime |
|---|---|---|
SubscriberQueue | spikes/candidate-h | fanout ready-driven (r46 §3) |
ReorderBuffer / SequenceNumber | spikes/candidate-h | delivery pump |
HungWorkerWatchdog / WatchdogStatus | spikes/candidate-h | stall/detach (r44 #8) |
HandleSlab | hyperdiff + candidate-h | HandlePool de producción (r44 #2) |
TrackingAllocator | hyperdiff | instrumentación de memoria |
El resultado es que r44 quedó descrita contra un layout que ya no existe. Este ADR la re-ancla.
track/byte-runtime consume zkit como dependencia .zon real. No se reimplementa.Prohibido copiar, vendorizar o re-derivar SubscriberQueue, ReorderBuffer,
HungWorkerWatchdog, HandleSlab o TrackingAllocator dentro de native/zig/. Ya se
promovieron una vez; duplicarlas sería des-ejecutar la condición #5 de r44 y crear exactamente
el par divergente que la regla de spikes existe para evitar.
Coste asumido y aceptado: styx gana una arista de dependencia externa hacia un repo que hoy no tiene release ni versionado semántico. Es más barato que dos copias que derivan.
Verificado 2026-08-27: zkit/src/handle.zig tiene cero atomics y documenta literalmente
"Not thread-safe. Callers must synchronize externally... (typical FFI pattern: single-threaded
consumer)". subscriber_queue.zig idem. El rescate preservó fielmente el contrato
single-threaded del spike — que es correcto como rescate, e insuficiente para el delivery plane
de styx, que es multi-thread (las 4 races TSAN que r44 #2 nombra).
Por tanto la condición #2 de r44 no está satisfecha por el rescate, y su trabajo no vive en
styx: una variante "styx-atómica" en native/zig/ sería el fork que el punto 1 prohíbe. El
trabajo es un nodo del roadmap de zkit, y track/byte-runtime lo consume cuando aterrice.
r44 #1 pide que la gauntlet compuesta H-F5 corra antes de promover a media-core/, sobre el
spike. r44 #5 mandó borrar el spike. Ejecutada #5, la letra de #1 es inejecutable: no queda
código del spike sobre el que correrla (EVIDENCE-REPORT.md §5 confirma que nunca llegó a correrse
ahí). No es una reinterpretación de conveniencia — son dos condiciones de la misma ADR que se
volvieron incompatibles al ejecutarse una de ellas.
Resolución que preserva la intención de #1 (que nada sin validar llegue al árbol de producción):
La promoción de M6 se materializa en rama. M7 (gauntlet) es el gate del merge de esa rama, no un sello posterior.
track/byte-runtime/m6NO puede declararsedonesin que M7 haya pasado.
M6 → M7 en el DAG es el orden de ejecución; M7 bloquea el cierre de M6 es el contrato.
zkit.model.yml: trans/wire-consumers se parte en dos — trans/wire-styx (esta arista,
owner axon2) y trans/wire-hyperdiff (territorio de axon1). Ambos blocked por la misma
pasada de convergencia. La diferencia real es de método, no de blocker: hyperdiff se wirea
con parity gate (lock/bindings-parity) porque tiene paquetes publicados; styx no lo
necesita (es el origen del rescate, cero consumidores publicados).
Corrección de waxin sobre mi primer borrador, que daba «está en producción» como motivo de
aplazamiento: "da igual que mc esté en prod, no lo rompes en prod" — el parity gate es la
tarea que impide romperlo, no una razón para no hacerlo. Cola-perro, y la había invertido yo.docs/track/byte-runtime/plans/byte-runtime-decompose.plan.md: addendum — M5/M6 nombran los
símbolos de zkit; M8 registra que el borrado del spike ya existe en rama.styx.model.yml: track/byte-runtime cita dec-0103.lock/standalone (zkit.model.yml). Ese lock dice "CERO aristas .zon ... en
esta pasada" y su razón es que el camino verde del toolchain no dependa de que zkit exista.
Sigue intacto: la arista de este ADR es posterior a que la convergencia de styx a
0.17.0-dev.1884 aterrice en master (rama chore/zig-017-convergence, hoy sin mergear y en
review de waxin). El nodo nace bloqueado, no accionable.@mks2508/hyperdiff-* es territorio de axon1.path vs tarball con hash) es
decisión de implementación de trans/wire-styx.C12-e2e (r44 #3), que sigue bloqueado por TKT-027/U4 — cerrarlo por scope creep
sigue explícitamente prohibido por r44.w4/zkit)Registro, no decisión nueva: §1 se cumple y queda vigilado. La arista .zon apunta a
MKS2508/zkit@c6195d3 (rama feat/styx-extraction; el hash va en native/zig/build.zig.zon).
Cada símbolo tiene un consumidor en un camino de producción de styx, salvo los marcados como
«tests»:
| Símbolo de zkit | Consumidor de producción en styx |
|---|---|
time.{monotonicNs,monotonicMs,sleepNs,ns_per_ms,Stopwatch} | todo el daemon (relojes, esperas, métricas) |
sync.{Mutex,Condition} | packager (transmux), backpressure_waker |
safety.Mutex | todos los locks del daemon y de media-core (antes pthread_mutex_t) |
fs.{File,createFile,openRead,readFileAlloc,makeDir,openRaw,max_path_bytes}, os.{getenv,getenvInt,randomBytes} | caches L2/L3, certificados TLS, config por entorno, ids de sesión |
safety.fs (Root, openFile) | LocalFileSource: todo acceso a medios bajo STYX_MEDIA_ROOT |
errors.ErrorSpaceWith | SourceErrorSpace de local_file.zig → TS generado en @styx/source-sdk |
ZeroCopyBuffer, BufferGuard | objetos MoQT, caché L1 |
HandleSlab (+ encodeHandle/decodeSlot/decodeGeneration) | tiered_cache (BufferHandle{slot, generation} de C7) |
PriorityQueue | scheduler del daemon |
LatestValue | TransportSignalsChannel |
AtomicHistogram | histograma de rangos del transporte |
BoundedQueue | PendingDeliveryQueue (MoQT) |
CancelToken | contrato del motor de medios (probe/demux) |
safety.BoundedReader, safety.BitReader | parsers de contenedor (ISOBMFF, Matroska, codec configs, frames) |
safety.BudgetAllocator | presupuesto de recepción por sesión MoQT y del daemon (SEC-Z08) |
TrackingAllocator | contabilidad de memoria del daemon (main.zig) |
testing.Fixture, safety.fuzz, safety.LeakReport | tests |
Sin consumidor en styx hoy, así que no se consumen (nada que wirear sin workload, r28):
SubscriberQueue, ReorderBuffer/SequenceNumber, HungWorkerWatchdog, WakeupPipe,
ConcurrentHandleSlab, safety.{Handle,TypedSlab}, safety.checked, log.
Las copias locales que existían se borraron en la misma rama (reloj, mutex/cond pthread,
shims de fs/env/random, test_fixture.zig, ZeroCopyBuffer/lifecycle, PriorityQueue,
el canal de señales, el histograma, la cola de entrega, los lectores de bytes/bits de los
parsers y el reparto del handle del slab).
Guard: zig build audit:safety (regla Z1, bun run check:zkit-no-reimpl) falla si un
fichero de media-daemon/ o media-core/ declara un símbolo con nombre de zkit que no es un
alias de zkit, llama a la libc que zkit envuelve o escribe un extern "c" a mano. Los nombres
los lee del propio zkit fijado, así que un símbolo nuevo de zkit queda protegido sin tocar el
guard. Corre en zig build y zig build test.