60 KiB
el tema con cargo xtask tal y como lo tenemos @.coder/2026-05-26-deps-sync.info.md es ¿ cómo actualizamos los crated a nuevas versiones sin romper el sync de dependencias ? ⎿ Read .coder/2026-05-26-deps-sync.info.md (232 lines)
⏺ Voy a verificar el estado actual del sistema xtask y la registry antes de responder.
Searched for 1 pattern, listed 1 directory (ctrl+o to expand)
⏺ ★ Insight ─────────────────────────────────────
- El sistema invierte el flujo habitual de Cargo: en vez de cargo update actualizando Cargo.lock, aquí los bumps de versión son cambios source-controlled sobre registry/dependencies.toml, y el xtask los propaga determinísticamente a cada Cargo.toml.
- El overlay (rustelo-deps-overlay.toml) rechaza por diseño los overrides de versión — esto convierte cada bump en una decisión global y atómica, no en una negociación per-impl.
- El --check reporta drift por conteo de líneas, no diff semántico: es un gate de "estado convergido", no un explicador del cambio. ─────────────────────────────────────────────────
El flujo para actualizar versiones
El sistema no impide actualizar — al contrario, lo canaliza por un único punto. La pregunta correcta no es "cómo actualizo sin romper el sync", sino "el sync convierte el bump en una decisión global; ¿estoy listo a propagar?".
Workflow concreto
-
Editar registry/dependencies.toml ← única fuente de verdad [deps.NAME] version = "X.Y.Z"
-
cargo xtask sync-deps ← propaga a rustelo/Cargo.toml
-
cargo xtask sync-deps --target ../jpl-website (y a cada impl conocida)
-
cargo build / cargo test ← validar en CADA workspace (rustelo + cada impl)
-
Si rompe en impl: a) FORWARD → adaptar código del impl al nuevo API b) REGRESS → bajar la versión en registry/dependencies.toml y volver a sincronizar
Lo que no se puede hacer (y es intencional)
┌─────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────┐ │ Intento │ Por qué falla │ ├─────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤ │ [overlay.sqlx] version = "..." en impl │ El validador rechaza override de versión (línea 90-93 del │ │ │ .info) │ ├─────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤ │ Editar a mano dentro del bloque # >>> │ El siguiente --check lo detecta como drift y sync-deps lo │ │ rustelo-sync:workspace-deps │ sobreescribe │ ├─────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤ │ Per-impl version pin │ "Per-impl version overrides are intentionally NOT supported" │ │ │ (línea 189) │ └─────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────┘
Esto es el corazón del invariante de unificación cross-workspace: si dos workspaces pueden tener versiones distintas, no hay garantía de unificación; el sistema entero pierde su razón de existir.
Qué hacer con bumps que rompen impl
El caso ya documentado: sqlx 0.8 → 0.9 en jpl-website tras un sync.
★ Insight ─────────────────────────────────────
- La "convergencia forzada" no es un bug sino el contrato. El xtask te empuja a tomar la decisión arquitectónica: el registry refleja lo que el framework dice que es canónico, y los impls deben alinearse o el registry retrocede.
- La opción "Regress registry" es legítima pero costosa: significa que el framework no puede avanzar hasta que el impl bloqueante se adapte. Útil como freno cuando hay deadline en impl.
- No existe punto medio. Si necesitas el punto medio, el sistema te está diciendo que el registry está mal-versionado: probablemente la nueva versión no debió subir al registry todavía. ─────────────────────────────────────────────────
Patrón recomendado para bumps frágiles (leptos, axum, sqlx)
- Crear branch en rustelo: bump/leptos-0.X
- Editar registry/dependencies.toml
- cargo xtask sync-deps # rustelo
- cargo build --workspace # ¿compila rustelo?
- cargo xtask sync-deps --target ../jpl-website
- cd ../jpl-website && cargo build # ¿compila impl?
- Si 4 o 6 falla:
- Fix forward → commit a rustelo (si es framework code) → commit a impl (si es código de impl)
- O regress registry y volver al paso 2
- Commit registry + Cargo.toml sincronizados JUNTOS (atomicidad de bump)
El paso 8 es importante: el pre-commit hook (rustelo-deps-sync con --check) bloquea cualquier commit donde registry/dependencies.toml esté desincronizado de Cargo.toml. Esto fuerza que el bump y su propagación sean un solo commit, no dos.
Gap actual del sistema (línea 217 del .info)
▎ Auto-discovery of impls via rustelo.manifest.toml [sync] section (currently impls are reached via explicit --target).
Hoy debes recordar a mano qué impls existen y pasarles --target uno a uno. Esto es un riesgo operacional real: si añades un impl nuevo y olvidas sincronizarlo, drifta silenciosamente hasta que alguien corra el hook en ese repo. Si quieres, esto sería el siguiente xtask útil de implementar (sync-deps --all leyendo de un manifiesto).
¿Quieres que profundice en alguno de los flujos — por ejemplo el patrón forward vs regress para un bump concreto que tengas pendiente — o que mire el código del xtask para confirmar el comportamiento de algún caso edge?
✻ Churned for 55s
❯ zed nos muestra versiones actualizables sobre Cargo.toml pero es sobreescrito por xtask
⏺ ★ Insight ─────────────────────────────────────
- Zed (vía rust-analyzer + crates lsp) detecta updates leyendo tablas [dependencies] o [workspace.dependencies] en archivos Cargo.toml. No reconoce registry/dependencies.toml porque no es un manifiesto Cargo válido — es un schema ad-hoc del xtask.
- El conflicto es estructural: el sistema xtask deliberadamente coloca la fuente de verdad fuera del sitio donde las herramientas Cargo-native esperan encontrarla. Esto es bueno para el invariante cross-workspace pero rompe el flujo editor-driven que ofrecen Zed/rust-analyzer/crates-tooling. ─────────────────────────────────────────────────
Esto es una fricción real, no un mal uso. Tienes tres caminos. Te los presento con los tradeoffs.
Opción A — Reverse-sync (ingest)
Añadir un modo al xtask que lee Cargo.toml y actualiza registry/dependencies.toml (dirección inversa):
- En Zed, click en el upgrade inline sobre Cargo.toml
- cargo xtask sync-deps --ingest ← lee Cargo.toml → escribe registry
- cargo xtask sync-deps --target ../jpl-website
- Commit los tres archivos juntos
Pros: mínimo cambio estructural, conserva el schema actual del registry (con rebase, etc). Contras: dos comandos por bump, fácil olvidar el --ingest y commitear Cargo.toml drifteado → el pre-commit hook lo detecta pero solo si está activo.
Opción B — Convertir el registry en Cargo.toml ficticio (recomendada)
Reemplazar registry/dependencies.toml por registry/Cargo.toml:
registry/Cargo.toml
[workspace] members = [] exclude = []
[workspace.dependencies] leptos = { version = "0.8" } axum = { version = "0.8" } sqlx = { version = "0.9", features = ["postgres"], default-features = false } rustelo_utils = { path = "../crates/foundation/crates/rustelo_utils" }
...
Y en rustelo/Cargo.toml: exclude = ["xtask", "registry"]
Workflow:
- Abrir registry/Cargo.toml en Zed
- Click inline-upgrade — Zed actualiza directamente la fuente de verdad
- cargo xtask sync-deps ← propaga a rustelo/Cargo.toml
- cargo xtask sync-deps --target ../jpl-website
★ Insight ─────────────────────────────────────
- Truco clave: registry/Cargo.toml con members = [] es un workspace válido pero vacío — Cargo no compila nada ahí, pero rust-analyzer sí parsea sus deps y Zed muestra los upgrades disponibles.
- Las paths necesitan re-prefix con ../ (porque la base cambia), pero el xtask ya tiene la lógica de pathdiff para rebase, así que solo invierte el cálculo en una dirección.
- La metadata rebase = false no cabe en el formato Cargo nativo. Solución: moverla a un sidecar registry/sync.toml o inferirla (cualquier path que sale del workspace = no-rebase por defecto). ─────────────────────────────────────────────────
Pros: Zed funciona nativamente, sin pasos extra. Bump y sync siguen siendo dos operaciones explícitas pero la primera ya no requiere herramienta especial. Contras: migración del schema, hay que decidir dónde vive rebase (sidecar o convención). Cargo carga un workspace extra en cada operación (impacto mínimo).
Opción C — Aceptar la fricción
Documentar que Zed no debe usarse para upgrades en Cargo.toml, solo para descubrir versiones disponibles. El bump real se hace a mano en registry/dependencies.toml. Cero código nuevo, máxima fricción humana.
Recomendación
Opción B. Es la única que elimina la fricción en lugar de añadirle un workaround. El xtask sigue siendo necesario (para el splice cross-workspace), pero deja de ser el único punto de entrada — Zed se convierte en una interfaz válida porque la fuente de verdad ahora habla el dialecto que Zed entiende.
La migración es mecánica:
- Renombrar registry/dependencies.toml → registry/Cargo.toml con schema [workspace.dependencies]
- Re-prefix de paths con ../
- Mover rebase a registry/sync.toml (o inferirlo)
- Adaptar xtask/src/registry.rs para leer el nuevo formato
- Añadir exclude = ["registry"] a rustelo/Cargo.toml
- Validar cargo xtask sync-deps --check con la nueva fuente
¿Quieres que lo implemente, o prefieres ver primero el código actual de xtask/src/registry.rs para evaluar el alcance del cambio?
✻ Baked for 1m 1s
※ recap: Goal: resolve friction where Zed shows Cargo.toml upgrades but xtask overwrites them. Current task: choose between reverse-sync (ingest), converting registry to a fake Cargo.toml (recommended), or accepting the friction. Next: decide which option to implement. (disable recaps in /config) por qué implementar la opción B es más recomentable que A ?
⏺ ★ Insight ─────────────────────────────────────
- La pregunta no es trivial: A es menos invasiva (no migra schema, no toca paths) y resuelve el síntoma. B requiere migración. Si solo miráramos esfuerzo, A gana. La elección depende de qué propiedad valoramos más: minimizar el cambio (A) o minimizar la complejidad sistémica resultante (B). ─────────────────────────────────────────────────
Te lo desgloso en ejes concretos. Voy del más decisivo al menos.
- Direccionalidad del flujo
A introduce bidirección. El sync deja de ser una operación push (registry → Cargo.toml) y pasa a tener dos modos: push y pull. Bidirectional syncs son históricamente fuente de bugs sutiles — son el problema clásico de réplica master-master.
B preserva la unidireccionalidad. Siempre registry/Cargo.toml → todos los demás. El xtask no necesita resolver conflictos porque no puede haberlos: solo hay un origen.
- Idempotencia y orden de operaciones
A pierde idempotencia parcial. El resultado de cargo xtask sync-deps depende del estado relativo de dos archivos: si Cargo.toml tiene un upgrade que registry no, el sync lo borra. Esto significa que el orden importa: primero --ingest, después sync-deps. Olvidar el orden = trabajo perdido silenciosamente.
B es idempotente. Corre sync-deps N veces sobre el mismo registry: resultado idéntico. El estado en disco de Cargo.toml es irrelevante para el resultado, porque siempre es destino, nunca origen.
- Coherencia con el modelo de "fuente de verdad"
★ Insight ─────────────────────────────────────
- El diseño actual ya tiene "DO NOT EDIT" en los marcadores del bloque managed. Es una declaración explícita: este texto es derivado, no lo toques. La opción A contradice esa declaración: pide al usuario que edite el bloque managed (vía Zed) como si fuera fuente, y luego ingiera el cambio. Confunde el contrato. ─────────────────────────────────────────────────
A rompe el invariante semántico del marker # >>> rustelo-sync:workspace-deps (managed by xtask — DO NOT EDIT). Con --ingest, el bloque managed pasa a ser editable bajo ciertas condiciones. El marker miente.
B preserva el invariante. El bloque managed sigue siendo solo-escritura-por-xtask. La edición humana ocurre en otro archivo, claramente identificado como fuente.
- Composabilidad con ecosistema Cargo
Esto es el argumento más fuerte a medio plazo.
A solo resuelve Zed. ¿Y cargo upgrade de cargo-edit? ¿Y Dependabot, que mira Cargo.toml? ¿Y cargo outdated? Cada herramienta que en el futuro quieras integrar requerirá su propio adaptador --ingest-from-X, o un paso manual.
B resuelve toda la familia de una sola vez. Cualquier herramienta que sepa leer [workspace.dependencies] (es decir, todas las herramientas Cargo-native) opera sobre la fuente sin saber que existe el sync.
A B
│ │
┌────────┼────────┐ ┌────────┼────────┐
│ │ │ │ │ │
Zed cargo-up Depbot Zed cargo-up Depbot
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
Cargo (custom) (custom) registry/Cargo.toml
│ │ │ │
└───────┼────────┘ ▼
▼ xtask sync
xtask --ingest
▼
registry
▼
xtask sync
En A cada herramienta nueva requiere un adaptador o un paso manual. En B la herramienta apunta directamente a la fuente.
- Superficie de código en el xtask
A añade código: parser inverso (Cargo.toml → dep map), detección de conflicto (¿qué pasa si registry y Cargo.toml difieren en N deps a la vez?), validación de overlay contra ingest, lógica de "qué dirección priorizar".
B reduce código: desaparece el parser custom de dependencies.toml, se reemplaza por el parser de Cargo.toml que el xtask ya tiene (o toml_edit directo). El schema custom se va.
- Modo de fallo y recuperación
┌───────────────────────────┬───────────────────────────────────────────────────────┬──────────────────────────────────────────┐ │ Escenario │ A │ B │ ├───────────────────────────┼───────────────────────────────────────────────────────┼──────────────────────────────────────────┤ │ Editor con upgrade inline │ flujo natural pero peligroso (olvido de --ingest) │ flujo natural y seguro │ ├───────────────────────────┼───────────────────────────────────────────────────────┼──────────────────────────────────────────┤ │ Edit manual en archivo │ difícil distinguir error de uso correcto (Cargo.toml │ error claro: editaste un bloque marcado │ │ equivocado │ es editable a veces) │ "DO NOT EDIT" │ ├───────────────────────────┼───────────────────────────────────────────────────────┼──────────────────────────────────────────┤ │ Dos personas suben bumps │ merge complejo: ambos editaron Cargo.toml + ambos │ merge normal en registry/Cargo.toml, │ │ distintos en paralelo │ --ingest-aron, registry tiene un orden ganador │ conflicto de Git visible │ │ │ silencioso │ │ ├───────────────────────────┼───────────────────────────────────────────────────────┼──────────────────────────────────────────┤ │ Pre-commit detecta drift │ sí, pero el mensaje confunde (¿debía hacer --ingest?) │ sí, mensaje claro: "registry y │ │ │ │ Cargo.toml difieren, corre sync-deps" │ └───────────────────────────┴───────────────────────────────────────────────────────┴──────────────────────────────────────────┘
- Curva de aprendizaje para nuevos contribuidores
A: dos archivos parecidos (Cargo.toml y dependencies.toml con schema custom), una operación que depende de la dirección. Necesita documentación y disciplina humana.
B: un archivo (registry/Cargo.toml) que cualquier rustáceo entiende a primera vista, un comando para propagar (sync-deps). La documentación se reduce a "el registry es un workspace Cargo vacío cuya única función es declarar dependencies".
El caso donde A gana
Hay uno: si los rebase/features/metadata custom del schema actual son suficientemente ricos para que migrarlos a formato Cargo nativo pierda información, A preserva la flexibilidad. Mirando el schema actual:
[deps.NAME] version = "..." path = "..." features = ["..."] default_features = false rebase = false
Solo rebase no tiene correspondencia directa en formato Cargo. El resto (version, path, features, default-features) es exactamente la sintaxis de Cargo workspace.dependencies. Así que la "pérdida" se limita a un campo, que se resuelve con un sidecar registry/sync.toml muy pequeño:
[paths] "platform-nats" = { rebase = false }
★ Insight ─────────────────────────────────────
- En el fondo, A trata el síntoma ("Zed no ve el registry"); B trata la causa ("la fuente de verdad usa un dialecto que el ecosistema no entiende"). La cura siempre es preferible al alivio cuando el coste de la cura es proporcional al problema — y aquí lo es: una migración mecánica de un archivo a otro formato. ─────────────────────────────────────────────────
Resumen ejecutivo
A resuelve Zed con un comando nuevo, manteniendo un schema custom y añadiendo bidireccionalidad. B resuelve Zed (y cualquier futura herramienta Cargo-native) eliminando el schema custom y manteniendo la unidireccionalidad del sync. El coste es una migración única; el beneficio es un sistema más simple y más componible para siempre.
A es la respuesta correcta si la migración es prohibitiva o si hay restricciones que aún no me has contado (p.ej. impls que ya usan el formato dependencies.toml directamente). B es la respuesta correcta en cualquier otro escenario.
¿Hay alguna restricción del lado de jpl-website u otros impls que me esté perdiendo y que pudiera hacer la migración más costosa de lo que asumo?
✻ Brewed for 1m 12s
❯ implementa la opción B
★ Insight ─────────────────────────────────────
- El sync reproduce bit-a-bit la región managed previa — confirma que el cambio de schema preserva la semántica. Para platform-nats (no-rebase) el path se canonicaliza a ../stratumiops/crates/platform-nats igual que antes, pero ahora viene de ../../stratumiops/... en registry/Cargo.toml (registry-relative).
- pathdiff opera textualmente sobre componentes, así que canonicalize antes del diff evita que .. segmentos contaminen el output. Sin canonicalize, el path emitido sería registry/../crates/... — válido pero feo. Con canonicalize, es limpio.
- [workspace.dependencies] con members = [] es un workspace Cargo legítimo: cargo metadata retorna packages: [] sin error. Cargo solo valida sintaxis del manifest, no intenta resolver paths (no hay miembros que las usen). Esto es justo lo que necesitamos: Zed/rust-analyzer escanean la tabla, Cargo no compila nada. ─────────────────────────────────────────────────
GUIDELINE: rust.md (no unwrap, idiomatic, ?, expect, clippy -D warnings clean) IMPLEMENTING: opción B — registry como workspace Cargo vacío editable nativamente por Zed EXCLUDED: documentación nueva, commits, actualización de .coder/2026-05-26-deps-sync.info.md (queda stale; pendiente de tu confirmación para refrescarlo)
Cambios aplicados
┌────────────────────────────┬───────────────┬─────────────────────────────────────────────────────────────────────────────────┐ │ Archivo │ Acción │ Por qué │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ registry/Cargo.toml │ nuevo │ Workspace Cargo vacío con [workspace.dependencies] — fuente de verdad │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ registry/sync.toml │ nuevo │ Sidecar: no_rebase = ["platform-nats"] (única metadata sin equivalente Cargo) │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ registry/dependencies.toml │ borrado │ Schema custom reemplazado │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ xtask/src/registry.rs │ reescrito │ Parsea Cargo.toml (untagged enum string-or-table) + lee sidecar; Dep con │ │ │ │ Deserialize manual │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ xtask/src/render.rs │ reescrito │ Rebase con origen en registry/, canonicalize antes de diff_paths, propaga Result │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ xtask/src/sync.rs │ edición │ Propaga ? desde render_region; fix clippy pendiente │ │ │ mínima │ (manual_pattern_char_comparison) │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ xtask/src/overlay.rs │ edición │ Mensaje de error apunta a registry/Cargo.toml │ │ │ mínima │ │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ xtask/src/main.rs │ edición │ Comentarios docs actualizados │ │ │ mínima │ │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ Cargo.toml (raíz) │ edición │ exclude = ["xtask", "registry"] para que la sub-workspace no contamine │ ├────────────────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────┤ │ .pre-commit-config.yaml │ edición │ Regex ^(Cargo.toml|registry/(Cargo|sync).toml)$ │ └────────────────────────────┴──────────────┴──────────────────────────────────────────────────────────────────────────────────┘
Verificación ejecutada
$ cargo build --manifest-path xtask/Cargo.toml → OK $ cargo clippy --manifest-path xtask/Cargo.toml -D warnings → OK $ cargo xtask sync-deps --check → "up to date" (exit 0) $ cargo metadata --no-deps --manifest-path Cargo.toml → OK $ cargo metadata --no-deps --manifest-path registry/Cargo.toml → OK (packages=[])
La región managed en rustelo/Cargo.toml quedó idéntica al estado previo a la migración — convergencia bit-a-bit valida que la traducción del schema preserva semántica.
Flujo nuevo para Zed
- Abrir registry/Cargo.toml en Zed → rust-analyzer detecta [workspace.dependencies] y muestra upgrades inline.
- Click en el upgrade → Zed escribe directamente en la fuente de verdad.
- cargo xtask sync-deps → propaga a rustelo/Cargo.toml.
- cargo xtask sync-deps --target ../jpl-website → propaga a cada impl.
- Pre-commit hook bloquea commits desincronizados (fires en cambios a Cargo.toml, registry/Cargo.toml o registry/sync.toml).
Pendientes (no implementados, por si los quieres)
- Actualizar .coder/2026-05-26-deps-sync.info.md: el doc describe el formato viejo (dependencies.toml, schema custom). Su modelo conceptual sigue siendo correcto pero los detalles concretos están stale.
- Sincronizar jpl-website u otros impls: no he tocado nada fuera de rustelo. Cuando ejecutes cargo xtask sync-deps --target ../jpl-website la primera vez producirá output equivalente al anterior (la región managed allí seguirá la misma forma).
- Auto-discovery de impls: sigue pendiente del .info original; no es regresión sino limitación heredada.
STATUS: Complete
⏺ ★ Insight ─────────────────────────────────────
- En rustelo las recetas viven en el top-level (justfiles/build.just importado con import), así que se invocan planas (just deps-sync). En jpl-website el justfile usa mod build "just/build.just", así que se invocan con namespace (just build deps-sync). Misma funcionalidad, dos convenciones distintas según el patrón del proyecto.
- Para el impl, la receta encapsula tres detalles operacionales molestos: la ruta al xtask (../rustelo/xtask/Cargo.toml), las flags --rustelo-root ../rustelo --target ., y el override de CARGO_TARGET_DIR para no contaminar el target volume-pinned del framework. El humano nunca tiene que recordarlos. ─────────────────────────────────────────────────
Resumen de cambios
Documentación
- .coder/2026-05-26-deps-sync.info.md — actualizado: schema nuevo (registry/Cargo.toml + sync.toml), workflow editor-driven con Zed, rationale de la migración, verificación de bit-identical reproduction.
- rustelo/reflection/qa.ncl — deps-sync-workflow reescrito: bump editor-driven como flujo recomendado, schema Cargo-native, sidecar no_rebase, sección "why a fake Cargo.toml".
- jpl-website/reflection/qa.ncl — deps-sync-impl-workflow actualizado: referencia a ../rustelo/registry/Cargo.toml, mención de Zed inline upgrades para descubrir versiones disponibles.
Recetas just │ rustelo │ just deps-sync │ propagar registry → rustelo/Cargo.toml │ ├─────────────┼──────────────────────────────────────────┼──────────────────────────────────────────────┤ │ rustelo │ just deps-sync-check │ verificar drift sin escribir │ ├─────────────┼──────────────────────────────────────────┼──────────────────────────────────────────────┤ │ rustelo │ just deps-sync-impl ../jpl-website │ propagar a impl arbitrario │ ├─────────────┼──────────────────────────────────────────┼──────────────────────────────────────────────┤ │ rustelo │ just deps-sync-check-impl ../jpl-website │ verificar drift en impl │ ├─────────────┼──────────────────────────────────────────┼──────────────────────────────────────────────┤ │ rustelo │ just deps-sync-all │ rustelo + impls conocidos en un solo comando │ ├─────────────┼──────────────────────────────────────────┼──────────────────────────────────────────────┤ │ jpl-website │ just build deps-sync │ pull desde ../rustelo/registry/Cargo.toml │ ├─────────────┼──────────────────────────────────────────┼──────────────────────────────────────────────┤ │ jpl-website │ just build deps-sync-check │ verificar drift contra rustelo │ └─────────────┴──────────────────────────────────────────┴──────────────────────────────────────────────┘
Verificado $ just deps-sync-check → ./Cargo.toml is up to date (exit 0) $ just --list (rustelo) → 5 recipes deps-sync* visibles $ just --list build (jpl) → deps-sync, deps-sync-check visibles bajo build::
STATUS: Complete
escribe un doc en .coder para pedir en una sesión claude code que se implemente este mismo mecanismo tal cual (opción B e incluir las recetas just) de sync de crates en un proyecto nuevo
⏺ ★ Insight ─────────────────────────────────────
- El doc está estructurado como prompt para una sesión Claude Code, no como referencia general. Cada task usa el formato obligatorio del proyecto (COMPLEXITY/DEPENDENCIES/RISKS/SUCCESS_CRITERIA) sin estimaciones temporales, lo que evita que la sesión receptora "improvise" pasos.
- Las tasks marcan dependencias explícitas (1→3→4→5→6→[7,8,9]→10), así que si la sesión receptora se interrumpe a la mitad, puede retomar desde un estado parcial seguro sin reconstruir contexto.
- El doc separa lo que se debe copiar tal cual (xtask source, markers # >>> rustelo-sync) de lo que hay que adaptar (paths registry-relativos, lista de deps, hook id). Esa separación es lo que distingue una replicación correcta de una "reescritura libre". ─────────────────────────────────────────────────
Resumen del doc
Ruta: .coder/2026-05-26-deps-sync-replication.plan.md
Es un brief auto-contenido para entregar a otra sesión Claude Code. Estructura:
- Why / Reference implementation — contexto mínimo + apuntadores a la doc canónica de rustelo
- Preconditions — qué debe tener el proyecto destino
- Open questions — 5 preguntas que la sesión receptora debe hacer al usuario antes de empezar (project root, project name, framework-vs-impl, no-rebase deps, downstream impls)
- 10 tasks ordenadas con COMPLEXITY/DEPENDENCIES/RISKS/SUCCESS_CRITERIA cada una: - 1: crear registry/Cargo.toml - 2: crear registry/sync.toml - 3: copiar xtask - 4: editar root Cargo.toml (exclude) - 5: añadir alias .cargo/config.toml - 6: primer sync + commit - 7: pre-commit hook - 8: recetas just (framework) - 9: recetas just (impl) — opcional - 10: validación end-to-end
- Adaptations checklist — qué cambiar vs rustelo (paths, nombre, lista de deps, etc.)
- Things to leave untouched — marker text, standalone-workspace de xtask, deserialización dual de Dep
- Order of independence — qué subconjuntos de tasks son estados estables si se quiere parar a mitad
- See also — los tres docs/QA de referencia en el repo rustelo
El doc asume que rustelo está accesible para cp -r ../rustelo/xtask .; si no lo está, la sesión receptora debe preguntar dónde vive (mencionado explícitamente en la Open question #1 y Task 3 NOTES).
STATUS: Complete
exlica lo de Auto-discovery de impls: sigue pendiente del .info original; no es regresión sino limitación heredada.
⏺ ★ Insight ─────────────────────────────────────
- "Limitación heredada" significa que existía en el diseño original con dependencies.toml y la migración a opción B no la introdujo ni la resolvió. Decir "no es regresión" es importante: si el usuario migrara con la expectativa de que B arreglase TODO lo del .info original, este punto le decepcionaría sin esta aclaración. ─────────────────────────────────────────────────
Qué significa "auto-discovery de impls"
Hoy, cuando ejecutas cargo xtask sync-deps, el xtask sincroniza un solo target — el que recibe por CLI. Hay tres modos posibles:
cargo xtask sync-deps → rustelo/Cargo.toml cargo xtask sync-deps --target ../jpl-website → jpl-website/Cargo.toml cargo xtask sync-deps --target ../otra-impl → otra-impl/Cargo.toml
El xtask no sabe qué impls existen en el ecosistema. Si añades ../proyecto-z mañana como consumidor del registry, el xtask no se entera hasta que alguien le pase --target ../proyecto-z explícitamente.
Auto-discovery sería el mecanismo opuesto: el xtask conoce la lista completa de impls y puede iterar sobre ellos sin que un humano se la recuerde:
cargo xtask sync-deps --all → sincroniza rustelo + cada impl declarada en algún lugar
Por qué importa operacionalmente
Hoy hay tres puntos de fricción:
- Memoria humana: para mantener el ecosistema en sync, alguien debe recordar la lista de impls. En mi receta deps-sync-all añadí ../jpl-website hardcoded — si mañana aparece ../jpl-blog, hay que editar la receta. Y nadie lo recordará hasta que rompa algo.
- Drift silencioso: si una impl nueva no se añade a la lista, los pre-commit hooks de cada repo seguirán fallando independientemente (eso protege la integridad de cada repo aisladamente), pero no hay un sitio donde rustelo te diga "atención, hay 3 impls drifteadas". Cada repo es una isla hasta que alguien hace commit allí.
- Onboarding: un colaborador nuevo en rustelo no sabe automáticamente qué impls hay que sincronizar tras un bump de versión. Tiene que preguntar o leer el README/justfile.
Por qué no es regresión de la migración a opción B
El diseño original (registry/dependencies.toml schema custom) tampoco tenía auto-discovery. El .info original lo lista en su sección Open items:
- Auto-discovery of impls via
rustelo.manifest.toml[sync]section (currently impls are reached via explicit--target).
La migración a opción B preservó todo el comportamiento del CLI: --target sigue funcionando idéntico, los markers son los mismos, el splice es el mismo. Solo cambió el formato de la fuente de verdad, no la topología del sync. Por eso uso "limitación heredada" — viaja con el sistema desde el diseño original.
★ Insight ─────────────────────────────────────
- En realidad la migración a opción B hace este open item más fácil de resolver, no más difícil. Antes había que extender el schema custom de dependencies.toml ([meta], [deps.X], y ahora añadir [sync.impls]). Con opción B, ese tipo de metadata no-Cargo ya tiene un sitio natural: registry/sync.toml. Solo hay que añadir un campo y una iteración en sync.rs. ─────────────────────────────────────────────────
Cómo se resolvería (boceto, no implementado)
Extender registry/sync.toml:
no_rebase = ["platform-nats"]
[impls] known = [ "../jpl-website", "../jpl-blog", # ejemplo futuro ]
En el xtask, añadir un flag --all:
// sync.rs (boceto) if args.all { let sidecar = registry::load_sidecar(&args.rustelo_root)?; // sync rustelo sync_target(&args.rustelo_root, /target=/ &args.rustelo_root, …)?; for impl_path in sidecar.impls.known { let target = args.rustelo_root.join(&impl_path); sync_target(&args.rustelo_root, &target, …)?; } }
Y la receta just simplificaría:
antes
deps-sync-all: @just deps-sync @just deps-sync-impl ../jpl-website # hardcoded
después
deps-sync-all: cargo xtask sync-deps --all # lista vive en sync.toml
Por qué sigue pendiente
Es un trade-off consciente, no un olvido:
┌───────────────────────────────────────┬──────────────────────────────────────────────────────────────┐ │ Argumento a favor de implementarlo │ Argumento a favor de aplazarlo │ ├───────────────────────────────────────┼──────────────────────────────────────────────────────────────┤ │ Elimina memoria humana del flujo │ Hoy solo hay 1 impl (jpl-website); sobre-ingeniería para N=1 │ ├───────────────────────────────────────┼──────────────────────────────────────────────────────────────┤ │ Drift detection centralizado │ Cada impl ya tiene su pre-commit hook independiente │ ├───────────────────────────────────────┼──────────────────────────────────────────────────────────────┤ │ Onboarding más simple │ El README/justfile ya documenta el comando manual │ ├───────────────────────────────────────┼──────────────────────────────────────────────────────────────┤ │ sync.toml ya tiene sitio para meterlo │ Es código nuevo en xtask, validación, tests │ └───────────────────────────────────────┴──────────────────────────────────────────────────────────────┘
El veredicto del .info original fue "no urgente, pero está identificado". Coincido. Si alguna vez aparece la segunda impl, vale la pena cerrarlo; con una sola impl, el --target manual es perfectamente operativo.
Resumen en una línea
▎ Auto-discovery = el xtask conoce la lista de impls que consumen el registry y puede sincronizarlas en bloque sin que un humano se ▎ las recuerde. Sigue pendiente porque con una sola impl no compensa el código adicional, pero la opción B (sidecar sync.toml) ▎ deja el camino más despejado para resolverlo el día que haga falta.
✻ Churned for 59s
❯ implementalo, en cuanto verifiquemos jpl-website vamos a crear más sites
Plan en orden de dependencias:
TASK 1: Extender sidecar con [impls.known] en registry.rs COMPLEXITY: simple DEPENDENCIES: none RISKS: typos en sync.toml apuntan a paths que no existen → mitigación: validar existencia al cargar SUCCESS_CRITERIA: load_sidecar() retorna no_rebase + impls
TASK 2: Refactorizar sync.rs para extraer sync_one() y añadir driver --all COMPLEXITY: simple DEPENDENCIES: TASK 1 RISKS: en --check --all, error en target N no debe abortar el chequeo de N+1 SUCCESS_CRITERIA: sync_one por target, drift de cualquier target → exit 1 al final
TASK 3: Añadir flag --all en CLI (conflicts_with --target y --overlay) COMPLEXITY: trivial DEPENDENCIES: TASK 2 SUCCESS_CRITERIA: --all sin --target ni --overlay funciona; combinarlos error
TASK 4: Declarar ../jpl-website en registry/sync.toml [impls.known] COMPLEXITY: trivial DEPENDENCIES: TASK 3 SUCCESS_CRITERIA: cargo xtask sync-deps --all itera 2 targets
TASK 5: Sustituir deps-sync-all en justfile + añadir deps-sync-check-all COMPLEXITY: trivial DEPENDENCIES: TASK 4 SUCCESS_CRITERIA: just deps-sync-check-all exit 0
TASK 6: Documentación (info, QA, replication plan) COMPLEXITY: simple DEPENDENCIES: TASK 5 SUCCESS_CRITERIA: auto-discovery sale de Open Items y aparece en Architecture
Empiezo.
⏺ ★ Insight ─────────────────────────────────────
- El refactor importante fue normalizar paths registry-relative → rustelo-root-relative en el loader, en lugar de cambiar la lógica del render. Eso preservó el contrato de Rebase::translate_path y evitó romper el rustelo-root-relativos, no registry-relativos). Una capa de traducción centralizada en un solo punto = un solo sitio que mantener.
- sync_one retorna un enum SyncOutcome en vez de un ExitCode. Eso permite al driver de --all agregar drift de N targets sin perder información: cada target imprime su outcome, y solo al final el run agregado decide el código de salida. Si hubiera usado ExitCode, habrías tenido que return-early en el primer drift.
- Adding una impl ahora es exactamente una línea en registry/sync.toml. No xtask, no recetas. Esa es la propiedad clave para tu plan de crear más sites: la fricción operacional al escalar de N=1 a N=K se mantiene constante (O(1) por impl). ─────────────────────────────────────────────────
GUIDELINE: rust.md (no unwrap, idiomatic, ?, expect, clippy -D warnings clean) IMPLEMENTING: auto-discovery de impls vía [impls.known] en sync.toml + --all en xtask EXCLUDED: ondaod evaluation (sin solicitud de adr?), commits
Cambios
┌───────────────────────┬──────────────────────────────────────────────────┐ │ Archivo │ Acción │ ├───────────────────────┼──────────────────────────────────────────────────┤ │ xtask/src/registry.rs │ Sidecar struct, load_sidecar() público, │ │ │ normalize_registry_path() (strip ..) │ ├───────────────────────┼──────────────────────────────────────────────────┤ │ xtask/src/sync.rs │ refactor en sync_one() + driver de --all; agrega │ │ xtask/src/sync.rs │ refactor en sync_one() + driver de │ │ │ --all; agrega drift sin fail-fast │ ├─────────────────────────────────────┼────────────────────────────────────┤ │ │ revert al contrato original │ │ xtask/src/render.rs │ (Dep.path rustelo-root-relative, │ │ │ sin canonicalize) │ ├─────────────────────────────────────┼────────────────────────────────────┤ │ xtask/src/main.rs │ flag --all, --target ahora │ │ │ Option │ ├─────────────────────────────────────┼────────────────────────────────────┤ │ registry/sync.toml │ añadido [impls] known = │ │ │ ["../jpl-website"] │ ├─────────────────────────────────────┼────────────────────────────────────┤ ──────────────────────────────────────── Archivo: .coder/2026-05-26-deps-sync.info.md Acción: [impls] documentado, --all documentado, auto-discovery movida de Open → Closed ──────────────────────────────────────── Archivo: reflection/qa.ncl (rustelo) Acción: sección PROPAGATE TO IMPLEMENTATIONS actualizada con --all ──────────────────────────────────────── Archivo: .coder/2026-05-26-deps-sync-replication.plan.md Acción: Task 2 y Task 8 actualizadas con [impls.known] y --all
Verificación
$ cargo xtask sync-deps --check → exit 0 $ cargo xtask sync-deps --all --check → 2 targets, exit 0 $ cargo xtask sync-deps --all --target X → rejected (exit 2) $ cargo xtask sync-deps --all --overlay Y → rejected (exit 2) $ cargo clippy --manifest-path xtask/Cargo.toml -- -D warnings → exit 0 $ just deps-sync-check-all → 2 targets, exit 0
Para los próximos sites
Cuando crees ../site-nueva, el workflow es:
- Crear el workspace ../site-nueva con [workspace.dependencies] (vacío o pobladio).
Para los próximos sites
Cuando crees ../site-nueva, el workflow es:
- Crear el workspace ../site-nueva con [workspace.dependencies] (vacío o pobladio).
- Opcional: crear ../site-nueva/rustelo-deps-overlay.toml con [overlay.] y [extra.].
- Editar registry/sync.toml: añadir "../site-nueva" al array [impls.known].
- cargo xtask sync-deps --all — itera rustelo + jpl-website + site-nueva.
- (Opcional) Replicar el hook pre-commit en ../site-nueva/.pre-commit-config.yaml apuntando al xtask de rustelo.
No hay cambios al xtask ni a las recetas just. La operación es O(1) por impl añadida.
STATUS: Complete
⏺ Confirmado. El doc ahora refleja el modelo [impls.known] + --all en cuatro puntos:
┌──────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────┐ │ Sección │ Contenido │ ├──────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤ │ Open questions (línea 67) │ Pregunta sobre impls indica que se encodean en registry/sync.toml [impls.known], no │ │ │ en la receta │ ├──────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤ │ Task 2 RISKS (línea 104) │ Añade riesgo de impl path sin Cargo.toml │ ├──────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤ │ Task 2 SUCCESS_CRITERIA (líneas │ Schema de sync.toml incluye [impls] known = [...] │ │ 113–114) │ │ ├──────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤ │ Task 2 NOTES (línea 121) │ Documenta que [impls.known] habilita --all y que añadir impls es one-line edit │ ├──────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤ │ Task 8 recipes (líneas 269–278) │ deps-sync-all y deps-sync-check-all usan cargo xtask sync-deps --all │ ├──────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤ │ Adaptations checklist (línea 354) │ Señala que la lista de impls va en sync.toml, no en la receta │ └──────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────┘
STATUS: Complete